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