JNI源码分析 (并实现JNI动态注册)
2017-09-26 10:56
411 查看
原文出自:http://blog.csdn.net/urrjdg/article/details/78091094
1 . C/C++ 的 编译 和 链接
2.编译器
将这个C/C++编译链接生成二进制文件的这个过程是谁做的?
是编译器
编译规则:
Eclipse
GUN编译器 —-> 编译规则 Android.mk (log.so是android自带的)
Android Studio
LLVM编译器 —-> 编译规则 CMakeList.txt
三段式编译器
3 . 使用android studio 创建一个工程
勾上 android studio 会给我们提供一个 exceptiosns support 异常支持
javah 生成头文件
public class FileUtils {
}
#include “com_example_zeking_lsn9_FileUtils.h”
由上产生的疑问:
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 就在本地的虚拟机空间来找这个方法
————可以通过使用javap -s -p -v FileUtils.class命令查看flag标志位————————
C:\Users\Zeking\Desktop\Lsn9\app\src\main\java\com\example\zeking\lsn9>javap -s -p -v FileUtils.class
SourceFile: “FileUtils.java”
4 . System.loadLibrary 找到so库文件 分析
native的方法栈为什么能被jvm调用到?从System.loadLibrary 入手
Runtime.java
String filename = loader.findLibrary(libraryName);
点进去 发现是 return null;
ClassLoader.java
所以可以想到 应该是 ClassLoader的实现类去实现了这个 findLibrary方法。
怎么找是哪个实现类 实现的呢?
Log.i(TAG,this.getClassLoader().toString());
从上面可以看出是 PathClassLoader
PathClassLoader .java 这里面没有 findLibrary 继续进到 BaseDexClassLoader
BaseDexClassLoader .java
DexPathList .java
首先我们先来看
DexPathList .java 中的 String fileName = System.mapLibraryName(libraryName);
System.java 看注释可以看出 ,是 根据你的平台来找你的 so库
再继续看
for (Element element : nativeLibraryPathElements) {
从DexPathList .java 可以看到 nativeLibraryPathElements 是在 DexPathList的构造函数里面初始化的
5 . System.loadLibrary
加载so库文件 分析
分析下他是怎么加载so库的
现在回到Runtime.java 的 loadLibrary0 方法 找到他的doLoad 方法
doLoad 方法
nativeLoad方法要去runtime.c(java_lang_Runtime.cc)
runtime.c 这个类 在 7.0 里面是没有的,4.4 里面才有,所以要去 4.4 里面拷贝到7.0 源码里面
将4.4 源码 中的 dalvik/vm 拷贝到 7.0 的dalvik目录下
以下是 Runtime.c的源码
下面就是 OpenjdkJvm.cc
java_vm_ext.cc
Java_vm_ext.h
6 . 用一个完整的例子来查看android是怎么实现动态注册的(MediaPlayer)
系统源码:frameworks\base\media\java\android\media\MediaPlayer.java
它的具体实现在 MediaPlayer.cpp里面
它的JNI的具体实现在 ./frameworks/base/media/jni/android_media_MediaPlayer.cpp
/frameworks/base/core/jni/AndroidRuntime.cpp
/external/conscrypt/src/compat/native/JNIHelp.cpp
7 . JNI 动态注册
根据以上的分析进行实现
——–JNI实现—————————-
最后总结两者的区别:
1 . C/C++ 的 编译 和 链接
c/c++ ========= 二进制文件 对于C/C++ 一般分为两个阶段 1. 编译 xxx.c ——> windows .obj ; Linux .o –》 语法检查 链接 .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 就在本地的虚拟机空间来找这个方法
————可以通过使用javap -s -p -v FileUtils.class命令查看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 4000 // 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< 18154 /span>.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库文件 分析
分析下他是怎么加载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_r\libcore\ojluni\src\main\native\runtime.c
runtime.c 这个类 在 7.0 里面是没有的,4.4 里面才有,所以要去 4.4 里面拷贝到7.0 源码里面
将4.4 源码 中的 dalvik/vm 拷贝到 7.0 的dalvik目录下
以下是 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 动态注册
根据以上的分析进行实现
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"); }}
——–JNI实现—————————-
#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 生成头文件方便简单 javah生一个头文件 操作简单 名字很长 书写不方便 初次调用的使用,需要依据名字搜索对应的FindSymbol(具体看Runctime.c) 来找到对应的方法,如果方法数较多的时候,效率不高 动态注册: 第一次调用效率高 使用一种数据结构JNINativeMethod来记录java native函数和JNI函数的对应关系 移植方便,便于维护(一个java文件中有多个native方法,只要修改下gMethods 的映射关系)
相关文章推荐
- JNI源码分析(并实现JNI动态注册)
- JNI源码分析 (并实现JNI动态注册)
- JNI源码分析(并实现JNI动态注册)
- android 动态注册JNI函数过程源码分析
- android 动态注册JNI函数过程源码分析
- (七)JNI 源码分析、动态注册
- JNI注册调用源码分析完整过程-安卓4.4
- OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机动态迁移源码分析
- OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机动态迁移源码分析
- JavaSE JNI 动态注册本地方法(c语言实现native层)
- Android源码解析之动态注册广播接收器的过程分析
- JAVA 动态代理(proxy)的实现和源码分析
- OpenStack基于Libvirt的虚拟化平台调度实现----Nova虚拟机动态迁移源码分析
- MTK平台CPU/GPU动态调频的实现之PerfService的源码分析
- Mybatis3源码分析(21)-Mapper实现-动态代理
- Android Apk资源加载机制源码分析以及资源动态加载实现系列文章
- JNI实现源码分析【二 数据结构】
- spring实现动态切换、添加数据源及源码分析
- Dubbo源码分析(六):Dubbo内核实现之动态编译
- JNI通过动态注册实现native函数