插件化中加載so庫(kù)解決方案

簡(jiǎn)介

先簡(jiǎn)單介紹下舆床,我們知道jninative層與java層交互的橋梁该溯,有了jni认轨,我們可以通過動(dòng)態(tài)或靜態(tài)的方式去加載so绅络,從而讀取so庫(kù)中的native邏輯。

常用架構(gòu)

當(dāng)我們需要將native代碼打包成so庫(kù)時(shí)嘁字,我們需要使用ndk-build等命令去生成對(duì)應(yīng)的架構(gòu)so庫(kù)恩急,常用的架構(gòu)如下:
armeabi,armeabi-v7a纪蜒,x86衷恭,mips,arm64-v8a纯续,mips64随珠,x86_64

外部加載與內(nèi)部加載

  • 打包在apk中的情況,不需要開發(fā)者自己去判斷ABI杆烁,Android系統(tǒng)在安裝APK的時(shí)候牙丽,不會(huì)安裝APK里面全部的so庫(kù)文件,而是會(huì)根據(jù)當(dāng)前CPU類型支持的ABI兔魂,從APK里面拷貝最合適的so庫(kù),并保存在APP的內(nèi)部存儲(chǔ)路徑的libs 下面举娩。
  • 動(dòng)態(tài)加載外部so的情況下析校,需要我們判斷ABI類型來加載相應(yīng)的so构罗,Android系統(tǒng)不會(huì)幫我們處理。

加載so的兩種方式

  • System.load
    參數(shù)必須為庫(kù)文件的絕對(duì)路徑
    使用注意:只能將so放到內(nèi)部進(jìn)行絕對(duì)路徑加載智玻,而不能放置于sd卡遂唧,否則會(huì)拋異常
  • System.loadLibrary
    參數(shù)為庫(kù)文件名,不包含庫(kù)文件的擴(kuò)展名

插件中so加載問題定位

小編寫的《實(shí)戰(zhàn)插件化-MPlugin》使用到了如下加載so方式

    // 步驟1:加載生成的so庫(kù)文件
    // 注意要跟so庫(kù)文件名相同
    static {
        System.loadLibrary("hello_jni");
    }

    // 步驟2:定義在JNI中實(shí)現(xiàn)的方法
    public native String getFromJNI();

如果僅僅是使用addDexPath方法將插件dex插入到宿主dexElements中吊奢,那么插件apk中存在加載so的話盖彭,是會(huì)拋經(jīng)典的UnsatisfiedLinkError異常
通過閱讀android7.0源碼我們發(fā)現(xiàn),當(dāng)我們調(diào)用System.loadLibrary("hello_jni")方法時(shí)页滚,會(huì)進(jìn)入如下源碼層

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) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

而通過如上源碼我們會(huì)發(fā)現(xiàn)當(dāng)filenamenull時(shí)召边,會(huì)拋出經(jīng)典的UnsatisfiedLinkError異常,所以我們繼續(xù)看loader.findLibrary(libraryName)這個(gè)方法裹驰,源碼如下:

BaseDexClassLoader.java
@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;
}

通過如上源碼我們大概知道了隧熙,System.loadLibrary是通過BaseDexClassLoader中的nativeLibraryPathElements數(shù)組來遍歷查詢子element來獲得librarypath,所以我們定位到nativeLibraryPathElements幻林,再來看nativeLibraryPathElements是如何生成的贞盯,繼續(xù)往下看源碼

DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
                                              ········
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);
                                              ········
}

通過如上源碼我們可以知道原來dexElementsnativeLibraryPathElements是分開的,所以這就是為什么我們明明將插件apk中的dex插入到宿主apk的dexElements中去運(yùn)行插件apk沪饺,而插件apk中因?yàn)榧虞d了so從而導(dǎo)致拋出經(jīng)典的UnsatisfiedLinkError異常躏敢。

解決方案

  • 第一種方式:通過DexClassLoader去load取插件apk,我們知道DexClassLoader 中還可以傳入libraryPath整葡,該參數(shù)就是允許你指定so加載路徑父丰,所以實(shí)現(xiàn)方式如下:
String librarySearchPath = "/data/data/mplugindemo.shengyuan.com.mplugindemo/mplugin_demo/lib/";
DexClassLoader loader = new DexClassLoader(dexPath, mContext.getCacheDir().getAbsolutePath(),librarySearchPath, mContext.getClassLoader());

然后獲得如上loader對(duì)象后,如果你的so加載邏輯是在fragment中掘宪,而只是為了將插件中的fragment載入到宿主容器中顯示蛾扇,如上方式就可以了,但是如果你是希望去啟動(dòng)插件Activity魏滚,由插件Activity去加載so的話镀首,還需要將LoadedApk中的mClassLoader對(duì)象替換成如上loader對(duì)象。(不建議使用鼠次,因?yàn)楫?dāng)你使用插件apk跳轉(zhuǎn)到下一個(gè)頁(yè)面的時(shí)候更哄,會(huì)拋出找不到第三方公共庫(kù)的異常,除非你重寫startActivity腥寇,然后load插件dex中的class來啟動(dòng)對(duì)應(yīng)的activity頁(yè)面)

  • 第二種方式:可以參考小編之前的《剖析ClassLoader深入熱修復(fù)原理》文章中提到的成翩,在 PathClassLoaderBootClassLoader 之間插入一個(gè) 自定義的MyClassLoader,然后在MyClassLoader中重寫findLibrary方法
  • 第三種方式:通過如上閱讀定位我們知道赦役,核心點(diǎn)在nativeLibraryPathElements數(shù)組麻敌,因?yàn)槲覀冎?code>nativeLibraryPathElements數(shù)組是通過makePathElements方法構(gòu)建生成的,所以我們可以通過反射去調(diào)用makePathElements方法掂摔,將librarySearchPath路徑傳入术羔,從而獲得新的nativeLibraryPathElements數(shù)組赢赊,然后將新舊合并。
    實(shí)現(xiàn)方式如下:
 public static void insertNativeLibraryPathElements(File soDirFile,Context context){
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object pathList = getPathList(pathClassLoader);
        if(pathList != null) {
            Field nativeLibraryPathElementsField = null;
            try {

                Method makePathElements;
                Object invokeMakePathElements;
                boolean isNewVersion = Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1;
                //調(diào)用makePathElements
                makePathElements = isNewVersion?pathList.getClass().getDeclaredMethod("makePathElements", List.class):pathList.getClass().getDeclaredMethod("makePathElements", List.class,List.class,ClassLoader.class);
                makePathElements.setAccessible(true);
                ArrayList<IOException> suppressedExceptions = new ArrayList<>();
                List<File> nativeLibraryDirectories = new ArrayList<>();
                nativeLibraryDirectories.add(soDirFile);
                List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
                //獲取systemNativeLibraryDirectories
                Field systemNativeLibraryDirectoriesField = pathList.getClass().getDeclaredField("systemNativeLibraryDirectories");
                systemNativeLibraryDirectoriesField.setAccessible(true);
                List<File> systemNativeLibraryDirectories = (List<File>) systemNativeLibraryDirectoriesField.get(pathList);
                Log.i("insertNativeLibrary","systemNativeLibraryDirectories "+systemNativeLibraryDirectories);
                allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
                invokeMakePathElements = isNewVersion?makePathElements.invoke(pathClassLoader, allNativeLibraryDirectories):makePathElements.invoke(pathClassLoader, allNativeLibraryDirectories,suppressedExceptions,pathClassLoader);
                Log.i("insertNativeLibrary","makePathElements "+invokeMakePathElements);

                nativeLibraryPathElementsField = pathList.getClass().getDeclaredField("nativeLibraryPathElements");
                nativeLibraryPathElementsField.setAccessible(true);
                Object list = nativeLibraryPathElementsField.get(pathList);
                Log.i("insertNativeLibrary","nativeLibraryPathElements "+list);
                Object dexElementsValue = combineArray(list, invokeMakePathElements);
                //把組合后的nativeLibraryPathElements設(shè)置到系統(tǒng)中
                nativeLibraryPathElementsField.set(pathList,dexElementsValue);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

注意

需要先解壓插件apk级历,提取apk中的so庫(kù)释移,根據(jù)Build.CPU_ABI來判斷當(dāng)前適用的so架構(gòu),然把對(duì)應(yīng)架構(gòu)的so庫(kù)復(fù)制到宿主apk對(duì)應(yīng)的data so目錄下(/data/data/mplugindemo.shengyuan.com.mplugindemo/mplugin168/lib/arm64-v8a
已在android7.0寥殖、8.0驗(yàn)證通過
實(shí)例地址:https://github.com/3332523marco/MPlugin

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玩讳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嚼贡,更是在濱河造成了極大的恐慌熏纯,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件编曼,死亡現(xiàn)場(chǎng)離奇詭異豆巨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掐场,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門往扔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熊户,你說我怎么就攤上這事萍膛。” “怎么了嚷堡?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蝗罗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蝌戒,道長(zhǎng)串塑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任北苟,我火速辦了婚禮桩匪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘友鼻。我一直安慰自己傻昙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布彩扔。 她就那樣靜靜地躺著妆档,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虫碉。 梳的紋絲不亂的頭發(fā)上贾惦,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼纤虽。 笑死乳绕,一個(gè)胖子當(dāng)著我的面吹牛绞惦,可吹牛的內(nèi)容都是我干的逼纸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼济蝉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼杰刽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起王滤,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤贺嫂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后雁乡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體第喳,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年踱稍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曲饱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡珠月,死狀恐怖扩淀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啤挎,我是刑警寧澤驻谆,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站庆聘,受9級(jí)特大地震影響胜臊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伙判,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一象对、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧澳腹,春花似錦织盼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羊娃,卻和暖如春唐全,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工邮利, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弥雹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓延届,卻偏偏與公主長(zhǎng)得像剪勿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子方庭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354