1. 程式人生 > >Kivy A to Z -- Kivy的執行機制

Kivy A to Z -- Kivy的執行機制

 1. 當看到一個在Android平臺上執行的Python程式時,我的第一個好奇的地方就是它究竟是怎麼做到的。

2. 好,費話少說,我們通過原始碼來分析一下。

3. 首先從dist/default匯入工程,如圖所示:

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. 好,整個過程講完了,不算複雜,但是確實有些繞。

(完)

相關推薦

Kivy A to Z -- Kivy執行機制

 1. 當看到一個在Android平臺上執行的Python程式時,我的第一個好奇的地方就是它究竟是怎麼做到的。 2. 好,費話少說,我們通過原始碼來分析一下。 3. 首先從dist/default匯入工程,如圖所示: 4. 接下來我們來理順一下整個的Python程式的

Kivy A to Z -- Kivy的訊息處理機制

外面一直在下雨,比較無聊,順便總結了下Kivy的訊息的處理過程。 總的來說,在Kivy裡,處理的訊息一共有四種:按鍵訊息,滑鼠訊息,觸屏訊息,還有自定義訊息。下面來看下整個訊息的處理流程。 先來看張圖:      先來解釋下這幾個類都是幹嘛的: 1、EventDis

Kivy a to z -- Kivy的編譯過程-distribute.sh指令碼分析

1 這一節重點來分析一下distribute.sh,以此來了解一下Kivy的整個編譯過程 2 在上一篇文章中,我們講到編譯的方法: ./distribute.sh -m 'openssl pyjnius pil kivy' 3 那整個過程是怎麼進行的,下面來分析一下: 4 

深入理解JVM_java代碼的執行機制01

功能 存在 oot 對象實例 符號 token 類型 格式 找對象 本章學習重點: 1、Jvm: 如何將java代碼編譯為class文件。 如何裝載class文件及如何執行class文件。 jvm如何進行內存分配和回收。 jvm多線程

【JS】JavaScript引擎的內部執行機制

under scrip str tro blog rip 回調函數 ron span  近期在復習JavaScript,看到setTimeout函數時。想起曾經剛學時,在一本書上看過setTimeout()裏的回調函數執行的間隔時間

【java】之java代碼的執行機制

() alt 分享 str clas not roc 成員 輸入 要在JVM中執行java代碼必須要編譯為class文件,JDK是如何將Java代碼編譯為class文件,這種機制通常被稱為Java源碼編譯機制。 1、JVM定義了class文件的格式,但是並沒有定義如何將ja

python裝飾器執行機制

abc print 前沿 spl ini self. color ret instance 前沿: 首先是看到了單例模型,想不明白 outer中的參數 為什麽能像 global的參數 一樣屹立不倒。 #單例模型 def single_model(cls): in

徹底弄懂 JavaScript 執行機制

函數 大名 定時 意思 技術 渲染 文字 根據 java 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發工作,我們經常會遇到這樣的情況:給定的幾行

js --- 執行機制

循環 gpo 可執行 pad loop 同步任務 16px 觸發 pos 1. JS為什麽是單線程的?  JS最初被設計用在瀏覽器中,那麽想象一下,如果瀏覽器中的JS是多線程的。 那麽現在有2個進程,process1 process2,由於是多進程的JS,所以他們對同一個

[轉]JS 引擎的執行機制

wan queue 多線程 .html 單擊事件 語句 eve resolve title ------------------------------------------------------ JS 引擎的執行機制 關於JS引擎的執行機制,首先牢記2點:

js為什麽是單線程的?10分鐘了解js引擎的執行機制

容易 等於 bsp -m 深入理解 block 順序 dom 依次 深入理解JS引擎的執行機制 1.JS為什麽是單線程的? 為什麽需要異步? 單線程又是如何實現異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說s

js執行機制

分發 sleep 代碼執行 過程 rom spa set 是否 引擎 1. 關於javascript js是一門單線程語言,一切js版的‘多線程’都是用單線程模擬起來的。 2. js事件循環 將任務分為2類:同步任務、異步任務 同步任務進入主線程,異步任務

深入理解Dalvik虛擬機- 解釋器的執行機制

util dlink stat counter before expose 加鎖 enter 機制 Dalvik的指令運行是解釋器+JIT的方式,解釋器就

a標簽自執行點擊事件

() style AC AD 點擊事件 click 原生 pac 標簽 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; color: #a5b2b9 } p.p2 { margin: 0.0px 0.

淺析JS異步執行機制

一個隊列 http請求 調度 等待 __name__ 服務端 nco sta req 前言 JS異步執行機制具有非常重要的地位,尤其體現在回調函數和事件等方面。本文將針對JS異步執行機制進行一個簡單的分析。 從一份代碼講起 下面是兩個經典的JS定時執行函數,這兩個函數的區別

關於linux的命令一個莫名的想法,az有哪些命令

linux系統命令如果有人突然問你linux系統中a到z中命令有哪些,不知道你是什麽反應。做稍微整理下,以下是a到z的常用的命令。命令a-z: a : awk,alias,ab b:blkid ,bash,brctl c:chmod,chown,cp,cd,cat d:df,date e:echo,expor

javascript的執行機制—Event Loop

如果 異步任務。 兩個 用戶 徹底 再次 IT bsp 執行順序 既然今天要談的是javascript的事件循環機制,要理解事件循環,首先要知道事件循環是什麽。 我們先從一個例子來看一下javascript的執行順序。 <script> setTime

LINQ to Object——立即執行的Enumerable類方法

sele span 是否 技術分享 lin CA cee 註意 第一個 在前面說到LINQ to Object——延時執行的Enumerable類的方法,接下來說說LINQ to Object——立即執行的Enumerable類方法。 1.ToArray 序列轉換成數組

JavaScript執行機制

指定 i++ 立即執行 使用 func bubuko 異步任務。 下一個 href 原文   簡書原文:https://www.jianshu.com/p/0d2d42fbe1dc 大綱   1、場景分析  2、執行機制相關知識點  3、以實例來說明JavaScript的執

shell條件判斷if中的-a到-z的意思

res 設備 ring1 描述 bin 數字 sed per === [ -a FILE ] 如果 FILE 存在則為真。 [ -b FILE ] 如果 FILE 存在且是一個塊特殊文件則為真。 [ -c FILE ] 如果 FILE 存在且是一個字特殊文件則為真