Android 平臺的Python——基礎篇(一)
Android 平臺的Python——JNI方案(二)
Android 平臺的Python——CLE方案實現(三)
Android 平臺的Python——第三方庫移植
Android 平臺的Python——編譯Python解釋器html
以前一段時間一直比較忙,致使不少想研究想寫的博客沒寫,如今有時間正好補充一篇。前面寫了三篇關於將Python3嵌入Android項目的博客,後來一直有人留言問怎麼移植Python的第三方庫,包括說調用標準庫報錯等等問題,我以前一直覺得這些是Python的基本常識,沒想到不少人都不知道。以前的三篇博客,大概是寫給會一點Python的Android工程師看的,並非徹底定位寫給小白看,這樣一來我會默認看客已經知道哪些知識,不少細節沒有詳細解釋,另外一方面,我寫博客的初衷是對本身知識的一個整理,等因而寫給本身看的筆記。可是從另外一個角度講,最好的學習方法是把本身學會的知識馬上教授他人,這就是所謂的學習金字塔原理,有興趣的人能夠去了解學習金字塔。那麼之後寫博客,我會盡量關注到一些細節,提高博客的水準,這也是對本身的一個昇華提升。node
Python 的模塊加載路徑
在講庫移植以前,先提下Python的基礎知識,當咱們本身編寫了一個好用的庫時,咱們但願每次建立Python工程時均可以引入這個庫,那麼最簡單的辦法就是把它拷貝到Python標準庫的路徑下,可是不建議這樣作,弄髒了標準庫的目錄,更好的作法是放到Python第三方庫路徑下,做爲一個第三方庫引入工程。這個路徑是
...\python\Lib\site-packages
路徑中的python就是你安裝的Python的根目錄。說到這裏,其實就引出了另外一個話題,Python的模塊加載路徑,咱們能夠在解釋器中輸出這個路徑python
import sys
print(sys.path)
1
2
以上代碼輸出的是一個列表,列表裏面就是Python解釋器去搜索模塊的路徑,一個一個去找,最後仍是找不到,就會報錯,說沒有這個模塊。知道了這一點,咱們只須要修改這個列表,把咱們本身寫的模塊的路徑加到這個搜索列表,這樣就能夠成功引入咱們本身編寫的模塊了。這種方法解決了一個問題,就是我想引入某個模塊,這個模塊或者是咱們本身寫的或者是從網上下載的某個庫源碼,但我不想把這些源碼拷貝到Python解釋器的庫路徑下面,而是存放在某個目錄,更方便管理,好比是個git庫目錄,這個時候咱們就能夠在腳本的開始加上如下代碼,動態將源碼路徑添加到Python模塊搜索路徑列表中android
import sys
sys.path.append("my code path")
1
2
CLE框架下導入Python第三方庫
前面的博客已經講過了怎麼使用CLE框架,在Android項目中嵌入Python解釋器,可是有一些細節,不少人還不清楚git
Android 工程的ibs目錄中放的libpython3.4m.so是什麼?
要回答這個問題,首先要了解Python解釋器的源碼結構。Python解釋器源碼能夠從官網下載,也能夠從Github上下載 Python解釋器源碼編程
劃去一些無關的內容,具體說一下結構
Include:包含Python提供的全部頭文件,若是須要本身使用C/C++編寫自定義模塊擴展Python,就須要用到這寫頭文件
Lib: 由Python語言編寫的全部標準庫
Modules:包含了標準庫中全部使用C語言編寫的模塊
Parser:對Python代碼進行詞法分析和語法分析的部分
Objects:全部Python的內建對象
Python:Python運行的核心。解釋器中的Compiler和執行引擎部分
若是對源碼學習感興趣,推薦一本書《Python源碼剖析》,看過以後會受益不淺,特別是對想本身改寫Python解釋器的人
那麼回到咱們的話題,libpython3.4m.so實際上只是Parser、Objects、Python以及一小部分Modules編譯出來的動態庫,只是提供了Python解釋器的核心功能。若有時間,在之後的博客,我會詳細講解,如何手動用NDK,使用Android.mk文件以及Makefile文件,分別在Windows系統和Ubuntu系統上交叉編譯出完整的在Android上運行的Python so庫。網絡
爲何使用CLE框架集成Python解釋器後,有些標準庫報找不到錯誤?
看博客不細心的人,可能沒有注意到一張圖,這裏面放的so是什麼?app
咱們打開下載的CLE文件starcore_for_android.2.6.0,進入裏面的python.files目錄下面,一路下去找到一個叫lib-dynload的目錄框架
能夠看到,裏面放了一堆so庫,這個就是上文講的Modules裏面編譯出來的Python標準庫,這些Python的標準庫都是用C語言寫的,CLE框架中,將Modules裏面的標準庫模塊都編譯成了一個個小的so,這樣作的好處就是能夠按需集成,咱們知道Android的Apk文件都是要儘量小的,你沒有用到的模塊,能夠不用集成到apk中,不然所有打包到libpython.so中,無故增長了apk大小。
好了,到此鋪墊都講完了。如今具體談一下裝載Python庫的思路ide
將須要的Python庫打包到工程的assets目錄下,在適當的時候,將assets目錄下的文件都拷貝到手機存儲中,這個存儲能夠是sdcard,也能夠是內部的/data/data/package-name下
在須要引入這些庫的時候,使用咱們一開始講的方法,將路徑添加到Python的模塊搜索列表——sys.path列表,讓解釋器能搜索到它們。
下面咱們以一個實例來具體說明,此次咱們須要移植的是爬蟲須要用到的兩個庫,requests和BeautifulSoup,有了這兩個庫,咱們瞬間就能將廢舊的Android手機制做成Python爬蟲機,老機器煥發新生命,怎麼樣激不激動?
1.打包庫
若是咱們本地Python環境中已經安裝了這些庫,能夠直接去Python的庫目錄打包,由於這些庫基本是純Python代碼,是跨平臺的,固然,別把Python2.x和Python3.x搞混。若是沒有安裝,去相關的官網下載它們的源碼打包。
不少人可能不知道,Python自己就是支持導入zip格式的包的,不信的同窗能夠本身在本地實驗一下,將本身寫的庫壓縮爲zip,而後依然能夠愉快的import。以上包中,python3.5.zip是純Python代碼的標準庫,在CLE裏面已經提供了,惟一須要說明的地方,是certifi庫爲何沒有打包,而是以目錄形式提供呢?這裏也正是我採坑的地方,以前試驗一直報錯沒有成功移植requests庫,就是由於打包了certifi,後來反覆測試定位到該包,發現裏面有一個cacert.pem文件,不是.py文件,在壓縮包中沒法被讀取,所以只能以文件夾形式提供了。
另外還有一個小點說一下,相信絕大部分人不會犯錯,但總有粗枝大葉的。打包的時候,要打包這個庫的源碼父目錄,就像certifi同樣,是打包這個certifi目錄,而不是進到certifi裏面,選中全部文件壓縮。以前一個同事就是犯這種錯誤,一直和我說不成功。
2.遞歸拷貝
在工程的assets目錄建立python文件夾,將全部包複製進該目錄,在app啓動的適當時候,調用如下代碼拷貝assets中的全部文件到手機存儲
// Extract python files from assets
AssetExtractor assetExtractor = new AssetExtractor(this);
assetExtractor.removeAssets("python");
assetExtractor.copyAssets("python");
// Get the extracted assets directory
String pyPath = assetExtractor.getAssetsDataDir() + "python";
1
2
3
4
5
6
7
AssetExtractor類在Android 平臺的Python——JNI方案(二)一文已經提過了,這裏再次給出開源庫中的
連接 這裏只有一點須要特別說明,在剛開始的時候我準備剪裁lib-dynload文件夾提供的C語言部分的Python標準庫,結果試驗性的放了幾個so,一直報各類找不到錯誤,最後不想浪費時間試錯,直接將lib-dynload中的全部so拷貝到了assets/python文件夾,有時間的朋友能夠精心剪裁出真正須要的so,減少apk體積。
3.添加庫到搜索路徑中
還沒看過CLE使用的那篇博客,請先瀏覽CLE的使用一文Android 平臺的Python——CLE方案實現(三)
另外須要注意的是在Android清單文件中,網絡權限別忘了
<uses-permission android:name="android.permission.INTERNET" />
protected void init() {
final String appLib = getApplicationInfo().nativeLibraryDir;
AsyncTask.execute(new Runnable() {
@Override
public void run() {
loadPy(appLib);
}
});
}
void loadPy(String appLib){
// Extract python files from assets
AssetExtractor assetExtractor = new AssetExtractor(this);
assetExtractor.removeAssets("python");
assetExtractor.copyAssets("python");
// Get the extracted assets directory
String pyPath = assetExtractor.getAssetsDataDir() + "python";
try {
// 加載Python解釋器
System.load(appLib + File.separator + "libpython3.5m.so");
} catch (Exception e) {
e.printStackTrace();
}
/*----init starcore----*/
StarCoreFactoryPath.StarCoreCoreLibraryPath = appLib;
StarCoreFactoryPath.StarCoreShareLibraryPath = appLib;
StarCoreFactoryPath.StarCoreOperationPath = pyPath;
StarCoreFactory starcore = StarCoreFactory.GetFactory();
//用戶名、密碼 test , 123
StarServiceClass service = starcore._InitSimple("test", "123", 0, 0);
mSrvGroup = (StarSrvGroupClass) service._Get("_ServiceGroup");
service._CheckPassword(false);
/*----run python code----*/
mSrvGroup._InitRaw("python35", service);
StarObjectClass python = service._ImportRawContext("python", "", false, "");
/* 設置Python模塊加載路徑 即sys.path.insert() */
python._Call("import", "sys");
StarObjectClass pythonSys = python._GetObject("sys");
StarObjectClass pythonPath = (StarObjectClass) pythonSys._Get("path");
pythonPath._Call("insert", 0, pyPath+ File.separator +"python3.5.zip");
pythonPath._Call("insert", 0, pyPath+ File.separator +"requests.zip");
pythonPath._Call("insert", 0, pyPath+ File.separator +"idna.zip");
pythonPath._Call("insert", 0, pyPath+ File.separator +"certifi");
pythonPath._Call("insert", 0, pyPath+ File.separator +"chardet.zip");
pythonPath._Call("insert", 0, pyPath+ File.separator +"urllib3.zip");
pythonPath._Call("insert", 0, pyPath+ File.separator +"bs4.zip");
pythonPath._Call("insert", 0, appLib);
pythonPath._Call("insert", 0, pyPath);
python._Set("JavaClass", Log.class);
service._DoFile("python", pyPath + "/test.py", "");
Log.d("callpython", "python end");
}
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
51
52
53
54
55
56
57
58
59
test.py文件
import imp #test load path
import requests
from bs4 import BeautifulSoup
def log(content):
JavaClass.d("formPython",content)
def testGet():
log('Hello,World from python')
r = requests.get("https://www.baidu.com/")
r.encoding ='utf-8'
bsObj = BeautifulSoup(r.text,"html.parser")
for node in bsObj.findAll("a"):
log("---**--- "+node.text)
testGet()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
日誌:
在Crytax-NDK的Python中集成第三方庫
有了CLE,我爲何仍然執着於Crytax-NDK中的Python解釋器了?說實話,我並非特別喜歡CLE,由於它封裝了太多細節,且源碼並未開源,具體實現代碼不知,性能就沒法掌控,特別是無用代碼,導入一些非必要的so和jar,增長了apk體積,由於這個框架並非專門針對python的,還能夠集成其餘的不少腳本語言到Android中,爲了通用性,每每就須要不少對咱們來講無用的代碼,性能也會有犧牲。可是它的優勢也很明確,那就是使用簡單,不須要你會Ndk開發,技術成本低。
使用Crytax-NDK實現,具體思路和上面講的是同樣的,直接參看Android 平臺的Python——JNI方案(二)一文,而後將須要的第三方庫源碼打包安裝到手機存儲,須要注意的地方就是在調用的Python腳本的開始處,加上如下代碼,也可使用其餘更優雅的方式,完成這個搜索路徑添加,這裏只是一個簡單的demo代碼演示。
import sys
sys.path.append("你拷貝到手機上的路徑/assets/python/urllib3.zip")
sys.path.append("你拷貝到手機上的路徑/assets/python/chardet.zip")
sys.path.append("你拷貝到手機上的路徑/assets/python/certifi")
sys.path.append("你拷貝到手機上的路徑/assets/python/idna.zip")
1
2
3
4
5
可是,可是……
HTTPSConnectionPool(host='www.baidu.com', port=443): Max retries exceeded with url: / (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.",))
1
這裏有一個極其操蛋的問題,使用requests訪問https的地址時,會報錯,只能訪問http地址。由於Crytax-NDK庫的Python解釋器編譯得有問題,沒有支持openssl,真不知道Crytax-NDK的做者怎麼想的,因爲Crytax-NDK是開源的,我好不容易找到了其源碼,查看了他們編譯Python解釋器的腳本,真讓人無語,不能訪問https的Python有什麼用?
能夠看到,在編譯ssl模塊時,加了一個OPENSSL_HOME屬性控制,即有ssl源碼時,就編譯這部分,不然跳過,然而Crytax-NDK裏面openssl的目錄是空的,因此最後生成的Python一系列so中,惟獨沒有ssl的so。嘗試從其餘地方拷貝一個ssl的so是不可行的,由於他們的Python解釋器裏,ssl的屬性是沒有enable的,你拷貝瞭解釋器也並不會去連接,然並卵,看來只能手動從新編譯這個Python解釋器了,可是手上沒有搭建環境,光環境搭建就得折騰一番,下次博客在寫吧,下次的博客我主要討論一下,本身手工編譯解釋器,而後運用cython模塊編譯pyjnius庫,實現純手動在Android搭建一個python.so+ pyjnius.so的環境,實現簡便的Java與Python的互操做,有了它,CLE基本能夠扔掉了。若是不知道pyjnius,請谷歌。
最後,若是您以爲個人博客對您有用,看過以後,麻煩點個贊,畢竟頂一下又不會懷孕,由於不少人看過以後,也沒有一點表示,無論怎麼說,寫博客既花時間,也耗費一點精力,畢竟也是在分享知識啊,在這個知識付費的時代,免費分享也不易,點個贊,只是鼠標一抖的事而已,謝謝!
關注我的公衆號:編程之路從0到1 ———————————————— 版權聲明:本文爲CSDN博主「血色v殘陽」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/yingshukun/article/details/82785257