Kivy A to Z -- Kivy的运行机制

 1. 当看到一个在Android平台上运行的Python程序时,我的第一个好奇的地方就是它究竟是怎么做到的。

2. 好,费话少说,我们通过源码来分析一下。

3. 首先从dist/default导入工程,如图所示:

 

Kivy A to Z -- Kivy的运行机制_第1张图片

 

4. 接下来我们来理顺一下整个的Python程序的引导过程

 

5. PythonActivity的onCreate中:

    mView =new SDLSurfaceView(
            this,
            mPath.getAbsolutePath());
    Hardware.view =mView;
    setContentView(mView); 



6. SDLSurfaceView实现了:SurfaceHolder.Callback,所以接下来SDLSurfaceView.surfaceChanged将被调用

 

7. 来看看surfaceChanged做了什么:

    if (!mRunning) {
        mRunning =true;
        new Thread(this).start();
    }else {
        mChanged =true;
        if (mStarted) {
             nativeExpose();
        }
    } 



从new Thread(this).start()我们知道会创建一个线程,在这个线程里,SDLSurfaceView的run方法将被调用

 

8. 接下来再看看SDLSurfaceView.run

这个函数主要是完成一些OPENGL的初始化工作,然后调用 waitForStart();

waitForStart()在等什么呢?答案是,在等待Python的初始化工作完成。

 

9. PythonActivity的onResume中:

    if (!mLaunchedThread) {
        mLaunchedThread =true;
        new Thread(this).start();
    }
    if (mView !=null) {
        mView.onResume();
    } 


 


该函数的主要作用就是创建了一个线程

另外,当PythonActivyt被创建的时候,mView.onResume什么事也没做。

 

10. 再来看看PythonActivity这个线程的run函数看看做了什么

public void run() {
    unpackData("private", getFilesDir());
    unpackData("public",externalStorage);
    System.loadLibrary("sdl");
    System.loadLibrary("sdl_image");
    System.loadLibrary("sdl_ttf");
    System.loadLibrary("sdl_mixer");
    System.loadLibrary("python2.7");
    System.loadLibrary("application");
    System.loadLibrary("sdl_main");
    System.load(getFilesDir() +"/lib/python2.7/lib-dynload/_io.so" );
    System.load(getFilesDir() +"/lib/python2.7/lib-dynload/unicodedata.so" );
    try {
        System.loadLibrary("sqlite3");
        System.load(getFilesDir() +"/lib/python2.7/lib-dynload/_sqlite3.so" );
    }catch(UnsatisfiedLinkError e) {
    }
    try {
        System.load(getFilesDir() +"/lib/python2.7/lib-dynload/_imaging.so" );
        System.load(getFilesDir() +"/lib/python2.7/lib-dynload/_imagingft.so" );
        System.load(getFilesDir() +"/lib/python2.7/lib-dynload/_imagingmath.so" );
    }catch(UnsatisfiedLinkError e) {
    }
    if (mAudioThread ==null ) {
        Log.i("python","Starting audio thread");
        mAudioThread =new AudioThread(this);
    }
    runOnUiThread(new Runnable () {
        public void run() {
            mView.start();
        }
    });
} 


 


首先是从private.mp3中解压出python的运行环境

unpackData("private", getFilesDir());

解压的路径是程序所在的data目录的files文件文件夹下

再解压出Kivy的代码

unpackData("public", externalStorage);

解压的路径是在内部SD卡中。

在这里主要要是加载了一堆的动态库,然后调用

runOnUiThread(new Runnable () { public void run() { mView.start(); } });

转到SDLSurfaceView.start中运行了。

 

11. 再来看看SDLSurfaceView.start做了什么:

    this.setFocusableInTouchMode(true);
    this.setFocusable(true);
    this.requestFocus();
    synchronized (this) {
        mStarted =true;
        this.notify();
    } 



这里我们比较关注的是:this.notify(),这样SDLSurfaceView.run里的waitForStart该返回了

 

12. 接着往下看SDLSurface.run里的代码:
 

   nativeResize(mWidth,mHeight);
    nativeInitJavaCallbacks();
    nativeSetEnv("ANDROID_PRIVATE",mFilesDirectory);
    nativeSetEnv("ANDROID_ARGUMENT",mArgument);
    nativeSetEnv("PYTHONOPTIMIZE","2");
     nativeSetEnv("PYTHONHOME",mFilesDirectory);
     nativeSetEnv("PYTHONPATH",mArgument +":" +mFilesDirectory +"/lib");
    //XXX Using SetOpenFile make a crash in nativeSetEnv. I don't
    // understand why, maybe because the method is static or something.
    // Anyway, if you remove that part of the code, ensure the Laucher
    // (ProjectChooser) is still working.
    final android.content.Intent intent =mActivity.getIntent();
    if (intent !=null) {
        final android.net.Uri data = intent.getData();
        if (data !=null && data.getEncodedPath() !=null)
            nativeSetEnv("PYTHON_OPENFILE", data.getEncodedPath());
    }
    nativeSetMultitouchUsed();
    nativeInit(); 



这里做了几件事:

设置Python的路径

设置Kivy代码所在的路径

最后调用native函数nativeInit即开始进行Python的初始化了。

 

13. 最后,来看下nativeInit做了什么动作

我们先找到src\jni\sdl_main下的sdl_main.c

extern C_LINKAGE void
JAVA_EXPORT_NAME(SDLSurfaceView_nativeInit) ( JNIEnv*  env, jobject thiz )
{
  int argc = 1;
  char * argv[] = { "sdl" };
  main( argc, argv );
};

extern C_LINKAGE void
JAVA_EXPORT_NAME(SDLSurfaceView_nativeIsSdcardUsed) ( JNIEnv*  env, jobject thiz, jint flag )
{
  isSdcardUsed = flag;
}

extern C_LINKAGE void
JAVA_EXPORT_NAME(SDLSurfaceView_nativeSetEnv) ( JNIEnv*  env, jobject thiz, jstring j_name, jstring j_value )
{
    jboolean iscopy;
    const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy);
    const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy);
    setenv(name, value, 1);
    (*env)->ReleaseStringUTFChars(env, j_name, name);
    (*env)->ReleaseStringUTFChars(env, j_value, value);
}


 


我们重点关注一下:SDLSurfaceView_nativeInit

JAVA_EXPORT_NAME(SDLSurfaceView_nativeInit) ( JNIEnv*  env, jobject thiz )
{
int argc = 1;
char * argv[] = { "sdl" };
main( argc, argv );
}; 



 这里的main是在哪里实现的呢?

先来看看sdl_main.c包含的头文件:sdl_main.h

#define main SDL_main

哦,原来这个main原名叫SDL_main

那么我们接下来搜索一下SDL_main在什么地方定义的吧,你会发现死活搜索不到

再来看下jni/sdl_main/Android.mk

LOCAL_SHARED_LIBRARIES := sdl application

这里引用了application这个动态库

application在哪里呢?我们找到src\jni\application\src\start.c,这里有一个main,但是我们要找的是SDL_main

我们接着看start.c引用的头文件SDL.h,在SDL.h里引用了SDL_main.h,而SDL_main.h里定义了:#define main SDL_main

嗯,很清楚了,start.c里的main就是SDL_main

最后,来看下main的代码,做了哪些工作:

int main(int argc, char **argv) {

    char *env_argument = NULL;
    int ret = 0;
    FILE *fd;

    LOG("Initialize Python for Android");
    env_argument = getenv("ANDROID_ARGUMENT");
    setenv("ANDROID_APP_PATH", env_argument, 1);
  //setenv("PYTHONVERBOSE", "2", 1);
    Py_SetProgramName(argv[0]);
    Py_Initialize();
    PySys_SetArgv(argc, argv);

    /* ensure threads will work.
     */
    PyEval_InitThreads();

    /* our logging module for android
     */
    initandroidembed();

    /* inject our bootstrap code to redirect python stdin/stdout
     * replace sys.path with our path
     */
    PyRun_SimpleString(
        "import sys, posix\n" \
        "private = posix.environ['ANDROID_PRIVATE']\n" \
        "argument = posix.environ['ANDROID_ARGUMENT']\n" \
        "sys.path[:] = [ \n" \
     "    private + '/lib/python27.zip', \n" \
     "    private + '/lib/python2.7/', \n" \
     "    private + '/lib/python2.7/lib-dynload/', \n" \
     "    private + '/lib/python2.7/site-packages/', \n" \
     "    argument ]\n" \
        "import androidembed\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" \
        "            androidembed.log(l)\n" \
        "        self.buffer = lines[-1]\n" \
        "    def flush(self):\n" \
        "        return\n" \
        "sys.stdout = sys.stderr = LogFile()\n" \
     "import site; print site.getsitepackages()\n"\
     "print 'Android path', sys.path\n" \
        "print 'Android kivy bootstrap done. __name__ is', __name__");

    /* run it !
     */
    LOG("Run user program, change dir and execute main.py");
    chdir(env_argument);

  /* search the initial main.py
   */
  char *main_py = "main.pyo";
  if ( file_exists(main_py) == 0 ) {
     if ( file_exists("main.py") )
       main_py = "main.py";
     else
       main_py = NULL;
  }

  if ( main_py == NULL ) {
     LOG("No main.pyo / main.py found.");
     return -1;
  }

    fd = fopen(main_py, "r");
    if ( fd == NULL ) {
        LOG("Open the main.py(o) failed");
        return -1;
    }

    /* run python !
     */
    ret = PyRun_SimpleFile(fd, main_py);

    if (PyErr_Occurred() != NULL) {
        ret = 1;
        PyErr_Print(); /* This exits with the right code if SystemExit. */
        if (Py_FlushLine())
       PyErr_Clear();
    }

    /* close everything
     */
  Py_Finalize();
    fclose(fd);

    LOG("Python for android ended.");
    return ret;
}

 


首先,当然是初始化Python:Py_Initialize()

接下来允许Python多线程:PyEval_InitThreads()

PyRun_SimpleString里运行的代码的作用是将Python的输出重定向到Android的log输出,这样我们就可以在logcat里看到Python的日志输出了。

最后,调用PyRun_SimpleFile运行Kivy代码

 

14. 好,整个过程讲完了,不算复杂,但是确实有些绕。

 

(完)

 

你可能感兴趣的:(android,python,kivy)