Android Framework層的JNI機制(一)

JNI(Java Native Interface)Java本地接口。最初對JNI的了解,僅僅停留在Java通過JNI可以實現(xiàn)對C/C++函數(shù)的調用。比如,首先在Java中寫好native方法。然后在C或C++中文件中,定義一個對應的函數(shù),在這個函數(shù)中,實現(xiàn)自己的代碼或者調用其他的標準庫。最后加載一下生成的動態(tài)庫,便可以開始使用這個native方法。像是在照葫蘆畫瓢,知其然,不知其所以然。最近在看Framework層中JNI相關的代碼,加上網(wǎng)上大咖的神貼,綜合理解,先以Framework中Log為研究對象,分析JNI在其中的使用。

概要

  • JNI機制基本要點
  • Android中JNI的存在方式
  • Framework層Log的JNI使用

一、JNI機制基本要點

用過JNI的工程師,都接觸過下面這些知識點:

  • JavaVM:表示Java虛擬機。
  • JNIEnv:表示JNI環(huán)境的上下文,例如注冊、查找類、異常等。
  • jclass:在JNI中表示的Java類。
  • jmethodID:在JNI中表示的Java類中的方法。
  • jfiledID:在JNI中表示的Java類中的屬性。
  • 線程:JNI中通過AttachCurrentThread和DetachCurrentThread方法,實現(xiàn)和Java線程的結合。

它們都在一個叫jni.h的頭文件中,這個頭文件是JNI機制中很重要的一個頭文件。
源代碼路徑:/libnativehelper/include/nativehelper/jni.h。

在libnativehelper目錄下的源文件,編譯后會生成一個libnativehelper.so的動態(tài)庫。其實,jni.h是Android根據(jù)Java本地調用的標準寫成的一個頭文件,在它里面包括了基本類型(類型的映射),以及JavaVM,JNIEnv,jclass,jmethodID,jfiledID等數(shù)據(jù)結構的定義。

JavaVM對應于jni.h中JNIInvokeInterface結構體,表示虛擬機。JNIEnv對應于JNINativeInterface結構體,表示JNI的環(huán)境。在JNI的使用過程中,所調用的功能大都來自JNINativeInterface結構體。例如,處理Java屬性和方法的查找,Java屬性的訪問,Java方法的調用等功能。另外,在JNINativeInterface結構體中,涉及到的一個JNINativeMethod結構體,它表示在本地實現(xiàn)的一個方法,即native方法,后面進行JNI注冊的時候會用到。

二、Android中JNI的存在方式

Android中JNI的存在方式主要分兩種: 框架層和應用層的JNI使用。不對應用層的使用情況進行介紹,主要目的還是看看框架層里面的JNI。
在Android框架中,JNI庫是一些普通的本地動態(tài)庫,被放置在目標系統(tǒng)的/system/lib目錄中。
Java框架層,最主要的JNI內(nèi)容源代碼路徑為:/frameworks/base/core/jni。
這里面的代碼會生成一個libandroid_runtiem.so的動態(tài)庫。接下來要分析的Log中JNI的使用,就在這個目錄之中。

三、Framework層Log的JNI使用

  1. Java框架層的Log

在編程的時候,大家都用過Log,其實這個我們經(jīng)常使用的Log工具,在Java框架層最終調用的是native方法。
貼上源碼的路徑:/frameworks/base/core/java/android/util/Log.java。
如果感興趣,可以進去瞧一瞧。咱們以Log.java中的println_native()這個本地方法,進行分析。

  1. Log的JNI實現(xiàn)

Log的JNI的實現(xiàn)是在一個叫android_util_Log.cpp的源文件中。
源碼路徑:
頭文件:/frameworks/base/core/jni/android_util_Log.h。
源文件:/frameworks/base/core/jni/android_util_Log.cpp。

在android_util_Log.cpp源文件中,我們可以找到println_native的身影。

/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

JNINativeMethod是前面提到的一個結構體,這個結構體表示一個實現(xiàn)的本地方法。這個結構體在jni.h文件中定義,內(nèi)容如下:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

它有三個指針變量,第一個是字符型指針,可以表示一個字符串,即native方法的名稱;第二個也是字符型指針,同樣可以表示一個字符串,代表這個native方法的參數(shù)和返回值(有特殊的表示方法);第三個是一個未指定類型指針,表示一個函數(shù)指針,指向這個native方法對應的jni函數(shù)。

有了對JNINativeMethod了解,就可以理解println_native在android_util_Log.cpp源文件中的含義了。其對應jni實現(xiàn)函數(shù)是android_util_Log_println_native()。在jni實現(xiàn)函數(shù)中,又調用了__android_log_buf_write()這個方法,__android_log_buf_write是本地框架層(非Java框架層)基礎的C庫之上,Android最底層的本地Log庫。
Log庫的源碼路徑為:
頭文件:system/core/include/cutils/log.h。
源文件:system/core/liblog。
編譯后會生成liblog.so動態(tài)庫和liblog.a靜態(tài)庫。

  1. Log的JNI注冊
int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, "android/util/Log");

    levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));

    return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}

這是android_util_Log.cpp源文件中對jni的注冊,可以看到RegisterMethodsOrDie()這個方法的調用,傳入了我們前面看到的gMethods數(shù)組,進行JNI注冊。到這里結束了嗎?確實第一次看到這里的時候,以為就此結束了。然而,是誰調用了register_android_util_Log()這個方法?在RegisterMethodsOrDie()這個函數(shù)里面又做了什么呢?

  1. register_android_util_Log()函數(shù)

register_android_util_Log()這個方法只在android_util_Log.cpp源文件中進行定義,需要找到誰對它進行了調用,才好進一步理解Log的JNI的注冊過程。Android源碼環(huán)境有一個非常不錯的方法,可以通過字符串,找到出現(xiàn)過的文件。
指令:cgrep 'register_android_util_Log'
類似于:find . -type f -name "*.cpp" | xargs grep "register_android_util_Log"
通過結果可以發(fā)現(xiàn)一個AndoridRuntime.cpp,這是個什么鬼?只能說它很強,是系統(tǒng)運行時的工具類,為Android的運行提供支持。JNI的部分封裝也在這個類中。
源碼路徑:
/frameworks/base/include/android_runtime/AndroidRuntime.h。
/frameworsk/base/core/jni/AndroidRuntime.cpp。
可以發(fā)現(xiàn),它也是在/frameworsk/base/core/jni目錄下,說明也是在libandroid_runtime.so動態(tài)庫中。
在AndroidRuntime.cpp源文件gRegJNI數(shù)組中,發(fā)現(xiàn)了register_android_util_Log方法。

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_util_Log),
    REG_JNI(register_android_util_MemoryIntArray),
    //省略
}

然后,又在AndroidRuntime.cpp源文件的AndroidRuntime::startReg()這個方法中,使用了gRegJNI這個數(shù)組。

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ATRACE_NAME("RegisterAndroidNatives");
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

按源代碼注釋的意思是,這里在向虛擬機注冊本地方法。同樣在AndroidRuntime.cpp源文件中,AndroidRuntime::start()中又調用了AndroidRuntime::startReg(),到這里,需要繼續(xù)往下看,就得找找是誰調用了AndroidRuntime::start()方法。然而,要想知道誰調用了它,已經(jīng)涉及到zygote這一塊的知識。

  1. zygote

zygote是通過init進程讀取init.rc啟動的一個守護進程的名稱。如果從最下面開始說,得再介紹一下Android啟動流程的本地階段,這一塊,屬于擴充了解。

Android啟動流程的本地階段:

  • BootLoader運行,Linux通用內(nèi)容。
  • Linux內(nèi)核運行,Linux通用內(nèi)容,通常是二進制形式代碼形式存在。
  • 內(nèi)核加載根文件系統(tǒng),Linux通用內(nèi)容。
  • init進程運行,用戶空間的第一個進程。
  • 運行init.rc腳本。
  • 加載system和data文件系統(tǒng)。
  • 運行各種服務,主要為各種守護進程。

本地部分啟動完成,形成一系列守護進程,其中名稱為zygote的守護進程,將繼續(xù)完成Java部分的初始化。

Java部分的啟動流程:

  • 從本地可執(zhí)行程序運行名為zygote的守護進程。
  • zygote運行ZygoteInit(進入Java程序)。
  • ZygoteInit運行SystemServer(Java類),并分裂出新的進程。
  • SystemServer首先運行l(wèi)ibandroid_servers.so庫當中的初始化(進入本地程序)。
  • 執(zhí)行l(wèi)ibanroid_servers.so當中的系統(tǒng)初始化。
    SystemServer中的Java初始化再次被調用(再入Java程序)。
  • 建立ServerThread線程。
  • ServerThread線程建立各個服務,然后進入循環(huán)。
  • ActivityManagerService在啟動結束發(fā)送相關信息。
  • 各個Java應用程序運行。

這些是引用資料書中的知識,想詳細了解,可以看看《Android核心原理及系統(tǒng)級應用高效開發(fā)》或《深入理解Android系統(tǒng)》?;氐轿覀兊膠ygote進程,init.rc中包含了一個init.${ro.zygote}.rc。 init.rcinit.${ro.zygote}.rc源碼路徑:/system/core/rootdir。

在init.zygote32.rc中,相關內(nèi)容如下:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

這個是Android系統(tǒng)中的特殊語法,它啟動了一個名稱為zygote的進程,也就是/system/bin/app_process這個可執(zhí)行程序。
源碼路徑為:/frameworks/base/cmds/app_process。
在這個目錄下,有一個app_main.cpp的源文件,其中相關的代碼如下:

// 省略
if (zygote) {
    runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
    runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
    fprintf(stderr, "Error: no class name or --zygote supplied.\n");
    app_usage();
    LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    return 10;
}

在app_main.cpp源文件中,有一個AppRuntime的類,它繼承了AndroidRuntime。runtime是AppRuntime類的一個實例,runtime.start()相當于調用了AndroidRuntime::start()這個方法,至此,前后就連接起來了。概括的說,系統(tǒng)啟動zygote進程時,會調用AndroidRuntime::start()方法,接著調用AndroidRuntime::startReg(),然后調用到了register_android_util_Log()這個方法。剩下最后一個問題,register_android_util_Log()被調用后,在它方法體中的RegisterMethodsOrDie()函數(shù)做了什么?

  1. RegisterMethodsOrDie()函數(shù)

RegisterMethodsOrDie()這個方法是/frameworks/base/core/jni/core_jni_helpers.h中聲明的一個方法。

static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                       const JNINativeMethod* gMethods, int numMethods) {
    int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
    return res;
}

在其中我們可以看到,其實又回到了AndroidRuntime這個類,調用了它的registerNativeMethods()方法,并最終調用了jniRegisterNativeMethods()進行本地方法的注冊。而jniRegisterNativeMethods()是/libnativehelper/JNIHelp.cpp源文件中的方法,它里面內(nèi)容為:

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp,
                     "Native registration unable to find class '%s'; aborting...",
                     className) == -1) {
            // Allocation failed, print default warning.
            msg = "Native registration unable to find class; aborting...";
        } else {
            msg = tmp;
        }
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        char* tmp;
        const char* msg;
        if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
            // Allocation failed, print default warning.
            msg = "RegisterNatives failed; aborting...";
        } else {
            msg = tmp;
        }
        e->FatalError(msg);
    }

    return 0;
}

這段代碼可以看出,通過調用(*env)的RegisterNatives指針函數(shù),進行了JNI注冊。所以最后的動作是交給了JNINativeInterface結構體所表示的JNI環(huán)境執(zhí)行。當在Java層調用native方法時,不需要依據(jù)native方法包和名稱尋找對應的JNI函數(shù)。而是可以通過已經(jīng)注冊的映射關系,快速找到對應的JNI函數(shù)的指針,從而開始函數(shù)調用,大大提高執(zhí)行效率。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容