Android JNI概述

作者: lbtrace | 来源:发表于2018-12-01 17:02 被阅读5次

    本文基于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中的线程都是Linux线程,由Linux内核调度。通常通过Thread.start()启动线程,但是也可以使用其他方法启动线程,然后attach到JavaVM。比如,使用pthread_create()创建线程,调用AttachCurrentThread()或者AttachCurrentThreadAsDaemon()attach到JavaVM。注意,在attach之前,没有JNIEnv,无法使用JNI。

    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());
    }
    

    参考

    相关文章

      网友评论

        本文标题:Android JNI概述

        本文链接:https://www.haomeiwen.com/subject/rfdrcqtx.html