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

Android 平台的Python——基础篇(一)
Android 平台的Python——JNI方案(二)
Android 平台的Python——CLE方案实现(三)
Android 平台的Python——第三方库移植
Android 平台的Python——编译Python解释器

上一篇博客已经讲了Python与C的交互,本篇主要关于在Android项目中嵌入Python解释器,实现Java与Python相互调用。以有好的项目提供了思路,地址:https://github.com/joaoventura/pybridge

我这里说一下简单实现

1、环境准备

配置好crystax ndk环境,并创建一个NDK项目,将crystax 包下面的 libpython3.5m.so拷贝至工程 lib/armeabi目录下

2、为Java 编写 native方法

public class Util {
	static {
		try {
	        System.loadLibrary("jni_test");
		} catch (Exception e) {
			Log.e("jni_test", ""+e);
		}
		
    }
	public static native int run(String path);
    
}

3、编写Jni代码

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)

4、编写Python代码

test.py

def sayHello():
    print("Hello Android,from Python ")
    
sayHello()

5、项目调用

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语言,否则出问题,不会解决。
与之相比,下一篇方案二的实现则简单得多。

关注个人公众号:编程之路从0到1

编程之路从0到1

你可能感兴趣的:(Python3新天地,Android技术)