Android 平台的Python——基础篇(一)
Android 平台的Python——JNI方案(二)
Android 平台的Python——CLE方案实现(三)
Android 平台的Python——第三方库移植
Android 平台的Python——编译Python解释器
上一篇博客已经讲了Python与C的交互,本篇主要关于在Android项目中嵌入Python解释器,实现Java与Python相互调用。以有好的项目提供了思路,地址:https://github.com/joaoventura/pybridge
我这里说一下简单实现
配置好crystax ndk环境,并创建一个NDK项目,将crystax 包下面的 libpython3.5m.so拷贝至工程 lib/armeabi目录下
public class Util {
static {
try {
System.loadLibrary("jni_test");
} catch (Exception e) {
Log.e("jni_test", ""+e);
}
}
public static native int run(String path);
}
jni_test.c
#include
#include
#include
#define LOG(x) __android_log_write(ANDROID_LOG_WARN, "jni_test", (x))
/* --------------- */
/* Android log */
/* --------------- */
static PyObject *androidlog(PyObject *self, PyObject *args)
{
char *str;
if (!PyArg_ParseTuple(args, "s", &str))
return NULL;
LOG(str);
Py_RETURN_NONE;
}
static PyMethodDef AndroidlogMethods[] = {
{"log", androidlog, METH_VARARGS, "Logs to Android stdout"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef AndroidlogModule = {
PyModuleDef_HEAD_INIT,
"androidlog", /* m_name */
"Log for Android", /* m_doc */
-1, /* m_size */
AndroidlogMethods /* m_methods */
};
PyMODINIT_FUNC PyInit_androidlog(void)
{
return PyModule_Create(&AndroidlogModule);
}
void setAndroidLog()
{
// Inject bootstrap code to redirect python stdin/stdout
// to the androidlog module
PyRun_SimpleString(
"import sys\n" \
"import androidlog\n" \
"class LogFile(object):\n" \
" def __init__(self):\n" \
" self.buffer = ''\n" \
" def write(self, s):\n" \
" s = self.buffer + s\n" \
" lines = s.split(\"\\n\")\n" \
" for l in lines[:-1]:\n" \
" androidlog.log(l)\n" \
" self.buffer = lines[-1]\n" \
" def flush(self):\n" \
" return\n" \
"sys.stdout = sys.stderr = LogFile()\n"
);
}
/* --------------------------------------------------------------- */
/* 以上部分代码,为C语言为Python3编写拓展模块的标准模板代码。 */
/* 这套模板写起来有些繁琐,我们之前已经用SWIG自动化实现过拓展模块 */
/* 这部分代码主要功能是将Python的print输出连接到Android的Log输出中 */
/* 与我们要探讨的内容联系不大,无须感到困惑 */
/* ---------------------------------------------------------------- */
/* java对应的native方法 */
JNIEXPORT jint
JNICALL Java_com_example_jnitest_Util_run(JNIEnv *env, jobject obj, jstring path)
{
LOG("Initializing the Python interpreter");
const char *pypath = (*env)->GetStringUTFChars(env, path, NULL);
// Build paths for the Python interpreter
char paths[512];
snprintf(paths, sizeof(paths), "%s:%s/stdlib.zip", pypath, pypath);
// Set Python paths
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
Py_SetPath(wchar_paths);
PyImport_AppendInittab("androidlog", PyInit_androidlog);
Py_Initialize();//初始化Python解析器
if (!Py_IsInitialized())
{
LOG("Initialize failed");
return -1;
}
setAndroidLog();
PyRun_SimpleString("import test");
// Cleanup
(*env)->ReleaseStringUTFChars(env, path, pypath);
PyMem_RawFree(wchar_paths);
Py_Finalize();//释放Python解析器
return 0;
}
Android.mk
LOCAL_PATH := $(call my-dir)
CRYSTAX_PATH := D:/developer/AS_SDK/crystax-ndk-10.3.2
include $(CLEAR_VARS)
LOCAL_MODULE := jni_test
LOCAL_SRC_FILES := jni_test.c
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := python3.5m
include $(BUILD_SHARED_LIBRARY)
# Include libpython3.5m.so
include $(CLEAR_VARS)
LOCAL_MODULE := python3.5m
LOCAL_SRC_FILES := $(CRYSTAX_PATH)/sources/python/3.5/libs/$(TARGET_ARCH_ABI)/libpython3.5m.so
LOCAL_EXPORT_CFLAGS := -I $(CRYSTAX_PATH)/sources/python/3.5/include/python/
include $(PREBUILT_SHARED_LIBRARY)
test.py
def sayHello():
print("Hello Android,from Python ")
sayHello()
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AssetExtractor assetExtractor = new AssetExtractor(this);
assetExtractor.removeAssets("python");
assetExtractor.copyAssets("python");
final String pythonPath = assetExtractor.getAssetsDataDir() + "python";
AsyncTask.execute(new Runnable() {
@Override
public void run() {
Log.d("---> ",""+Util.run(pythonPath));
}
});
}
}
AssetExtractor.java 工具类,部分代码
public class AssetExtractor {
private final static String LOGTAG = "AssetExtractor";
private Context mContext;
private AssetManager mAssetManager;
public AssetExtractor(Context context) {
mContext = context;
mAssetManager = context.getAssets();
}
/**
* Copies the assets from the APK to the device.
*
* @param path: the source path
*/
public void copyAssets(String path) {
for (String asset : listAssets(path)) {
copyAssetFile(asset, getAssetsDataDir() + asset);
}
}
/**
* Removes recursively the assets from the device.
*
* @param path: the path to the assets folder
*/
public void removeAssets(String path) {
File file = new File(getAssetsDataDir() + path);
recursiveDelete(file);
}
}
方案一的实现到此结束,这种方案的好处是可以完全自主的控制Python解释器与java的交互,缺点是过于麻烦,且须精通Android的ndk编程以及C语言,否则出问题,不会解决。
与之相比,下一篇方案二的实现则简单得多。