溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中怎么利用Robolectric加載運行本地So動態(tài)庫

發(fā)布時間:2021-06-29 14:31:20 來源:億速云 閱讀:216 作者:Leah 欄目:移動開發(fā)

這篇文章將為大家詳細講解有關Android中怎么利用Robolectric加載運行本地So動態(tài)庫,文章內(nèi)容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

前言

Robolectric 是 Android 的單元測試框架,運行無需 Android 真機環(huán)境直接運行在 JVM 之上,所以在 test case  運行速度效率上有了很大提升,接近于 Java JUnit test(JUnit test > Robolectric ?  androidTest)。不過框架本身并不支持 so 本地庫的加載使用,加載時會直接報錯,因為實際上運行環(huán)境是電腦機器,而我們打出的 so  文件是給手機上用的所以當然會報錯。雖然在 GitHub 上很多人問過關于使用 so  的問題但基本都建議說不要在單元測試中去加載本地庫,這在原則上是要這么做,但可能有些項目中做起來就有些困難了,比如在代碼結構不夠好、依賴耦合較大或者本身就對 so  庫依賴很大的情況下。所以下面說說在項目中 Robolectric 要怎么解決需要加載運行本地 so 庫這個問題。

動態(tài)庫

動態(tài)庫又稱動態(tài)鏈接庫(Dynamic-link library 縮寫 DLL),是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫,DLL  不是可執(zhí)行文件。動態(tài)鏈接提供了一種方法,使進程可以調用不屬于其可執(zhí)行代碼的函數(shù)。函數(shù)的可執(zhí)行代碼位于一個 DLL 中,該 DLL  包含一個或多個已被編譯、鏈接并與使用它們的進程分開存儲的函數(shù)。DLL 還有助于共享數(shù)據(jù)和資源。多個應用程序可同時訪問內(nèi)存中單個DLL 副本的內(nèi)容。DLL  是一個包含可由多個程序同時使用的代碼和數(shù)據(jù)的庫。Windows下動態(tài)庫為 .dll 后綴(一般為 PE 格式),在 Linux 在為 .so 后綴(一般為  ELF 格式),macOS下為 .dylib 后綴(一般為 Mach-O 格式)。由于 CPU  架構和動態(tài)庫文件格式的不同因而在不同平臺下不能通用。其它細節(jié)的東西就不展開了因為也不會 :-)

而 Android 本身是 Linux 系統(tǒng),所以用的動態(tài)庫也是 .so 的文件,因而運行與 JVM 的 Robolectric  是不能直接加載使用的(Linux 某些情況下可用,下面提到)。

Robolectric 中使用動態(tài)庫

我們知道動態(tài)庫一般都是打給特定平臺、特定 CPU 架構用的,所以要解決在 Robolectric 下加載運行 so 動態(tài)庫的問題的思路就是在不同  Robolectric 運行平臺下去處理加載不同的動態(tài)庫,所以你要在 Ronbolectriv 中使用的 so 動態(tài)庫***要有源碼不然在 macOS 和  Windows 下就不就好處理了。

Note: 注意動態(tài)庫名稱已 lib 開頭。

Linux 下 Robolectric 中使用動態(tài)庫

Android 與 Linux 同氣連枝,所以底層的東西很多是通用的,動態(tài)庫也一樣。我們 Android 使用 so 時一般也要對不同 CPU  架構的手機下使用不同的 so 文件,譬如:armeabi-v7a、mips、x86。而我們使用的 LInux 發(fā)行版一般都是 64  位的,所以原理上我們使用x86-64 的動態(tài)庫是可以的,不過可能需要處理依賴庫問題如果你的本地代碼里有 include 其它依賴的話。如果沒加進來  Robolectric 運行就會報如下的錯誤:

java.lang.UnsatisfiedLinkError: xxx/xxx.so xxx 動態(tài)庫找不到。

xxx.so 就是你所使用 so 的依賴,比如把新浪微博 SDK 的 x86-64 的 libweibosdkcore.so 加載進來的話就會報  liblog.so 等找不到,因為 libweibosdkcore 中有對 Android liblog 等 so 庫的依賴。那這個問題怎么解決呢。我們想想打包  so 庫時用的是 ndk,需要使用 ndk-bundle 工具,我們想想,跟編譯 apk 差不多,apk 打包需要 sdk 工具,compileSdk  里就是我們編譯的依賴,里面有android.jar。所以我們可以到 ndk-bundle 里找找,***我們發(fā)現(xiàn)不同 CPU 架構下的 so  依賴庫都是有的,像我們一般的電腦 64 位 CPU 即可使用 arch-x86_64 下的 so 動態(tài)庫,所以我們只需要在加載我們程序的 so  庫之前加載這些必須的依賴即可。處理代碼后面貼出。

Android中怎么利用Robolectric加載運行本地So動態(tài)庫

注意 ndk-bundle 里的 so 也是只能在 Linux 下用的,如果用于其它平臺會報錯,原因前面已說明。

java.lang.UnsatisfiedLinkError: xxx.so: unknown file type, first eight bytes: 0x7F 0x45 0x4C 0x46 0x02 0x01 0x01 0x00

macOS 下 Robolectric 中使用動態(tài)庫

前面已提到,不同平臺下動態(tài)鏈接庫是不通用的,所以必須對源碼重新編譯打包以移植到不同平臺下,如果你的 so 沒有源碼的話那在 macOS 和 Windows  下就行不通了。重新打包我們可以按如下兩步進行:

# 先生成 .o ,-I 后加進 Java jni 的編譯依賴  cc -c -I/System/Library/Frameworks/JavaVM.framework/Headers *.cpp  # 打包成 .dylib  g++ -dynamiclib -undefined suppress -flat_namespace *.o -o something.dylib

某些依賴庫可以到 /usr/lib 下找找,比如 libc 和 libstdc++ 。

Windows 下 Robolectric 中使用動態(tài)庫

本人沒有在 Windows 下開發(fā)所以這部分就略過了,思路是一樣的。

Sample

下面是簡單的處理代碼示例。首先新建一個包含 jni 的工程,里面寫個基本的本地庫,如下:

正常流程

// native-lib.cpp   #include <jni.h> #include <string>  extern "C" jstring Java_xyz_rocko_rsnl_nativeinterface_NativeSample_stringFromJNI(        JNIEnv *env,        jobject /* this */) {     // 簡單返回個字符串    std::string hello = "Hello from Native.";    return env->NewStringUTF(hello.c_str()); }

然后在 Application 啟動時會加載這個本地庫:

// NativeLibsApplication.java

public class NativeLibsApplication extends Application {   // Used to load the 'native-lib' library on application startup.  static {    System.loadLibrary("native-lib");  } }

此時運行 Robolectric 的 test case 就發(fā)生如下報錯:

java.lang.UnsatisfiedLinkError: no native-lib in java.library.path

Android中怎么利用Robolectric加載運行本地So動態(tài)庫

處理后的流程

首先流程應該在我們的代碼里避免可以直接加載 so 動態(tài)庫,然后 Robolectric 在啟動時自己去加載需要的動態(tài)庫。

// NativeLibsApplication.java

public class NativeLibsApplication extends Application {   @Override public void onCreate() {    super.onCreate();    loadNativeLibraries();  }   /**   * 簡單讓子類可自己實現(xiàn)   */  protected void loadNativeLibraries() {      // 代碼里真正加載本地庫的地方,當然你自己的可以處理地更解耦一點。    NativeLibrariesManager.loadNativeLibraries();  } }

然后我們的 Robolectric 里自定義自己的 Application,里面根據(jù)需要在不同運行平臺下自己加載需要的本地動態(tài)庫,首先復制我們給  Robolectric 用的本地庫到 test 的 libs 文件夾里,按不同平臺分類,如下圖:

Android中怎么利用Robolectric加載運行本地So動態(tài)庫

Linux 下的我們從 ndk-bundle 里復制我們需要的 .so,然后我們自己的本地庫打一個 x86-64 的即可,注意  compileSdkVersion 選上高一點支持 x86-64 的版本。

然后重新移植打出 macOS 下的動態(tài)庫,簡單寫個打包腳本如下:

// make_macOS_dylib.sh

#!/usr/bin/env bash  OUTPUT=../../../build/intermediates/dylibs mkdir -p ${OUTPUT}  # .o file cc -c -I/System/Library/Frameworks/JavaVM.framework/Headers *.cpp -o ${OUTPUT}/libnative-lib.o  # .dylib file g++ -dynamiclib -undefined suppress -flat_namespace ${OUTPUT}/*.o -o ${OUTPUT}/libnative-lib.dylib

libnative-lib.dylib 就是我們要的。

然后我們自定義 Application 處理加載這些動態(tài)庫:

// RobolectricApplication.java

public class RobolectricApplication extends NativeLibsApplication {   static {    ShadowLog.stream = System.out; //Android logcat output.  }   @Override protected void loadNativeLibraries() {    //Disable super class load so file.    //super.loadNativeLibraries();    Log.d(TAG, "=====>> Robolectric start native libraries.");     String libsBasePath =        new File(new File("").getAbsolutePath() + "/src/test/libs").getAbsolutePath();    String os = System.getProperty("os.name");    os = !TextUtils.isEmpty(os) ? os : "";    List<File> soFileList = new ArrayList<>();    String systemArchPath = libsBasePath + "/framework/";    //!!! 64 位機器下處理    if (os.contains("Mac")) {      //load system library if need      String macSysSoBasePath = systemArchPath + "macOS/";      soFileList.addAll(addLibs(macSysSoBasePath));      // App so...      String macAppSoPath = libsBasePath + "/macOS_x86-64/";      // mac下so要使用macOS專用庫      soFileList.addAll(addLibs(macAppSoPath));    } else if (os.contains("Linux")) {      //load system library if need      String linuxSysSoBasePath = systemArchPath + "arch_x86-64/";      soFileList.addAll(addLibs(linuxSysSoBasePath));      // App so...      String linuxAppSoPath = libsBasePath + "/linux_x86-64/";      soFileList.addAll(addLibs(linuxAppSoPath));    } else if (os.contains("Windows")) {      // ignore    }     for (File soFie : soFileList) {      System.load(soFie.getAbsolutePath());    }  }   private List<File> addLibs(@NonNull String path) {    File[] basePathFiles = new File(path).listFiles();    List<File> pathFilesList = new ArrayList<>();    if (basePathFiles != null && basePathFiles.length > 0) {      pathFilesList.addAll(Arrays.asList(basePathFiles));    }    return pathFilesList;  } }

現(xiàn)在就可以加載了,運行如下 test case,結果如下圖,成功了。

@Test public void testLoadNativeLibrariesSuccess() throws Exception {       String nativeExcepted = "Hello from Native.";       String result = NativeSample.stringFromJNI();       Log.d(TAG, "result: " + result);       assertEquals(nativeExcepted, result); }

Android中怎么利用Robolectric加載運行本地So動態(tài)庫

End

Linux 下使用最快速方便,只需要打包程序的 so 時順便打包出 x86-64 的 so ,然后復制 ndk-bundle 的 so  加上需要的依賴即可。macOS 和 Windows 下就需要自己打包出各自平臺下的動態(tài)庫才可使用,如果代碼里有 Android 自帶 so  依賴的話那就需要自己去重新移植編譯打包 ndk-bundle 里的動態(tài)庫了。

關于Android中怎么利用Robolectric加載運行本地So動態(tài)庫就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI