前言

早在2017年的时候,出于业余兴趣,我就开始研究关于Python移植到Android上的实现方案,我一直希望能实现Android与Python的混合编程,并为此写了一系列博客,我希望借助JNI技术,实现Java与Python的交互。或许是出于上班忙,时间少,精力有限,人的惰性等等原因,一直没有实现一套框架,降低Android与Python混编的难度,做到尽可能封装C语言代码,让使用者无需掌握NDK开发,C语言编程等。原理是早已走通了,剩下的就是苦力活,写C代码,写JNI代码,对接口一一封装。

现在终于不用遗憾了,因为已经有人做了我一直想做的事,而且是以我想要的思路。我一直关注着Android与Python混合编程的信息,当我看到Chaquopy框架时,真的难掩的开心,比我自己实现的还要开心!

如果有人想探寻Android与Python的混编的原理与实现,那我之前的写的博客还能派上一点用场

Android 平台的Python——基础篇(一)

Android 平台的Python——JNI方案(二)

Android 平台的Python——CLE方案实现(三)

Android 平台的Python——第三方库移植

Android 平台的Python——编译Python解释器

Chaquopy是什么?

简单的直观的解释,它是在Android Studio中基于Gradle的构建系统实现的一个插件。它可以帮助我们用最简便的方式实现Android技术与Python混合编程。甚至对于Python的忠实拥趸来说,可以完全使用Python语言开发一个apk,基本不用写Java代码。

实际上Chaquopy并不仅仅是一个插件那么简单,它是一套框架。gradle插件这部分只是用来打包apk的而已

基础用法-快速入门

首先使用Android studio创建一个hello工程,快速编写代码感受一下

请先确保你当前电脑上的Python环境可用,Chaquopy是根据当前电脑上的Python版本来选择集成对应的版本解释器到apk中的。如你的电脑上有多个Python版本,可通过配置明确指定对应的版本

defaultConfig {

python {

buildPython "C:/Python36/python.exe"

}

}

配置依赖

工程根目录下的 build.gradle

buildscript {

repositories {

google()

jcenter()

// 设置仓库

maven { url "https://chaquo.com/maven" }

}

dependencies {

classpath 'com.android.tools.build:gradle:3.3.1'

// 导入Chaquopy框架的包

classpath "com.chaquo.python:gradle:6.3.0"

}

}

app模块下的 build.gradle

apply plugin: 'com.android.application'

// 应用插件

apply plugin: 'com.chaquo.python'

android {

compileSdkVersion 28

defaultConfig {

applicationId "org.hello"

minSdkVersion 16

targetSdkVersion 28

versionCode 1

versionName "1.0"

// 指定abi,如需在模拟器调试,增加"x86",否则指定"armeabi-v7a"即可

ndk {

abiFilters "armeabi-v7a", "x86"

}

}

buildTypes {}

}

dependencies {

implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'com.android.support:appcompat-v7:28.0.0'

implementation 'com.android.support.constraint:constraint-layout:1.1.3'

}

配置完成后,同步一下gradle,网络状况不良可能会失败,多同步几次,亲测无需代理,同步成功后,所需的依赖就准备好了

编写代码

同步成功后,在工程中的main目录下会生成python文件夹,如未生成,手动生成一个即可,该目录即用来存放我们自己编写的python代码

Python代码

在python文件夹中创建hello.py

from java import jclass

def greet(name):

print("--- hello,%s---" % name)

def add(a,b):

return a + b

def sub(count,a=0,b=0,c=0):

return count - a - b -c

def 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代码

MainActivity.java

package org.hello;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import com.chaquo.python.Kwarg;

import com.chaquo.python.PyObject;

import com.chaquo.python.android.AndroidPlatform;

import com.chaquo.python.Python;

import java.util.ArrayList;

import java.util.List;

public class MainActivity extends AppCompatActivity {

static final String TAG = "PythonOnAndroid";

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initPython();

callPythonCode();

}

// 初始化Python环境 void initPython(){

if (! Python.isStarted()) {

Python.start(new AndroidPlatform(this));

}

}

// 调用python代码 void callPythonCode(){

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 PyObject obj3 = py.getModule("hello").callAttr("get_list", 10,"xx",5.6,'c');

List pyList = obj3.asList();

Log.d(TAG,"get_list = "+pyList.toString());

// 将Java的ArrayList对象传入Python中使用 List params = new ArrayList();

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返调Java类

package org.hello;

import android.util.Log;

import java.util.ArrayList;

import java.util.List;

public class JavaBean {

private String name;

private List data;

public JavaBean(String n){

this.name = n;

data = new ArrayList();

}

public void setData(String el){

this.data.add(el);

}

public void print(){

for (String it: data) {

Log.d("Java Bean - "+this.name,it);

}

}

}

小结:

1. Python没有方法重载,通常一个函数会声明很多参数,注意使用Kwarg类进行命名式传参

2. 注意对象转换,PyObject类是桥梁,fromJava函数将一个Java对象转换为相应的Python对象,toJava函数正好相反,将Python中的对象转换成Java中的对象

3. 以上未演示map用法,实际上与List类似,对应Python中的字典对象,PyObject提供了asMap方法

进阶用法

生成静态代理

我们可以使用Python类来扩展Java,实质上就是编写Python类后,使用工具自动生成对应的Java类

在gradle中进行配置python模块

defaultConfig {

python {

staticProxy "test_class"

}

}

在Python目录中创建test_class.py

from android.os import Bundle

from android.support.v7.app import AppCompatActivity

from com.chaquo.python.hello import R

from java import jvoid, Override, static_proxy,jint,method

class MainActivityEx(static_proxy(AppCompatActivity)):

@Override(jvoid, [Bundle])

def onCreate(self, state):

AppCompatActivity.onCreate(self, state)

self.setContentView(R.layout.activity_main)

'''要想Java类生成对应方法,必须使用该装饰器,指定返回值和参数类型'''

@method(jint, [jint])

def func(self,num):

return 1 + num

Make工程之后会生成对应的Java代码。注意,生成的代码并不在src下,在方法中引用一下MainActivityEx,并自动导包后,可点进去查看生成的源码

// Generated at 2019-08-31T12:29:18Z with the command line:// --path D:\workspace\flutter_space\flutter_web\hello\app\build\generated\python\sources\debug;D:\workspace\flutter_space\flutter_web\hello\app\build\generated\python\requirements\debug/common --java D:\workspace\flutter_space\flutter_web\hello\app\build\generated\python\proxies\debug test_class

package test_class;

import com.chaquo.python.*;

import java.lang.reflect.*;

import static com.chaquo.python.PyObject._chaquopyCall;

@SuppressWarnings("deprecation")

public class MainActivityEx extends android.support.v7.app.AppCompatActivity implements StaticProxy {

static {

Python.getInstance().getModule("test_class").get("MainActivityEx");

}

public MainActivityEx() {

PyObject result;

result = _chaquopyCall(this, "__init__");

if (result != null) result.toJava(void.class);

}

@Override public void onCreate(android.os.Bundle arg0) {

PyObject result;

result = _chaquopyCall(this, "onCreate", arg0);

if (result != null) result.toJava(void.class);

}

public int func(int arg0) {

PyObject result;

result = _chaquopyCall(this, "func", arg0);

return result.toJava(int.class);

}

// 省略......}

注意,要使用静态代理生成器,Python中的类必须使用static_proxy方法进行包装,如需生成方法,还需要使用相关的Python装饰器,详细用法见Static proxy文档

静态代理可同时配置多个

defaultConfig {

python {

staticProxy(

"chaquopy.test.static_proxy.basic",

"chaquopy.test.static_proxy.header",

"chaquopy.test.static_proxy.method"

)

}

}

第三方库引入

Chaquopy支持90%的纯Python源码的第三方库,如BeautifulSoup等,当然,Python很多知名库都是C/C++语言写的,使用Python包装一层而已,例如numpy、pillow、scikit-learn等等,像这样的二进制包,Chaquopy框架也支持一部分,这就相当难得了,实际上,Python移植到安卓平台,最难搞的就是第三方库的移植。想查看Chaquopy支持哪些包含二进制包的Python库,请点击Chaquopy pypi

增加gradle配置

defaultConfig {

python {

// ......

pip {

install "Beautifulsoup4"

install "requests"

install "numpy"

}

}

}

再hello.py中增加代码

from bs4 import BeautifulSoup

import requests

import numpy as np

# ...省略...

# 爬取网页并解析

def get_http():

r = requests.get("https://www.baidu.com/")

r.encoding ='utf-8'

bsObj = BeautifulSoup(r.text,"html.parser")

for node in bsObj.findAll("a"):

print("---**--- ", node.text)

# 使用numpy

def print_numpy():

y = np.zeros((5,), dtype = np.int)

print(y)

MainActivity.java增加调用代码

void callPythonCode(){

// ......省略 py.getModule("hello").callAttr("get_http");

py.getModule("hello").callAttr("print_numpy");

}

使用了网络,还需增加网络权限

完全使用Python开发

前面说过了,Chaquopy框架可以完全使用Python语言编写apk,并且开发者还提供了一个 模板工程

整个工程的main目录下只有一个Python目录,没有java目录,这实际上就是我们之前说的静态代理,并不是没有Java代码,只是根据Python代码自动生成对应的Java代码

from android.os import Bundle

from android.support.v7.app import AppCompatActivity

from com.chaquo.python.hello import R

from java import jvoid, Override, static_proxy

class MainActivity(static_proxy(AppCompatActivity)):

@Override(jvoid, [Bundle])

def onCreate(self, state):

AppCompatActivity.onCreate(self, state)

self.setContentView(R.layout.activity_main)

原理解析

Chaquopy框架并未开源,因此只能通过反编译apk来探究其实现原理

查看AndroidPlatform.class源码,有如下方法

private void loadNativeLibs() {

System.loadLibrary("crystax");

System.loadLibrary("crypto_chaquopy");

System.loadLibrary("ssl_chaquopy");

System.loadLibrary("sqlite3");

System.loadLibrary("python" + Common.PYTHON_SUFFIX);

System.loadLibrary("chaquopy_java");

}

当我看到crystax.so的加载代码时,立刻明白了其实现原理,它使用的是crystax版本的ndk工具链,继续查看反编译的资源结构验证猜想

由其资源结构,基本可知其实现方案,几乎与我之前研究并写的一些博客吻合,该框架的实现方式,基本与我的想法不谋而合,也是我推崇的实现方案。

简单说就是以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框架干的事,不出所料它应该就是libchaquopy_java.so

还有一点值得说说,看过Python解释器源码的应该知道,PyObject是CPyhton解释器中一切对象的超类,当然,在C语言中它是一个结构体,CPython 提供的C语言API,基本上也就是将C语言结构体转换为PyObject实现与Python代码的交互,Python调用C也一样,而Chaquopy框架在处理Java与Python交互时,很巧妙的使用Java实现一个PyObject类,我的理解,它实际上就是将CPython解释器中的PyObject映射到了一个Java类,通过操作这个类实现交互,很有一点前端里所谓虚拟DOM的意思。

更多深入的具体的细节,请直接查看上面给出的我之前写的博客。

文档

这篇文章仅作为一篇开胃菜,更多详细的具体的用法,还是需要查看Chaquopy的文档的,查看文档也是程序员的基本素养了

如果想学习调用Python解释器,这里还有编译好的各个平台版本的Python解释器 - android上的python解释器

缺陷多线程 Chaquopy是线程安全的。但是,因为它基于CPython(Python参考实现),所以它受到CPython的全局解释器锁(GIL)的限制。这意味着尽管Python代码可以在任意数量的线程上运行,但在任何给定时刻只会执行其中一个线程。

内存管理 如果Python对象引用直接或间接引用原始Python对象的Java对象,则可以创建跨语言引用循环。任何一种语言的垃圾收集器都无法检测到这样的循环。避免内存泄漏。要么在循环中的某处使用弱引用,要么在不再需要时手动中断循环。

欢迎关注我的公众号:编程之路从0到1

android与python交互_Android与Python混合编程相关推荐

  1. php和python交互-PHP与Python进行数据交互

    最近,决定在一个项目用tp5进行APP接口开发,用Python做数据分析,然后这就面临一个问题:PHP和Python如何进行数据交互? 思路 我解决此问题的方法是利用了PHP的passthru函数来调 ...

  2. java与python交互_Java与Python

    有这么一个命题,就是说后端算法部门,使用了TensorFlow等机器学习算法进行了数据预测,现在有个需求,就是说要通过浏览器页面输入相关的计算参数,然后返回计算结果,理论上呢,python也可以作为一 ...

  3. git和python交互_使用Python连接和执行GIT命令

    我尝试使用python连接和执行GIT命令,如pull.check status.add和commit新文件等 但我似乎对GIT可执行文件有一个问题.我遵循了教程中的代码来编写T,除非像PyCharm ...

  4. python交互界面实例_Python面向对象编程扑克牌发牌程序,另含大量Python代码!

    1. 题目 编写程序, 4名牌手打牌,计算机随机将52张牌(不含大小鬼)发给4名牌手,在屏幕上显示每位牌手的牌. 很多人学习python,不知道从何学起. 很多人学习python,掌握了基本语法过后, ...

  5. C/C++在Java、Android和Objective-C三大平台下实现混合编程

    点击蓝字 关注我们 因公众号更改推送规则,请点"在看"并加"星标"第一时间获取精彩技术分享 来源于网络,侵删 Android和iOS开发都支持C++开发,可以一 ...

  6. android java和c混合编程_C/C++在Java、Android和Objective-C三大平台下实现混合编程

    Android和iOS开发都支持C++开发,可以一套代码多平台使用.同时C++难以反编译的特性也可以为Android开发带来代码的保密,另一native特性也可以提高代码的运行效率. 一.为什么使用C ...

  7. android java和c混合编程_C/C++在Java项目、Android和Objective-C三大平台下实现混合编程...

    Android和iOS开发都支持C++开发,可以一套代码多平台使用.同时C++难以反编译的特性也可以为Android开发带来代码的保密,另一native特性也可以提高代码的运行效率. 一.为什么使用C ...

  8. python交互模式设置及VIM的tab补齐

    一.python交互模式设置 Python 解释器具有简单的行编辑功能. 在 Unix 系统上,任何 Python 解释器都可能已经添加了 GNU readline 库支持,这样就具备了精巧的交互编辑 ...

  9. python交互解释器_Python 交互解释器

    本章我们讨论交互式 Python 交互解释器. Python 代码可以通过两种基本方式启动. 作为脚本或在交互式解释器中. 这是一个小型 Python 脚本的示例. 它是从 UNIX Shell 启动 ...

最新文章

  1. 4 关卡编辑器_虚幻引擎4与生存游戏产生化学反应,超真实开放世界手游诞生
  2. ios开发-Storyboard在多个viewcontroller之间导航的实现
  3. 2020-11-13(c++下JNI开发不同点)
  4. android 8.0手机开服务端,Android 8.0 + Service开启方式兼容处理
  5. 还在维护吗_你的模具生锈了吗?来了解一下这些防锈维护事项
  6. 中国电信陆良军:2020年5G手机终端规模达1.7亿
  7. 【python】循环结构if、for 、while
  8. GPS 的物理数学原理
  9. 常见数据分析误区有哪些
  10. 使用 Linux下 timerfd 系列 API 创建定时器并使用 epoll 监听
  11. Linux7(CentOs7.5)安装ssh、和修改ssh端口号
  12. pattern-exploiting training (PET)--Few-Shot Learners
  13. 新、JQ知识点笔记整理。
  14. Windows11/10 使用RDP远程桌面时提示 您的凭据不工作/登录没有成功可能的一种原因
  15. 对给定的10个国家名,按其字母的顺序输出。C++
  16. c语言程序设计服装销售系统报告,服装销售管理系统—C语言课程设计
  17. WPS表格 - 数字累加技巧总结
  18. 数字化时代-16:生意人 商人 企业家 资本家 区别
  19. 由浅到深玩转Python爬虫(一)初识爬虫
  20. 风信子网络工作室介绍

热门文章

  1. Windows server 2012 搭建×××图文教程(一)安装×××相关服务
  2. 边结点结构体的定义及使用
  3. activiti自己定义流程之整合(五):启动流程时获取自己定义表单
  4. 英特尔在移动芯片为何衰败 看完此文豁然开朗
  5. Echarts的坐标调整,调整内部网格和外部的间隔
  6. 如何制作启动盘安装ubuntu系统
  7. 局域网组建与维护(1)
  8. 墨迹天气接口html,moji_weather_spider.py
  9. 免费Windows系统服务器,免费试用人数太多,Windows 365云电脑服务器炸了,官方紧急暂停...
  10. java 抽象工厂工厂_Java设计模式之简单工厂、工厂方法和抽象工厂