美文网首页程序员android 开发程序员
JNI源码分析 (并实现JNI动态注册)

JNI源码分析 (并实现JNI动态注册)

作者: AWeiLoveAndroid | 来源:发表于2017-09-26 23:48 被阅读529次

    本博客转载自网址:http://blog.csdn.net/urrjdg/article/details/78091094

    1. C/C++ 的 编译 和 链接

    c/c++ ========= 二进制文件
    对于C/C++ 一般分为两个阶段

    1. 编译

    xxx.c ——> windows .obj ; Linux .o –》 语法检查

    1. 链接

    .o —–> log.so .dll .exe

    举例: a.c a.h b.c b.h 
    a.c –>b.h(test方法)
    

    在编译阶段只会去找b.h有没有test方法, 而在链接的阶段,他会在b.o当中去找这个test方法
    如果没有test方法会 报 LinkErro错误。 而这个Link erro 错误一般是因为,我们在一个文件当中引入了一个.h文件,并且使用了这个文件当中的这个方法,而这个对应的.h文件对应的.o文件(中间文件)里面没有这个方法的实现体。

    2.编译器

    将这个C/C++编译链接生成二进制文件的这个过程是谁做的?

    是编译器!

    编译规则:

    Eclipse
        GUN编译器  ----> 编译规则 Android.mk     (log.so是android自带的)
    Android Studio
        LLVM编译器 ----> 编译规则 CMakeList.txt
    
    三段式编译器
    

    3. 使用android studio 创建一个工程

    勾上 android studio 会给我们提供一个 exceptiosns support 异常支持

    public class MainActivity extends AppCompatActivity {
    
      private static String TAG = "MainActivity";
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        //tv.setText(stringFromJNI());
        diff();
      }
    
    
      public void diff(){
        Log.d(TAG,"diff ");
        FileUtils.diff("a","b",2);
      }
    
    }
    

    javah 生成头文件

    public class FileUtils {
    
      public static native void diff(String path,String pattern_Path,int file_num);
    
      public static void javaDiff(String path,String pattern_Path,int file_num){}
    
      // Used to load the 'native-lib' library on application startup.
      static {
        System.loadLibrary("native-lib");
      }
    }
    

    头文件:

    #include "com_example_zeking_lsn9_FileUtils.h"
    
    #include <android/log.h>
    
    //int __android_log_print(int prio, const char *tag,  const char *fmt, ...)
    #define TAG "Zeking_JNI"
    // __VA_ARGS__  代表可以输入参数  %s  %d 之类的
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
    
    /*
     * Class:     com_example_zeking_lsn9_FileUtils
     * Method:    diff
     * Signature: (Ljava/lang/String;Ljava/lang/String;I)V
     */
    JNIEXPORT void JNICALL Java_com_example_zeking_lsn9_FileUtils_diff
    (JNIEnv *env, jclass clazz, jstring path, jstring pattern_Path, jint file_num){
    
        LOGI("JNI Begin....%s..","Zeking  Hello");
    }
    

    jvm是虚拟机内存 , C/C++是native内存 , 并且这个so库是放在apk的lib下面的

    那这个so库 ,系统是怎么找到的 ? System.loadLibrary是怎么来找到的? 并且系统是如何来区分(JVM是怎么来区分native 方法(diff)和 javaDiff方法)

    native关键字起到什么作用? loadLibrary做了什么?

    当我们调用javaDiff的时候会到Java虚拟机的内存当中来处理找这个方法,而加了native关键字的时候他就会去到C++的堆栈空间找这个C++的实现。 为什么native会这样,起了什么作用?

    先在看声明了native的方法和没有声明native方法之间的区别。

    使用 javap -s -p -v FileUtils.class 找到这两个方法,可以看到这两个方法的区别在于 flag ,native声明的方法 多了个 ACC_NATIVE 的flag。也就是说java在执行这个文件的时候 ,对于有ACC_NATIVE 的flag的方法,他就会去 native区间去找,如果没有ACC_NATIVE 这个flag 就在本地的虚拟机空间来找这个方法

    C:\Users\Zeking\Desktop\Lsn9\app\src\main\java\com\example\zeking\lsn9>javap -s -p -v FileUtils.class
    Classfile /C:/Users/Zeking/Desktop/Lsn9/app/src/main/java/com/example/zeking/lsn9/FileUtils.class
      Last modified 2017-9-2; size 469 bytes
      MD5 checksum 19201ed5479758e0dfffb63528653a65
      Compiled from "FileUtils.java"
    public class com.example.zeking.lsn9.FileUtils
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #5.#16         // java/lang/Object."<init>":()V
       #2 = String             #17            // native-lib
       #3 = Methodref          #18.#19        // java/lang/System.loadLibrary:(Ljava/lang/String;)V
       #4 = Class              #20            // com/example/zeking/lsn9/FileUtils
       #5 = Class              #21            // java/lang/Object
       #6 = Utf8               <init>
       #7 = Utf8               ()V
       #8 = Utf8               Code
       #9 = Utf8               LineNumberTable
      #10 = Utf8               diff
      #11 = Utf8               (Ljava/lang/String;Ljava/lang/String;I)V
      #12 = Utf8               javaDiff
      #13 = Utf8               <clinit>
      #14 = Utf8               SourceFile
      #15 = Utf8               FileUtils.java
      #16 = NameAndType        #6:#7          // "<init>":()V
      #17 = Utf8               native-lib
      #18 = Class              #22            // java/lang/System
      #19 = NameAndType        #23:#24        // loadLibrary:(Ljava/lang/String;)V
      #20 = Utf8               com/example/zeking/lsn9/FileUtils
      #21 = Utf8               java/lang/Object
      #22 = Utf8               java/lang/System
      #23 = Utf8               loadLibrary
      #24 = Utf8               (Ljava/lang/String;)V
    {
      public com.example.zeking.lsn9.FileUtils();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 7: 0
    
      public static native void diff(java.lang.String, java.lang.String, int);
        descriptor: (Ljava/lang/String;Ljava/lang/String;I)V 
        flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE  // 这边多了个 ACC_NATIVE 代表是native
    
      public static void javaDiff(java.lang.String, java.lang.String, int);
        descriptor: (Ljava/lang/String;Ljava/lang/String;I)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=0, locals=3, args_size=3
             0: return
          LineNumberTable:
            line 11: 0
    
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: ldc           #2                  // String native-lib
             2: invokestatic  #3                  // Method java/lang/System.loadLibrary:(Ljava/lang/String;)V
             5: return
          LineNumberTable:
            line 15: 0
            line 16: 5
    }
    SourceFile: "FileUtils.java"
    

    4. System.loadLibrary 找到so库文件 分析

    native的方法栈为什么能被jvm调用到?从System.loadLibrary 入手

    System.loadLibrary("native-lib");
    

    System.java

    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }
    

    Runtime.java

    synchronized void loadLibrary0(ClassLoader loader, String libname) {
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        if (loader != null) {
            // 点进去发现是return null;找到so库的全路径
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
    
        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : getLibPaths()) {
            String candidate = directory + filename;
            candidates.add(candidate);
    
            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }
    
        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }
    

    接着看:

    String filename = loader.findLibrary(libraryName);
    点进去 发现是  return null;
    

    ClassLoader.java

    protected String findLibrary(String libname) {
        return null;
    }
    

    所以可以想到 应该是 ClassLoader 的实现类去实现了这个 findLibrary方法。 怎么找是哪个实现类 实现的呢?

    Log.i(TAG,this.getClassLoader().toString());
    
    dalvik.system.PathClassLoader[DexPathList[
        [zip file "/data/app/com.example.zeking.lsn9-1/base.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_dependencies_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_0_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_1_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_2_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_3_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_4_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_5_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_6_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_7_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_8_apk.apk", 
            zip file "/data/app/com.example.zeking.lsn9-1/split_lib_slice_9_apk.apk"],
            nativeLibraryDirectories=[/data/app/com.example.zeking.lsn9-1/lib/arm64, 
                /data/app/com.example.zeking.lsn9-1/base.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_dependencies_apk.apk!/lib/arm64-v8a,
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_0_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_1_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_2_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_3_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_4_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_5_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_6_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_7_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_8_apk.apk!/lib/arm64-v8a, 
                /data/app/com.example.zeking.lsn9-1/split_lib_slice_9_apk.apk!/lib/arm64-v8a,
                /vendor/lib64, 
                /system/lib64
            ]
        ]
    ]
    

    从上面可以看出是 PathClassLoader

    PathClassLoader .java 这里面没有 findLibrary 继续进到 BaseDexClassLoader

    public class PathClassLoader extends BaseDexClassLoader {
        ......
    }
    

    BaseDexClassLoader .java

    private final DexPathList pathList;
    /**
    * Constructs an instance.
    *
    * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
    * defaults to {@code ":"} on Android
    * @param optimizedDirectory directory where optimized dex files
    * should be written; may be {@code null}
    * @param librarySearchPath the list of directories containing native
    * libraries, delimited by {@code File.pathSeparator}; may be
    * {@code null}
    * @param parent the parent class loader
    */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }
    
    
    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
    

    DexPathList .java

    public String findLibrary(String libraryName) {
      
        String fileName = System.mapLibraryName(libraryName);
    
        for (Element element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);
    
            if (path != null) {
                return path;
            }
        }
    
        return null;
    }
    

    首先我们先来看

    DexPathList .java 中的 String fileName = System.mapLibraryName(libraryName);

    System.java 看注释可以看出 ,是 根据你的平台来找你的 so库

    /**
     * Maps a library name into a platform-specific string representing
     * a native library.
     *
     * @param      libname the name of the library.
     * @return     a platform-dependent native library name.
     * @exception  NullPointerException if <code>libname</code> is
     *             <code>null</code>
     * @see        java.lang.System#loadLibrary(java.lang.String)
     * @see        java.lang.ClassLoader#findLibrary(java.lang.String)
     * @since      1.2
     */
    public static native String mapLibraryName(String libname);
    

    再继续看 for (Element element : nativeLibraryPathElements) {
    DexPathList .java 可以看到 nativeLibraryPathElements 是在 DexPathList的构造函数里面初始化的

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
    
        ......
        // 找so库是从两个地方来找,
        // 1.在BaseDexClassLoader初始化的时候传入的目录 这个目录是 librarySearchPath,这个就是应用apk下面的解压的lib目录下
        // 2. 在系统的环境变量里面,System.getProperty("java.library.path"):
        // 这个目录通过Log.i(TAG,System.getProperty("java.library.path"));
        // 打印 出来是 /vendor/lib64:/system/lib64 或者  /vendor/lib:/system/lib
        // dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.zeking.lsn9-1.apk"],
        //              nativeLibraryDirectories=[/data/app-lib/com.example.zeking.lsn9-1, /system/lib]]]
        // /data/app-lib/com.example.zeking.lsn9-1, 
        // /system/lib
    
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        // 这个是系统里面 java.library.path 
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        // 就是在这边进行初始化的
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);
    
          ......
    }
    

    5. System.loadLibrary 加载so库文件 分析

    分析下 System.loadLibrary 是怎么加载so库的

    现在回到Runtime.java 的 loadLibrary0 方法 找到他的doLoad 方法

    synchronized void loadLibrary0(ClassLoader loader, String libname) {
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        if (loader != null) {
            String filename = loader.findLibrary(libraryName); // 找到so库的全路径
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
    
        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : getLibPaths()) {
            String candidate = directory + filename;
            candidates.add(candidate);
    
            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }
    
        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }
    

    doLoad 方法

    private String doLoad(String name, ClassLoader loader) {
        // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
        // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
    
        // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
        // libraries with no dependencies just fine, but an app that has multiple libraries that
        // depend on each other needed to load them in most-dependent-first order.
    
        // We added API to Android's dynamic linker so we can update the library path used for
        // the currently-running process. We pull the desired path out of the ClassLoader here
        // and pass it to nativeLoad so that it can call the private dynamic linker API.
    
        // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
        // beginning because multiple apks can run in the same process and third party code can
        // use its own BaseDexClassLoader.
    
        // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
        // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
    
        // So, find out what the native library search path is for the ClassLoader in question...
        String librarySearchPath = null;
        if (loader != null && loader instanceof BaseDexClassLoader) {
            BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
            librarySearchPath = dexClassLoader.getLdLibraryPath();
        }
        // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
        // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
        // internal natives.
        synchronized (this) {
            // 这一边
            return nativeLoad(name, loader, librarySearchPath);
        }
    }
    // 这一边
     // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
    private static native String nativeLoad(String filename, ClassLoader loader,
                                            String librarySearchPath);
    

    nativeLoad 方法 要去 runtime.c(java_lang_Runtime.cc)android-7.1.0_r1.7z\android-7.1.0_r1\libcore\ojluni\src\main\native\runtime.c 里面去找,没有的可以去下载安卓底层源码

    以下是 Runtime.c的源码

    #include "jni.h"
    #include "jni_util.h"
    #include "jvm.h"
    
    #include "JNIHelp.h"
    
    #define NATIVE_METHOD(className, functionName, signature) \
    { #functionName, signature, (void*)(className ## _ ## functionName) }
    
    JNIEXPORT jlong JNICALL
    Runtime_freeMemory(JNIEnv *env, jobject this) {
        return JVM_FreeMemory();
    }
    
    JNIEXPORT jlong JNICALL
    Runtime_totalMemory(JNIEnv *env, jobject this) {
        return JVM_TotalMemory();
    }
    
    JNIEXPORT jlong JNICALL
    Runtime_maxMemory(JNIEnv *env, jobject this) {
        return JVM_MaxMemory();
    }
    
    JNIEXPORT void JNICALL
    Runtime_gc(JNIEnv *env, jobject this) {
        JVM_GC();
    }
    
    JNIEXPORT void JNICALL
    Runtime_nativeExit(JNIEnv *env, jclass this, jint status) {
        JVM_Exit(status);
    }
    
    // 这个就是 nativeLoad 方法 的实现
    JNIEXPORT jstring JNICALL
    Runtime_nativeLoad(JNIEnv *env, jclass ignored, jstring javaFilename,
                       jobject javaLoader, jstring javaLibrarySearchPath) {
        // JVM_NativeLoad 方法 在 OpenjdkJvm.cc 中
        return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
    }
    
    static JNINativeMethod gMethods[] = {
            // 使用了一个 NATIVE_METHOD 的 宏替换 ,这个宏替换在这个类的顶部
            NATIVE_METHOD(Runtime, freeMemory, "!()J"),
            NATIVE_METHOD(Runtime, totalMemory, "!()J"),
            NATIVE_METHOD(Runtime, maxMemory, "!()J"),
            NATIVE_METHOD(Runtime, gc, "()V"),
            NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
            NATIVE_METHOD(Runtime, nativeLoad,
                          "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)"
                                  "Ljava/lang/String;"),
    };
    
    void register_java_lang_Runtime(JNIEnv *env) {
        jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
    }
    

    下面就是 OpenjdkJvm.cc

    JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                   jstring javaFilename,
                                   jobject javaLoader,
                                   jstring javaLibrarySearchPath) {
    ScopedUtfChars filename(env, javaFilename);
    if (filename.c_str() == NULL) {
      return NULL;
    }
    
    std::string error_msg;
    {
      // 这边 有一个 JavaVMExt  , 这个方法的参数有一个 JNIEnv 。
      // 那好,JavaVM* 和 JNIEnv 有什么区别呢?
      // JavaVM* : 一个android应用的进程,有且仅有一个javaVm
      // JNIEnv :每个java线程都对应一个env的环境变量
      // 虚拟机里面jvm 是怎么找到具体的so库的堆栈的?,他调用了 JavaVM的loadNativeLibrary 方法里面,
      // 创建了一个结构体(这个结构体,包一个的指针,这个指针放我们真实加载完操作的文件地址),
      // 在这个结构体里面将我传进来的动态库()filename.c_str())加到结构体里面,然后保存到VM里面,
      // 那么对于我的android进程其他的地方,我只要拿到这个VM,就能找到这个结构体,通过这个结构体,
      // 就能找到这个so库里面的方法栈和引用内存
      art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
      // vm->LoadNativeLibrary 方法 在  java_vm_ext.cc 
      bool success = vm->LoadNativeLibrary(env,
                                           filename.c_str(),
                                           javaLoader,
                                           javaLibrarySearchPath,
                                           &error_msg);
      if (success) {
        return nullptr;
      }
    }
    

    java_vm_ext.cc

    bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                      const std::string& path,
                                      jobject class_loader,
                                      jstring library_path,
                                      std::string* error_msg) {
      error_msg->clear();
    
      // See if we've already loaded this library.  If we have, and the class loader
      // matches, return successfully without doing anything.
      // TODO: for better results we should canonicalize the pathname (or even compare
      // inodes). This implementation is fine if everybody is using System.loadLibrary.
      SharedLibrary* library;    // 创建SharedLibrary对象,SharedLibrary 是一个类对象
      Thread* self = Thread::Current();
      {
        // TODO: move the locking (and more of this logic) into Libraries.
        MutexLock mu(self, *Locks::jni_libraries_lock_);
        library = libraries_->Get(path);// 实例化动态库library对象,这个path就是 so库的绝对路径,这个对象还没有赋值
      }
      void* class_loader_allocator = nullptr;
      {
        ScopedObjectAccess soa(env);
        // As the incoming class loader is reachable/alive during the call of this function,
        // it's okay to decode it without worrying about unexpectedly marking it alive.
        mirror::ClassLoader* loader = soa.Decode<mirror::ClassLoader*>(class_loader);
    
        ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); // 获取ClassLinker对象
        if (class_linker->IsBootClassLoader(soa, loader)) {
          loader = nullptr;
          class_loader = nullptr;
        }
    
        class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader);
        CHECK(class_loader_allocator != nullptr);
      }
      if (library != nullptr) {
        // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
        if (library->GetClassLoaderAllocator() != class_loader_allocator) {
          // The library will be associated with class_loader. The JNI
          // spec says we can't load the same library into more than one
          // class loader.
          StringAppendF(error_msg, "Shared library \"%s\" already opened by "
              "ClassLoader %p; can't open in ClassLoader %p",
              path.c_str(), library->GetClassLoader(), class_loader);
          LOG(WARNING) << error_msg;
          return false;
        }
        VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
                  << " ClassLoader " << class_loader << "]";
        if (!library->CheckOnLoadResult()) {
          StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
              "to load \"%s\"", path.c_str());
          return false;
        }
        return true;
      }
      // Open the shared library.  Because we're using a full path, the system
      // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
      // resolve this library's dependencies though.)
    
      // Failures here are expected when java.library.path has several entries
      // and we have to hunt for the lib.
    
      // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
      // class unloading. Libraries will only be unloaded when the reference count (incremented by
      // dlopen) becomes zero from dlclose.
    
      Locks::mutator_lock_->AssertNotHeld(self);
      const char* path_str = path.empty() ? nullptr : path.c_str();
      // OpenNativeLibrary 是android 打开 natvie Library 并且返回 一个handle,这个handle赋值到了
      // 这个handl 就是android 真实加载so库完之后返回的一个指针,这个handle指针放在SharedLibrary的对象library 中,
      // 而library 放到了 libraries_ 这个智能指针中,
      void* handle = android::OpenNativeLibrary(env,
                                                runtime_->GetTargetSdkVersion(),
                                                path_str,
                                                class_loader,
                                                library_path);// 打开Native   库拿到一个handle 句柄
    
      bool needs_native_bridge = false;
      if (handle == nullptr) {
        if (android::NativeBridgeIsSupported(path_str)) {
          handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
          needs_native_bridge = true;
        }
      }
    
     if (env->ExceptionCheck() == JNI_TRUE) {
        LOG(ERROR) << "Unexpected exception:";
        env->ExceptionDescribe();
        env->ExceptionClear();
      }
      // Create a new entry.
      // TODO: move the locking (and more of this logic) into Libraries.
      bool created_library = false;
      {
        // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
        // 这里用到一个 C++ 的智能指针 , 
        std::unique_ptr<SharedLibrary> new_library(
            // new SharedLibrary 的时候 传入了 handle 指针
            new SharedLibrary(env, self, path, handle, class_loader, class_loader_allocator));
        MutexLock mu(self, *Locks::jni_libraries_lock_);
        library = libraries_->Get(path);
        if (library == nullptr) {  // We won race to get libraries_lock.
          library = new_library.release();
          libraries_->Put(path, library);// 将我们指定的库加载进来,保存在library对象中
          created_library = true;
        }
      }
      if (!created_library) {
        LOG(INFO) << "WOW: we lost a race to add shared library: "
            << "\"" << path << "\" ClassLoader=" << class_loader;
        return library->CheckOnLoadResult();
      }
      VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
    
      bool was_successful = false;
      void* sym;
      if (needs_native_bridge) {
        library->SetNeedsNativeBridge();
      }
          sym = library->FindSymbol("JNI_OnLoad", nullptr);  // 拿到JNI_OnLoad方法
      if (sym == nullptr) {
        VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
        was_successful = true;
      } else {
        // Call JNI_OnLoad.  We have to override the current class
        // loader, which will always be "null" since the stuff at the
        // top of the stack is around Runtime.loadLibrary().  (See
        // the comments in the JNI FindClass function.)
        ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
        self->SetClassLoaderOverride(class_loader);
    
        VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
        typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
        JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
        int version = (*jni_on_load)(this, nullptr);
    
        if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
          fault_manager.EnsureArtActionInFrontOfSignalChain();
        }
    
        self->SetClassLoaderOverride(old_class_loader.get());
    
        if (version == JNI_ERR) {
          StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
        } else if (IsBadJniVersion(version)) {
          StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
                        path.c_str(), version);
          // It's unwise to call dlclose() here, but we can mark it
          // as bad and ensure that future load attempts will fail.
          // We don't know how far JNI_OnLoad got, so there could
          // be some partially-initialized stuff accessible through
          // newly-registered native method calls.  We could try to
          // unregister them, but that doesn't seem worthwhile.
        } else {
          // 加载成功的标志
          was_successful = true;
        }
        VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
                  << " from JNI_OnLoad in \"" << path << "\"]";
      }
    
      library->SetResult(was_successful);
      return was_successful;
    }
    
    
    static bool IsBadJniVersion(int version) {
      // We don't support JNI_VERSION_1_1. These are the only other valid versions.
      // 当不等于JNI_VERSION_1_2 或 JNI_VERSION_1_4  或 JNI_VERSION_1_6 就是个错误的version
      return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
    }
    

    Java_vm_ext.h

    // libraries_   是JVM 中的一个静态变量,有多少个so库,就会保存多少个SharedLibrary对象
    std::unique_ptr<Libraries> libraries_  智能指针
    

    关键是与JVM的联系:android进程,有且只有一个JavaVMExt*指针对象,当我们在LoadNativeLibrary的时候,new了一个SharedLibrary的对象指针,而SharedLibrary保存了handle句柄,然后在找文件方法的时候,都是通过对象里面的handle句柄来进行操作的,library有一个FindSymbol 来找方法,找到JNI_OnLoad方法去做具体的调用,这就是JNI设计的流程

    6. 用一个完整的例子来查看android是怎么实现动态注册的(MediaPlayer)

    frameworks\base\media\java\android\media\MediaPlayer.java

    ...
    static {
            System.loadLibrary("media_jni");
            native_init();
        }
    ...
    private static native final void native_init();
    private native final void native_setup(Object mediaplayer_this); // java函数名
    private native final void native_finalize();
    ...
    

    它的具体实现在 MediaPlayer.cpp里面
    它的JNI的具体实现在 ./frameworks/base/media/jni/android_media_MediaPlayer.cpp

    static JNINativeMethod gMethods[] = {
        ······
        {"native_init",         "()V",                              (void *)android_media_MediaPlayer_native_init},
        // 这边是 native_setup : 第一个 是java函数名,第二个是签名,第三个是 jni具体实现方法的指针
        {"native_setup",        "(Ljava/lang/Object;)V",            (void *)android_media_MediaPlayer_native_setup},
        {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
        ······
    };
    
    // jni具体实现方法的指针
    static void
    android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
    {
        ALOGV("native_setup");
        sp<MediaPlayer> mp = new MediaPlayer();
        if (mp == NULL) {
            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
            return;
        }
    
        // create new listener and give it to MediaPlayer
        sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
        mp->setListener(listener);
    
        // Stow our new C++ MediaPlayer in an opaque field in the Java object.
        setMediaPlayer(env, thiz, mp);
    }
    
    // This function only registers the native methods
    static int register_android_media_MediaPlayer(JNIEnv *env)
    {
        // gMethods 在这边被调用,系统可以拿到AndroidRuntime:,我们拿不到,只能分析,他注册的时候做了什么事情,
        // 分析: env ,"android/media/MediaPlayer" 是MediaPlayer.java的包名+类名
        // gMethods
        // NELEM(gMethods)算这个结构体数组的占多少个字节,将这个大小放进去(是个宏定义,便于复用)
        // # define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
        // registerNativeMethods 具体实现在AndroidRuntime.cpp 具体见下一段代码
        return AndroidRuntime::registerNativeMethods(env,
                    "android/media/MediaPlayer", gMethods, NELEM(gMethods));
    }
    
    //  这边重写了jni.h声明的 JNI_OnLoad方法,在JNI_OnLoad中进行注册(register_android_media_MediaPlayer),
    //  在注册过程中,声明了一个gMethods的结构体数组,这里面写好了方法映射。而JNI_OnLoad的调用处,就是System.loadLibrary 的时候会走到
    //  这里,然后进行动态注册
    jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            ALOGE("ERROR: GetEnv failed\n");
            goto bail;
        }
        assert(env != NULL);
    
        ...
        // register_android_media_MediaPlayer 在这边被调用
        if (register_android_media_MediaPlayer(env) < 0) {
            ALOGE("ERROR: MediaPlayer native registration failed\n");
            goto bail;
        }
        ...
    
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    
    bail:
        return result;
    }
    

    /frameworks/base/core/jni/AndroidRuntime.cpp

    /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
        const char* className, const JNINativeMethod* gMethods, int numMethods)
    {
        // jniRegisterNativeMethods 是在JNIHelp.cpp 里面实现的
        return jniRegisterNativeMethods(env, className, gMethods, numMethods);
    }
    

    /external/conscrypt/src/compat/native/JNIHelp.cpp

    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);
        // 这边是重点 ,findClass 的实现是 env->FindClass(className)
        scoped_local_ref<jclass> c(env, findClass(env, className));
        if (c.get() == NULL) {
           char* msg;
           asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
           e->FatalError(msg);
        }
        // env的注册 
        if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
           char* msg;
          asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
           e->FatalError(msg);
        }
    
        return 0;
    }
    

    7. JNI 动态注册

    根据以上的分析进行实现:

    java代码:

    public class FileUtils {
    
        public static native void diff(String path,String pattern_Path,int file_num);
    
        public static void javaDiff(String path,String pattern_Path,int file_num){}
    
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    }
    

    C代码:

    #include "com_example_zeking_FileUtils.h"
    
    #include <android/log.h>
    #include <assert.h>
    
    //int __android_log_print(int prio, const char* tag, const char* fmt, ...)
    #define TAG "Zeking_JNI"
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
    # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
    /*
     * Class:     com_example_zekign_FileUtils
     * Method:    diff
     * Signature: (Ljava/lang/String;Ljava/lang/String;I)V
     */
    JNIEXPORT void JNICALL native_diff
            (JNIEnv *env, jclass clazz, jstring path, jstring pattern_Path, jint file_num)
    {
    
        LOGI("JNI begin 动态注册的方法 ");
    
    }
    
    static const JNINativeMethod gMethods[] = {
            {
                    "diff","(Ljava/lang/String;Ljava/lang/String;I)V",(void*)native_diff
            }
    };
    
    static int registerNatives(JNIEnv* engv)
    {
        LOGI("registerNatives begin");
        jclass  clazz;
        clazz = (*engv) -> FindClass(engv, "com/example/zeking/FileUtils");
    
        if (clazz == NULL) {
            LOGI("clazz is null");
            return JNI_FALSE;
        }
    
        if ((*engv) ->RegisterNatives(engv, clazz, gMethods, NELEM(gMethods)) < 0) {
            LOGI("RegisterNatives error");
            return JNI_FALSE;
        }
    
        return JNI_TRUE;
    }
    
    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
    
        LOGI("jni_OnLoad begin");
    
        JNIEnv* env = NULL;
        jint result = -1;
    
        if ((*vm)->GetEnv(vm,(void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            LOGI("ERROR: GetEnv failed\n");
            return -1;
        }
        assert(env != NULL);
    
        registerNatives(env);
    
        return JNI_VERSION_1_4;
    }
    

    静态注册:
    每个class都需要使用javah生成一个头文件,并且生成的名字很长书写不便;初次调用时需要依据名字搜索对应的JNI层函数来建立关联关系,会影响运行效率
    用javah 生成头文件方便简单
    1.javah生一个头文件 操作简单
    2.名字很长 书写不方便
    3.初次调用的使用,需要依据名字搜索对应的FindSymbol(具体看Runctime.c) 来找到对应的方法,如果方法数较多的时候,效率不高

    动态注册:
    第一次调用效率高
    使用一种数据结构JNINativeMethod来记录java native函数和JNI函数的对应关系
    移植方便,便于维护(一个java文件中有多个native方法,只要修改下gMethods 的映射关系)

    相关文章

      网友评论

        本文标题: JNI源码分析 (并实现JNI动态注册)

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