Android系統中使用ndk進行編程,有不少的好處(Java的跨平臺特性致使其本地交互的能力不夠強大,一些和操做系統相關的特性Java沒法完成;代碼的保護:因爲apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大;能夠方便地使用C/C++開源庫;便於移植,用C/C++寫的庫能夠方便在其餘平臺上再次使用;提供程序在某些特定情形下的執行效率,可是並不能明顯提高Android程序的性能)。java
要使用ndk進行編程,在Java層就必需要對so進行加載。Java層加載so的函數有兩個:android
System.load(String pathName) System.loadLibraray(String libName)
兩個函數的區別就是load函數的參數是so文件的絕對地址。loadLibrary的參數是so的名稱,這個so文件必須放在apk的lib目錄下,並且so的名稱必須去掉前面的lib和後邊的「.so」。以下所示:編程
System.load("/data/local/tmp/libhello.so"); System.loadLibrary("hello");
load
和loadLibraray
函數在/android6.0/libcore/luni/src/main/java/java/lang/System.java
中:less
public static void load(String pathName) { Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader()); } /** * See {@link Runtime#loadLibrary}. */ public static void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); }
getRuntime()
函數用於獲取Runtime
的一個實例。函數
public static Runtime getRuntime() { return mRuntime; }
loadLibrary():
性能
public void loadLibrary(String nickname) { loadLibrary(nickname, VMStack.getCallingClassLoader()); } void loadLibrary(String libraryName, ClassLoader loader) { if (loader != null) { 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 : mLibPaths) { 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); }
loadLibrary()
函數主要進行了兩步操做。
第一步:獲取library的path:
根據ClassLoader
的不一樣,會有兩種不一樣的處理方法。
若是ClassLoader
非空,會利用ClassLoader的findLibrary()方法獲取library的path。
若是ClassLoader
爲空,會經過傳入的library name和System.mapLibraryName
得到真正的library name。例如傳入的是hello
,獲得的是libhello.so
,而後在mLibPaths查找`libhello.so’,最終肯定library的path。
第二步:調用doLoad()方法。this
第一步目前我不關心,不去深究。主要看doLoad的實現。spa
private String doLoad(String name, ClassLoader loader) { String ldLibraryPath = null; String dexPath = null; if (loader == null) { // We use the given library path for the boot class loader. This is the path // also used in loadLibraryName if loader is null. ldLibraryPath = System.getProperty("java.library.path"); } else if (loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; ldLibraryPath = 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, ldLibraryPath); } }
得到libbrary的路徑;
調用native函數nativeLoad()進行加載加載。操作系統
文件位置:
/android6.0.1_r66/art/runtime/native/java_lang_Runtime.cc
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPathJstr) { ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == nullptr) { return nullptr; } SetLdLibraryPath(env, javaLdLibraryPathJstr); std::string error_msg; { JavaVMExt* vm = Runtime::Current()->GetJavaVM(); bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg); if (success) { return nullptr; } } // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear(); return env->NewStringUTF(error_msg.c_str()); }
nativeLoad()
主要作了兩件事:
第一件事:利用SetLdLibraryPath()
將Java的library的path轉換成native的。
第二件事情:調用LoadNativeLibrary進行加載。<關鍵>指針
位置:/android6.0/art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) { ... const char* path_str = path.empty() ? nullptr : path.c_str(); void* handle = dlopen(path_str, RTLD_NOW); ... if (needs_native_bridge) { library->SetNeedsNativeBridge(); sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr); } else { sym = dlsym(handle, "JNI_OnLoad"); } if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } else {
dlopen()
打開so文件,獲得函數的指針dlsym()
調用so文件中的JNI_OnLoad方法,開始so文件的執行。