赞
踩
Python在开发者社区中时最受欢迎的语言之一, 因为其简单,健壮并且有着庞大的软件生态使其可以在多个领域发挥作用. 类似NumPy和SciPy这样的包允许你在项目中使用高等数学计算, 而这样的计算在其它的语言中是无法简单实现的. 那么如果将Python引入到Android应用中又会带来什么样效果呢?
Chaquopy是一个可以帮助开发者通过Java/Kotlin在Android平台中运行Python脚本的框架. 和其它跨语言库不同, 它不再有NDK依赖的烦恼, 也不需要native code[1], 并且安装也十分的简单. 在这篇文件中, 我们将探索Chaquopy, 并通过Kotlin来进行构建和使用.
和大多数跨语言接口工作原理一样, Python和Android都有着C/C++的血统, 使其可以通过中间件来进行通信. Android的NDK允许开发者使用通过C/C++编写的本地库, 来帮助Android应用获得更好的图形和科学计算效果.
Chaquopy使用CPython, 一个通过C来实现的Python实现. 不同于一般的误解, Python并不是一个纯粹的解释性语言. Python的源码最开始会被构建为可以被CPython执行的特殊字节码. 当然, CPython只是Python的几种解释器之一, 其它的还有PyPy, IronPython, Jython等等.
Chaquopy通过Android NDK工具链来构建CPython, CPython在项目构建的时候通过Chaquopy Gradle插件从Maven仓库中心进行下载, 在这个过程中用户并不需要下载NDK. 它还下载Chaquopy运行支持通过JNI将Java/Kotlin和Python连接起来.
同时, 我们还需要Python包管理工具pip
, 它可以下载为解释器下载包. 像NumPy
和SciPy
这样的受欢迎的包可以通过原生代码执行高密集的CPU计算, 我们需要事先安装这些包. 因此, Chaquopy团队维护了自己的存储库,其中包含专门为Android的ARM架构构建的本地软件包. 这些软件包的维护者不会为Android平台构建他们的本地代码,因为用户数量较少,因此Chaquopy团队会针对Android平台构建它们并通过自己的存储库进行发布.
对于纯粹的Python包, 不需要额外的构建并且Chaquopy可以直接运行这些.从更宏观来看, Chaquopy包含了三个主要的组件.
在新/现有的Android项目中添加Chaquopy, project级的build.gradle
文件的顶部, 我们定义项目的plugin并且添加Chaquopy的Gradle插件.
- plugins {
- id 'com.android.application' version '7.4.2' apply false
- id 'com.android.library' version '7.4.2' apply false
- id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
- id 'com.chaquo.python' version '13.0.0' apply false
- }
下一步, 我们在module级的build.gradle
文件中添加Chaquopy plugin和指定ABI规则,
- plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
- id 'com.chaquo.python'
- }
-
- android {
- ...
- defaultConfig {
- ...
- ndk {
- abiFilters "armeabi-v7a" //, "arm64-v8a", "x86", "x86_64"
- }
- }
- ...
- }
正如官方文档提及的, Python解释器是使用Android NDK来构建的本机组件,NDK为指定的版本构建原生代码, 比如like arm,x86或x86_64. 不同的设备支持不同的架构. 所以我们只能包含特定版本的Python解释器, 而不是为所有架构都进行构建, 因为这会增加应用程序的大小. Android官方文档中是这么说的,
构建系统的默认行为是将每个ABI的二进制文件包括在单个APK也称为胖 APK)内. 与仅包含单个ABI的二进制文件的APK相比,胖APK要大得多, 要权衡的是兼容性更广,但APK更大. 强烈建议您利用app bundle和APK拆分减小 APK的大小,同时仍保持最大限度的设备兼容性.
下一步, 我们将配置Python构建版本, 我们可以通过修改module级build.gradle
文件来指定版本.
- plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
- id 'com.chaquo.python'
- }
-
- android {
- ...
- defaultConfig {
- ...
- ndk {
- abiFilters "armeabi-v7a" //, "arm64-v8a", "x86", "x86_64"
- }
- python {
- version "3.10"
- }
- }
- ...
- }
不同的Chaquopy支持不同的Python版本有着不同的最小API需要. 通过这个表可以查找到你需要匹配的版本. 下一步, 我们指定在Python解释器需要的包的版本.
- defaultConfig {
- python {
- pip {
- // A requirement specifier, with or without a version number:
- install "scipy"
- install "requests==2.24.0"
-
- // An sdist or wheel filename, relative to the project directory:
- install "MyPackage-1.2.3-py2.py3-none-any.whl"
-
- // A directory containing a setup.py, relative to the project
- // directory (must contain at least one slash):
- install "./MyPackage"
-
- // "-r"` followed by a requirements filename, relative to the
- // project directory:
- install "-r", "requirements.txt"
- }
- }
- }
这是在Chaquopy中安装包的几种不同方法. 它可以是具有特定版本的包名, 也可以是自定义包或者requirement.txt
包列表.
在Python中,我们使用属于Python模块的函数或者数据成员, 一个Python模块包含.py文件. 要使用任何Python模块中的成员. 第一步是将Python源代码放入<project>/app/src/main/python
文件夹中.
- # Contents of my_module.py
-
- import numpy as np
-
- def get_exec_details():
- return __file__
-
- def sumOp( nums ):
- return sum( nums )
-
- def powOp( a , x ):
- return a**x
-
- def npMatrixSum( m , n ):
- mat = np.ones( ( m , n ) )
- mat_sum = np.sum( mat , axis=1 )
- return mat_sum
-
- class Operations:
-
- num_ops = 2
-
- def meanOp( self , nums ):
- return sum( nums ) / len( nums )
-
- def maxOp( self , nums ):
- return max( nums )
-
- nums_len = 10
- nums_len_str = "ten"
- ops = Operations()
为了使用my_module
中的成员, 我们需要使用Python.getModule
方法传递模块的名称. 在这之前, 我们需要运行Python
在应用中被允许, 这可以在Application的onCreate方法中执行,
- class App : Application() {
-
- override fun onCreate() {
- super.onCreate()
- if( !Python.isStarted() ) {
- Python.start( AndroidPlatform( this ) )
- }
- }
-
- }
将App
添加到 AndroidManifest.xml
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools">
-
- <application
- android:name=".App"
- ...
- </application>
-
- </manifest>
那么在MainActivity
中, 我们就可以使用Python.getInstance
(否则的话会出现PyException
异常),
- val py = Python.getInstance()
- val module = py.getModule( "my_module" )
为了使用数据成员, 像my_module.py
中的nums_len
,
- val numsLength = module[ "nums_len" ]?.toInt()
- println( "Nums Length is $numsLength" )
Nums Length is 10
访问对象ops
类中的属性
- val ops = module[ "ops" ]!!
- println( "Operations: $ops" )
- println( "num_ops : ${ ops[ "num_ops" ] }" )
- println( "mean func : ${ ops[ "meanOp" ] }" )
- Operations: <my_module.Operations object at 0xb9339ce8>
- num_ops : 2
- mean func : <bound method Operations.mean of <my_module.Operations object at 0xb9339ce8>>
由于Python中的函数是对象, 因此允许将函数作为模块的值进行访问.然后,我们使用PyObject.call
方法来向函数传递参数并获取结果(如果函数返回一个值).
- val sumFunc = module[ "sumOp" ]
- val sum = sumFunc?.call( intArrayOf( 12 , 25 , 32 ) )
- val powFun = module[ "powOp" ]
- val pow = powFun?.call( 5 , 2 )
- println( "Sum: $sum" )
- println( "Pow: $pow" )
- Sum: 69
- Pow: 25
要从ops
对象访问成员函数,
- val meanFunc = ops[ "meanOp" ]
- val mean = meanFunc?.call( intArrayOf( 23 , 45 , 12 , 91 ) )
- println( "Mean: $mean" )
-
- // OR
-
- val mean = ops.callAttr( "meanOp" , intArrayOf( 23 , 45 , 12 , 91 ) )
- println( "Mean: $mean" )
Mean: 42.75
这是一个示例, 其中Python函数使用numpy
并返回类型为np.ndarray
的结果
- # my_module.py
- import numpy as np
-
- def npMatrixSum( m , n ):
- mat = np.ones( ( m , n ) )
- mat_sum = np.sum( mat , axis=1 )
- return mat_sum
-
- val npSumFunc = module[ "npMatrixSum" ]
- val output = npSumFunc?.call( 2 , 3 )
-
- // OR
-
- val output = module.callAttr( "npMatrixSum" , 2 , 3 )
-
- println( "Output: $output" )
- println( "Output shape: ${output!![ "shape" ] }")
- Output: [3. 3.]
- Output shape: (2,)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。