JNI原始碼分析(並實現JNI動態註冊)
本部落格轉載自網址:http://blog.csdn.net/urrjdg/article/details/78091094
1. C/C++ 的 編譯 和 連結
c/c++ ========= 二進位制檔案
對於C/C++ 一般分為兩個階段
- 編譯
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 就在本地的虛擬機器空間來找這個方法
C:UsersekingDesktopLsn9appsrcmainjavacomexamplezekinglsn9>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.7zandroid-7.1.0_r1libcoreojlunisrcmain
ative
untime.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)
frameworksasemediajavaandroidmediaMediaPlayer.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
");
goto bail;
}
assert(env != NULL);
...
// register_android_media_MediaPlayer 在這邊被呼叫
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed
");
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
");
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呼叫c動態連結庫函式程式碼實踐函式
- Android JNI原理分析Android
- Android JNI 程式碼自動生成Android
- JNI實現圖片壓縮
- jni
- containerd 原始碼分析:啟動註冊流程AI原始碼
- java呼叫c++動態庫之jni呼叫JavaC++
- 原始碼分析 — Activity的清單註冊校驗及動態注入原始碼
- cmake + JNI
- 使用 Rust 語言編寫 Java JNI 實現RustJava
- 動態註冊和靜態註冊
- JNI:Java程式碼呼叫原生程式碼Java
- JNI-C
- nacos註冊中心原始碼流程分析原始碼
- Android:JNI與NDK(二)交叉編譯與動態庫,靜態庫Android編譯
- 鴻蒙手機版JNI實戰(JNI開發、SO庫生成、SO庫使用)鴻蒙
- JDK動態代理實現原理詳解(原始碼分析)JDK原始碼
- @angular/router 原始碼分析之註冊路由Angular原始碼路由
- Netty原始碼分析--Channel註冊(中)(六)Netty原始碼
- Netty原始碼分析--Channel註冊(上)(五)Netty原始碼
- JNI的語法
- java JNI簡介Java
- 安卓驅動、HAL、JNI與java安卓Java
- oracle的靜態註冊和動態註冊Oracle
- 實現Oracle非1521標準埠動態註冊Oracle
- Android中的JNI入門實戰Android
- 【Java】NIO中Channel的註冊原始碼分析Java原始碼
- Nacos(一)原始碼分析Nacos註冊示例流程原始碼
- 註冊中心 Eureka 原始碼解析 —— 應用例項註冊發現(一)之註冊原始碼
- Android JNI 之 Bitmap 操作Android
- JNI 學習總結
- JNI 檔案遍歷
- C++庫封裝JNI介面——實現java呼叫c++C++封裝Java
- Android Studio NDK:二、JNI 返回JAVA 實體AndroidJava
- ShadowDefender 註冊碼 分析
- Android JNI實現Java與C/C++互相呼叫,以及so庫的生成和呼叫(JNI方式呼叫美圖秀秀so)AndroidJavaC++
- Spring Cloud Nacos實現動態配置載入的原始碼分析SpringCloud原始碼
- JNI-從jvm原始碼分析Thread.interrupt的系統級別執行緒打斷原理JVM原始碼thread執行緒