不懂這幾點就落後了:Android、Python工程師必讀!

Android 平臺的Python:

(本文以Python3爲例,Python3是未來,大家都懂的)
Python作爲一個功能強大又語法簡潔的語言,其應用已無需多言。要想在Android平臺運行起Python,也有方案實現,其實質就是在Android系統上搭建Python環境。對此Google已經提供了SL4A(Scripting Layer for Android )方案,支持多種腳本語言,除此之外,還可以使用一個叫QPython的app,可以直接在Android上編寫以及運行Python代碼。但其實意義不大,寫好的Python代碼並不是以一個獨立的app進程運行的,只不過是在QPython這個應用中運行而已。這兩者都不符合我現在要討論的東西,如題,筆者想要討論的是如何在Android平臺使用Java與Python代碼相互調用,換言之,就是如何在Android工程中嵌入一個Python解釋器。

首先談一點,爲什麼要在Android平臺使用Python?Python擁有衆多強大的第三方庫和框架,在機器學習、大數據處理等諸多方面都有不俗的應用。另外,就語法而言,Python比Java更加簡潔,同時又功能強大,既可面向過程亦可面向對象,而不像Java一樣,是一種純粹的面嚮對象語言,哪怕打印一句話也需要先創建類。Python作爲一種腳本語言,可以邊解釋邊執行,而不需編譯,另外Python中存在的元類,可以使我們動態的創建類,如此可以在不需要重新編譯安裝apk的情況下,動態的由遠程服務端爲Android項目添加功能。我們還可以將Python已有的一些東西移植到Android平臺,例如tornado、django等,總之玩法多多。

在Android平臺,官方並不支持直接使用Python開發app,基於虛擬機的Java(或kotlin)纔是更好的選擇,其他語言是無法自如的使用官方Framework提供的api的,尤其是在程序界面的表現上,典型的反例就是kivy。什麼是kivy,可自行了解,但要解決Android平臺上Java與Python的交互,kivy確實是一個方向,而且是一個醍醐灌頂的方向。kivy實際上已經解決我們需要實現的目的,模仿Android平臺上的kivy實現機制即可。但是,kivy使用的是Cython解釋器,非CPython解釋器,需要學習Cython語法,並且在其他一些方面存在一些限制。kivy給我們提供的思路就是藉助Java的jni機制,實現Python與Java的交互。即在一個安卓apk工程中包含一個cython.so解釋器,通過jni機制調用解釋器去解釋執行Python代碼,通過Java調C,C調Python實現交互。有一點需要說明,Python作爲一門膠水語言,Python與C的交互是非常方便的,因此才能實現這一系列調用。

也爲大家推薦了技術教程:

Android視頻編碼和直播推流

Android高手進階

一天掌握Scrapy爬蟲框架

【Python全棧】網絡爬蟲體驗課

Django Web框架/Python最牛框架

探究Linux的總線、設備、驅動模型

Linux 實用講解+實操+面試題

zabbix企業實戰應用

Linux視頻教程

關於該種方案,已有國外網友實踐,原理如下

這裏寫圖片描述

鏈接地址

除此之外,本博客將通過另外兩種方案實現。其中第一種類似上述方案,但集成CPython解釋器,非Cython,因此需要掌握如何實現Python與C的交互。

Python與C交互基礎
C調用Python

簡單使用
流程:

初始化Python解析器
執行Python代碼,字符串,對象或模塊。
關閉Python解析器。
創建一個.c源文件,代碼如下,創建一個pytest.py文件,實現一個printTime函數

#include<Python.h>
int main()
{
Py_Initialize();//初始化Python解析器
if (!Py_IsInitialized())
{
printf("Initialize failed");
return -1;
}
PyRun_SimpleString("print('hello C !')");
PyRun_SimpleString("import pytest");
PyRun_SimpleString("pytest.printTime()");
Py_Finalize();/關閉Python解析器
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:除了用PyRun_SimpleString函數直接運行代碼,還可以使用PyRun_SimpleFile函數運行一個Python腳本
原型:PyRun_SimpleFile(FILE fp, const char filename) ,由於版本差異,使用該方式可能會造成崩潰,推薦另一種替代方式
PyRun_SimpleString(“execfile(“test.py”)”)

調用Python函數
pytest.py

import time

def printTime():
print('invoke printTime:'+str(time.time()))
return (1,)#元組只有一個元素時,需在末尾加逗號
1
2
3
4
5
C 代碼

int main()
{
PyObject module_name,module,func,dic;
char fun_name = "printTime";//需調用的Python函數名
PyObject
resultValue;

Py_Initialize();
if (!Py_IsInitialized())
{
    printf("Initialize failed");
    return -1;
}

//導入Python 模塊並檢驗
module_name = Py_BuildValue("s", "pytest");
module = PyImport_Import(module_name);

if (!module)
{
    printf("import test failed!");
    return -1;
}

//獲取模塊中的函數列表,是一個函數名和函數地址對應的字典結構
dic = PyModule_GetDict(module);
if (!dic)
{
    printf("failed !\n");
    return -1;
}

func = PyDict_GetItemString(dic, fun_name);
if (!PyCallable_Check(func))
{
    printf("not find %s\n", fun_name);
    return -1;
}

int r;
//獲取Python函數返回值,是一個元組對象
resultValue = PyObject_CallObject(func, NULL);
PyArg_ParseTuple(resultValue, "i", &r);
printf("result :%d\n", r);

Py_DECREF(module);
Py_DECREF(dic);
Py_Finalize();
return 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
基礎api

C API 調用 Python 對應
PyImport_ImportModel import module
PyImport_ReloadModule reload(module)
PyImport_GetModuleDict module.dict
PyDict_GetItemString dict[key]
PyDict_SetItemString dict[key] = value
PyDict_New dict = {}
PyObject_GetAttrString getattr(obj, attr)
PyObject_SetAttrString setattr(obj, attr, val)
PyObject_CallObject funcobj(argstuple)
PyEval_CallObject funcobj(
argstuple)
PyRun_String eval(exprstr) , exec(stmtstr)
PyRun_File exec(open(filename().read())
Py_BuildValue()函數
作用:將C/C++類型類型的數據轉變成PyObject*對象。

原型:PyAPI_FUNC(PyObject) Py_BuildValue(const char format, …);

參數解釋:
format及轉換格式,類似與C語言中%d,%f,後面的不定參數對應前面的格式,具體格式如下:

“s”(string) [char *] :將C字符串轉換成Python對象,如果C字符串爲空,返回NONE。

“s#”(string) [char *, int] :將C字符串和它的長度轉換成Python對象,如果C字符串爲空指針,長度忽略,返回NONE。

“z”(string or None) [char *] :作用同”s”。

“z#” (stringor None) [char *, int] :作用同”s#”。

“i”(integer) [int] :將一個C類型的int轉換成Python int對象。

“b”(integer) [char] :作用同”i”。

“h”(integer) [short int] :作用同”i”。

“l”(integer) [long int] :將C類型的long轉換成Pyhon中的int對象。

“c”(string of length 1) [char] :將C類型的char轉換成長度爲1的Python字符串對象。

“d”(float) [double] :將C類型的double轉換成python中的浮點型對象。

“f”(float) [float] :作用同”d”。

“O&”(object) [converter, anything] :將任何數據類型通過轉換函數轉換成Python對象,這些數據作爲轉換函數的參數被調用並且返回一個新的Python對象,如果發生錯誤返回NULL。

“(items)”(tuple) [matching-items] :將一系列的C值轉換成Python元組。

“[items]”(list) [matching-items] :將一系列的C值轉換成Python列表。

“{items}”(dictionary) [matching-items] :將一系類的C值轉換成Python的字典,每一對連續的C值將轉換成一個鍵值對。

例:
後面爲PyObject的返回值

Py_BuildValue("")None

Py_BuildValue("i",123) 123

Py_BuildValue("iii",123, 456, 789) (123, 456, 789)

Py_BuildValue("s","hello") 'hello'

Py_BuildValue("ss","hello", "world") ('hello', 'world')

Py_BuildValue("s#","hello", 4) 'hell'

Py_BuildValue("()")()

Py_BuildValue("(i)",123) (123,)

Py_BuildValue("(ii)",123, 456) (123, 456)

Py_BuildValue("(i,i)",123, 456) (123, 456)

Py_BuildValue("[i,i]",123, 456) [123, 456] Py_BuildValue("{s:i,s:i}", "abc",123, "def", 456) {'abc': 123, 'def': 456}

Py_BuildValue("((ii)(ii))(ii)", 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PyArg_ParseTuple函數
作用:此函數其實相當於sscanf(str,format,…),是Py_BuildValue的逆過程,這個函數將PyObject參數轉換成C/C++數據類型,傳遞的是指針,但這個函數與Py_BuildValue有點不同,這個函數只能解析Tuple元組,而Py_BuildValue函數可以生成元組,列表,字典等。

原型:PyAPI_FUNC(int) PyArg_ParseTuple(PyObject args, const char format,…)

Args:一般爲Python程序返回的元組。

Foramt:與Py_BulidValue類型,就不在累述咯。

元組操作函數:
因爲程序之間傳遞的參數,大多數爲Tuple類型,所以有專門的函數來操作元組:

PyAPI_FUNC(PyObject *)PyTuple_New(Py_ssize_t size);
解釋:新建一個參數列表(調試了下,發現其實是用鏈表實現的),size列表爲長度的寬度

PyAPI_FUNC(Py_ssize_t)PyTuple_Size(PyObject *);
解釋:獲取該列表的大小

PyAPI_FUNC(PyObject )PyTuple_GetItem(PyObject , Py_ssize_t);
解釋:獲取該列表某位置的值

PyAPI_FUNC(int) PyTuple_SetItem(PyObject ,Py_ssize_t, PyObject );
解釋:設置該列表此位置的值。如PyTuple_SetItem(pyParams,1,Py_BuildValue(“i”,2));設置第2個位置的值爲2的整數。
備註:對應的列表和字典也有對應的操作

更多的接口調用以及數據類型轉化,參照Python文檔
這裏寫圖片描述

Python 調用C

Python調用C有兩種方式

使用ctypes模塊,Python文檔有詳細示例
這裏寫圖片描述

使用C爲Python編寫拓展模塊
Python之所以如此強大,正是由於可以使用C\C++爲其編寫拓展模塊,手動編寫拓展模塊的方式稍微有些繁瑣,可借用SWIG自動實現,簡潔快速。更多詳細的SWIG用法,見其官方文檔
官網下載 windows包並解壓

使用vs創建空項目,並配置vs。右鍵當前項目,選擇屬性
這裏寫圖片描述
現在使用C爲Python創建一個叫user的拓展模塊,該模塊包含一個showHello函數:
分別創建三個文件
user.i
user.c
user_wrap.c

在user.i中添加如下代碼

%module user
%inline %{
extern void showHello();
%}
1
2
3
4
user.c中添加

#include <stdio.h>

void showHello()
{
printf("hello Python!\n");

}
1
2
3
4
5
6
7
8
右鍵user.i 文件並選擇屬性
這裏寫圖片描述
點擊應用後如下圖,完成配置
這裏寫圖片描述
右鍵當前項目,選擇屬性,完成如下配置,確定
這裏寫圖片描述
最後生成即可(選擇工具欄 生成 –> 批生成)

創建測試代碼調用C驗證

import user
user.show()
1
2
在Linux下則無需如此麻煩的配置,可直接使用命令

On Unix the compilation of examples is done using the file Example/Makefile. This makefile performs a manual module compilation which is platform specific. Typically, the steps look like this (Linux):

% swig -python interface.i
% gcc -fpic -c interface_wrap.c -I/usr/local/include/python1.5
% gcc -shared interface_wrap.o $(OBJS) -o interfacemodule.so
% python
Python 1.5.2 (#3, Oct 9 1999, 22:09:34) [GCC 2.95.1 19990816 (release)] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam

>> import interface
>> interface.blah(...)
1
2
3
4
5
6
7
8
9
10
11
此處.i文件爲SWIG的接口文件,其中%module後面定義模塊名,用%inline定義方法列表

%inline %{
包含導出的函數
%}
1
2
3
有了Python與C的交互基礎,則還需要Android中的NDK開發基礎,關於Android平臺的jni調用,本文不在此處詳解,可看看我的JNI方面博客,而此處我們需要使用Crystax NDK開發工具鏈,非官方NDK工具鏈,需自行下載。下一篇正式涉及Python for Android。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章