Android JNI概述
本文基於Android 9.0原始碼分析
Android JNI簡介
JNI是Java Native Interface, 它提供了一種從位元組碼(Java/Kotlin)到Native程式碼(c/c++/assembly)的互動方式
JavaVM與JNIEnv
JNI定義了兩個關鍵的資料結構:JavaVM和JNIEnv

- JavaVM
- JavaVM提供了"invocation interface"函式表,允許你建立和銷燬JavaVM,理論上你可以在程序內建立多個JavaVM例項,但Android只允許建立一個。
- JNIEnv
- JNIEnv提供了大部分JNI方法,JNIEnv儲存在TLS中,基於上述原因,不能線上程間共享JNIEnv。上面的圖中JNINativeInterface實際是全域性結構體,圖有點問題。
- 執行緒
Android Native方法註冊
使用 RegisterNatives
顯示註冊
示例
public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } ...... /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); }
// 1) using RegisterNatives register native method static jstring StringFromJNI(JNIEnv *env, jobject obj) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } static JNINativeMethod jni_methods[] = { {"stringFromJNI", "()Ljava/lang/String;", (void *)StringFromJNI} }; // 這裡JNI_OnLoad不需要新增extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } if (env->RegisterNatives(env->FindClass("lbtrace/jniregister/MainActivity"), jni_methods, sizeof(jni_methods) / sizeof(JNINativeMethod)) != JNI_OK) return -1; return JNI_VERSION_1_6; }
原理

-
首先獲取呼叫者的類載入(這裡是應用類載入器),然後在類載入器的DexPathList中查詢Native庫
- 根據Native庫的名字產生平臺相關的庫名
- 在DexPathList的Native庫路徑列表中查詢Native庫,找到後返回庫的絕對路徑
-
JavaVM負責載入Native庫,如果沒有載入過,裝載Native庫到程序地址空間,然後查詢Native庫中是否有函式
JNI_OnLoad()
,如果存在,執行;否則執行時動態查詢Native方法。 -
在
JNI_OnLoad()
中,首先獲取當前執行緒的JNIEnv,然後呼叫其RegisterNatives()
註冊Native方法。- 首先檢查需要註冊Native方法的資訊是否合法
- 根據方法名、方法簽名查詢對應的Native方法的ArtMethod
- 將Native函式地址寫入ArtMethod物件特定的成員中
執行時動態查詢
示例
// 2) Android Runtime dynamic find native method // 必須新增extern "C" extern "C" JNIEXPORT jstring JNICALL Java_lbtrace_jniregister_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
原理

- ART執行時執行到呼叫Native方法的位元組碼時,首先獲取方法對應的ArtMethod,根據ArtMethod物件的地址以及成員entry_point_from_quick_compiled_code_的偏移得到JNI Stub的地址。
- 在JNI Stub函式中,首先進行Native方法呼叫前的準備工作(引數處理、程序狀態切換等),然後根據特定的Native方法名(java_xxx_xxx_xxx)查詢Native方法,呼叫Native方法,Native方法地址儲存到ArtMethod,最後是Native方法呼叫結束後的清理工作。

思考:Native方法中jobject型別的引數是什麼?
在先前的Android版本中是Local Reference
- StackReference

Android JNI本地引用和全域性引用
對於基本型別,進行JNI呼叫時,可以直接拷貝到Native方法,但是對於Java物件採用引用傳遞。虛擬機器必須能夠追蹤到所有傳遞到Native方法的Java物件,避免被GC回收。當Native方法不再需要Java物件時,必須有一種方法通知虛擬機器。

對於本地引用及全域性引用的實現細節,將在後續文章中討論。
Local Reference
本地引用在Native方法呼叫期間可用,在Native方法返回後自動釋放。
- 對於大的Java物件的本地引用,不用時應及時釋放
- 不要建立太多本地應用
Global Reference
全域性引用不會自動釋放,直到顯式的刪除
Weak Global Reference
弱全域性引用是一種特殊的全域性引用,與普通的全域性引用不同的是,GC可以回收弱全域性引用關聯的Java物件。當GC執行時,會回收一個僅僅被弱全域性引用關聯Java物件。所以在使用之前,應該首先檢查弱全域性引用的Java物件是否被回收。
思考:Android中Java引用到底是什麼?

- 根據Java引用原理,可以在Native層直接修改Java物件內容
public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { ...... TestObject testObject = new TestObject(); Log.i("JNI", "Before : " + testObject.month + "月" + testObject.day + "日"); stringFromJNI(testObject); Log.i("JNI", "After : " + testObject.month + "月" + testObject.day + "日"); ...... } public native String stringFromJNI(Object obj); static class TestObject { int month = 11; int day = 29; } } extern "C" JNIEXPORT jstring JNICALL Java_lbtrace_jniregister_MainActivity_stringFromJNI( JNIEnv* env, jobject obj, jobject test_obj) { std::string hello = "Hello from C++"; // Java TestObject Mirror address int32_t *test_obj_ptr = reinterpret_cast<int32_t *>( *(reinterpret_cast<int32_t *>(test_obj))); // According TestObject memory layout // Swap month field and day field in TestObject int32_t tmp = *(test_obj_ptr + 2); *(test_obj_ptr + 2) = *(test_obj_ptr + 3); *(test_obj_ptr + 3) = tmp; return env->NewStringUTF(hello.c_str()); }
參考
- ofollow,noindex">JNI Tips
- Java Native Interface Specification