Android加載so文件 源碼分析

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");

System.java

loadloadLibraray函數在/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());
    }

Runtime.java

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()進行加載加載。
clipboard.png操作系統

java_lang_Runtime.cc

文件位置: /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進行加載。<關鍵>指針

java_vm_ext.cc

位置:/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 {
  1. 利用dlopen()打開so文件,獲得函數的指針
  2. 利用dlsym()調用so文件中的JNI_OnLoad方法,開始so文件的執行。

clipboard.png

相關文章
相關標籤/搜索