Unity3D特效百例 | 案例项目实战源码 | Android-Unity实战问题汇总 |
---|---|---|
游戏脚本-辅助自动化 | Android控件全解手册 | 再战Android系列 |
Scratch编程案例 | 软考全系列 | Unity3D学习专栏 |
蓝桥系列 | ChatGPT和AIGC |
👉关于作者
专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎底部卡片私我,交流让学习不再孤单。
👉实践过程
😜前言
出于业余兴趣,我几年前一直想研究关于Python移植到Android上的实现方案,就像JNI开发一样真正的在Andorid中进行混合Python编程。但出于上班忙时间少,加上人生大事精力有限,人的惰性等等原因,一直没有深入探索,直到2024年有项目需要集成Python,就准备查询下资料,发现已经有人做了我一直想做的事,而且已经做得比较完善了,更可喜的是思路大体一致。
真是应了那句话:你能想到的东西,世界上一定已经存在了,只不过你还没见到而已。
支持AGP版本和Python运行版本
😜配置环境
首先你需要确保已经安装好了Python环境,并且环境变量也配置好了,Python 的安装包可以一键帮我们都处理好,只需要你仔细看清安装弹框中的说明做好勾选即可。
gradle的版本不同,build.gradle中的写法不同,读者根据自己的环境做好对应。
找到工程目录的 build.gradle
buildscript { //写法一repositories {google()maven { url 'https://jitpack.io' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }maven { url 'https://maven.aliyun.com/repository/google' }maven { url 'https://maven.aliyun.com/repository/public' }maven { url 'https://maven.aliyun.com/repository/jcenter' }maven { url "https://chaquo.com/maven" } //python插件的地址 mavenCentral()}dependencies {classpath("com.android.tools.build:gradle:7.0.2")classpath "com.chaquo.python:gradle:15.0.1" //python插件的版本,尽量用新的,支持的内容多 也修复了很多问题
// classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"}
}
task clean(type: Delete) {delete rootProject.buildDir
}
plugins { //写法二id 'com.android.application' version '8.0.2' apply falseid 'org.jetbrains.kotlin.android' version '1.6.21' apply falseid 'com.chaquo.python' version '15.0.1' apply false //这个是重点
}task clean(type: Delete) {delete rootProject.buildDir
}
具体项目的 build.gradle
plugins {id 'com.android.application'id 'com.chaquo.python' //集成python必备
}android {compileSdk 32defaultConfig {applicationId "com.example.myapplication"minSdk 26targetSdk 32versionCode 1versionName "1.0"ndk {abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"}python {buildPython "D:\\Program Files\\Python\\python.exe" //不同的人电脑环境不同pip {install "matplotlib" //如果想要安装三方包
// // 需求说明符,带或不带版本号:
// install "scipy"
// install "requests==2.24.0"
// // 相对于项目目录的sdist或wheel文件名:
// install "MyPackage-1.2.3-py2.py3-none-any.whl"
// // 包含setup.py的目录,相对于项目
// // 目录(必须包含至少一个斜杠):
// install "./MyPackage"
// // "-r" '后面跟着一个需求文件名,相对于
// // 项目目录:
// install "-r", "requirements.txt"}}testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}dependencies {implementation 'androidx.appcompat:appcompat:1.3.0'implementation 'com.google.android.material:material:1.4.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
工程下的 setting.gradle
//开发andorid的都知道 gradle 配置的写法 变动的很大 下方的设置看需要和你的当下项目是否类似再写。
pluginManagement {repositories {google()maven { url 'https://jitpack.io' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }maven { url "https://chaquo.com/maven" } //确保这个地址mavenCentral()}plugins {id 'com.android.application' version '7.1.0-alpha11'id 'com.android.library' version '7.1.0-alpha11'id 'org.jetbrains.kotlin.android' version '1.6.21'}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()maven { url 'https://jitpack.io' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }maven { url "https://chaquo.com/maven" } //确保这个地址mavenCentral()}
}
rootProject.name = "My Application"
include ':app'
😜代码实战
代码要写在具体项目的 main 目录的 python 文件夹。
Python中代码
from java import jclass
def sayHello():print("这是一个测试")def greet(name):print("--- hello,%s ---" % name)def add(a,b):return a + bdef sub(count,a=0,b=0,c=0):return count - a - b -cdef get_list(a,b,c,d):return [a,b,c,d]def print_list(data):print(type(data))# 遍历Java的ArrayList对象for i in range(data.size()):print(data.get(i))# python调用Java类
def get_java_bean():JavaBean = jclass("org.hello.JavaBean") # 实体类的类名jb = JavaBean("python")jb.setData("json")jb.setData("xml")jb.setData("xhtml")return jb
Java中代码
核心思想便是对象转换,PyObject类是桥梁,fromJava函数将一个Java对象转换为相应的Python对象,toJava函数正好相反,将Python中的对象转换成Java中的对象
public class TestPython extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_python);if (!Python.isStarted()) {Python.start(new AndroidPlatform(this)); //可以放到app里进行初始化}Python python = Python.getInstance();PyObject pyObject = python.getModule("hello"); //调用的文件名pyObject.callAttr("sayHello"); //调用的方法名Python py = Python.getInstance();// 调用hello.py模块中的greet函数,并传一个参数// 等价用法:py.getModule("hello").get("greet").call("Android");py.getModule("hello").callAttr("greet", "Android");// 调用python内建函数help(),输出了帮助信息py.getBuiltins().get("help").call();PyObject obj1 = py.getModule("hello").callAttr("add", 2,3);// 将Python返回值换为Java中的Integer类型Integer sum = obj1.toJava(Integer.class);Log.d("TAG","add = "+sum.toString());// 调用python函数,命名式传参,等同 sub(10,b=1,c=3)PyObject obj2 = py.getModule("hello").callAttr("sub", 10,new Kwarg("b", 1), new Kwarg("c", 3));Integer result = obj2.toJava(Integer.class);Log.d("TAG","sub = "+result.toString());// 调用Python函数,将返回的Python中的list转为Java的list 也可以使用mapPyObject obj3 = py.getModule("hello").callAttr("get_list", 10,"xx",5.6,'c');List<PyObject> pyList = obj3.asList();Log.d("TAG","get_list = "+pyList.toString());// 将Java的ArrayList对象传入Python中使用List<PyObject> params = new ArrayList<PyObject>();params.add(PyObject.fromJava("alex"));params.add(PyObject.fromJava("bruce"));py.getModule("hello").callAttr("print_list", params);// Python中调用Java类PyObject obj4 = py.getModule("hello").callAttr("get_java_bean");JavaBean data = obj4.toJava(JavaBean.class);data.print();Python pyTe = Python.getInstance(); //matplotlib库的方法PyObject module = pyTe.getModule("plot"); //文件名byte[] dddz = module.callAttr("plot", "1 2 3 4", "1 2 3 4").toJava(byte[].class);ImageView img = findViewById(R.id.testImg);img.setImageBitmap(BitmapFactory.decodeByteArray(dddz, 0, dddz.length));}
}
- Python.getInstance():获取Python运行环境,映射为Java中的Python;
- py.getModule:执行指定的Python文件,映射为Java中的PyObject;
- module.callAttr:执行Python文件中指定的方法,并传递参数,返回PyObject对象;
kotlin代码自行转换即可。
😜原理
简单说就是以android的JNI技术为桥梁,JNI技术解决了Java与C/C++混合编程的问题,而Python官方解释器则是纯C语言实现的,名为CPython解释器,在Android上,Python解释器就是一个so动态库。JNI接口使得C语言能反射Java的类与方法,而Python运行在C语言之上,那么Python也就具备了调用Java的能力。整个过程就是Java调用C语言代码,C再调用CPython解释器从而执行Python代码;Python调用CPython解释器,CPython调用C语言代码,C语言代码再反射Java代码,完成一次反调。这之间,粘合Java与CPython解释器的一段C语言代码,也就是Chaquopy框架干的事。
😜可能得问题
- 当你刚配置好环境第一次运行了 python 的 hello word 后,就开始蠢蠢欲动准备搞一搞集成三方库。可以当你在 build.gradle 中配置好 pip 一运行,发现
有可能
出现下面的问题:
Process ‘command ‘C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe’’ finished with non-zero exit value 1
静下心来好好想想,区别就在 pip 安装三方库上,Android 上既然是 Chaquopy 远程安装那么就得往该插件上考虑。在整个过程中我们的配置只有两个地方是可变动的,
一个是 python 的安装路径,一个是 Chaquopy 的版本号。本地安装路径如果和配置不一致,不是这个错误。那么只有一个问题,就是 Chaquopy 的版本。
我们切换最新的试试,发现解决了。 - Chaquopy 不是支持所有的三方库,这是最难搞的事情。具体可从这查看。点击跳转
- 还可以实现 Python 自动生成 Java的类,但是该功能用处不大,想要了解的可自行搜索静态代理。
Chaquopy是线程安全的。但是,因为它基于CPython(Python参考实现),所以它受到CPython的全局解释器锁(GIL)的限制。这意味着尽管Python代码可以在任意数量的线程上运行,但在任何给定时刻只会执行其中一个线程。
如果Python对象引用直接或间接引用原始Python对象的Java对象,则可以创建跨语言引用循环。任何一种语言的垃圾收集器都无法检测到这样的循环。所以避免内存泄漏很重要。Python代码就需要写的很合理。
👉其他
📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。
温馨提示:点击下方卡片获取更多意想不到的资源。