Android插件化-類加載

插件化的第一步就是要解決類加載問(wèn)題贬媒,因?yàn)椴寮遣话惭b的,要直接加載Apk中的類,apk的中的class是封裝成dex文件放在APK內(nèi)的移剪。

Dex文件

Dex即 Dalvik Executable的簡(jiǎn)寫喜喂,Dex文件是一種壓縮文件格式的封裝瓤摧。 是Android對(duì)class文件進(jìn)行翻譯、重構(gòu)玉吁、解釋照弥、壓縮等操作之后的產(chǎn)物。dex中各個(gè)類能夠共享數(shù)據(jù)进副,在一定程度上降低了冗余这揣,同時(shí)也是文件結(jié)構(gòu)更加經(jīng)湊悔常,實(shí)驗(yàn)表明,dex文件是傳統(tǒng)jar文件大小的50%左右给赞。
文件結(jié)構(gòu):

名稱 解釋
header dex文件頭部机打,記錄整個(gè)dex文件的相關(guān)屬性
string_ids 字符串?dāng)?shù)據(jù)索引,記錄了每個(gè)字符串在數(shù)據(jù)區(qū)的偏移量
type_ids 類似數(shù)據(jù)索引片迅,記錄了每個(gè)類型的字符串索引
proto_ids 原型數(shù)據(jù)索引残邀,記錄了方法聲明的字符串,返回類型字符串柑蛇,參數(shù)列表
field_ids 字段數(shù)據(jù)索引芥挣,記錄了所屬類,類型以及方法名
method_ids 類方法索引耻台,記錄方法所屬類名空免,方法聲明以及方法名等信息
class_defs 類定義數(shù)據(jù)索引,記錄指定類各類信息粘我,包括接口鼓蜒,超類,類數(shù)據(jù)偏移量
data 數(shù)據(jù)區(qū)征字,保存了各個(gè)類的真實(shí)數(shù)據(jù)
link_data 連接數(shù)據(jù)區(qū)

DexClassloader

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的

 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
  • dexPath:目標(biāo)類所在的APK或jar文件的路徑都弹。類裝載器將從該路徑中尋找指定的目標(biāo)類,該類必須是APK或jar的全路徑.如果要包含多個(gè)路徑,路徑之間必須使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)獲得。支持加載APK匙姜、DEX和JAR畅厢,也可以從SD卡進(jìn)行加載。最終都是將dexPath路徑上的文件ODEX優(yōu)化到內(nèi)部位置optimizedDirectory氮昧,然后框杜,再進(jìn)行加載的。
  • optimizedDirectory: 緩存需要加載的dex文件袖肥,并創(chuàng)建一個(gè)DexFile對(duì)象咪辱,如果它為null,那么會(huì)直接使用dex文件原有的路徑來(lái)創(chuàng)建DexFile對(duì)象椎组。該參數(shù)就是制定的從Apk文件中解壓出的dex 文件存放的路徑油狂。
  • libPath:目標(biāo)類中所使用的C/C++庫(kù)存放的路徑
  • parent: 該裝載器的父裝載器。一般為當(dāng)前執(zhí)行類的裝載器寸癌,例如在Android中以context.getClassLoader()作為父裝載器专筷。

DexClassLoader可以指定自己的optimizedDirectory,所以可以加載外部的dex蒸苇,因?yàn)檫@個(gè)dex會(huì)被復(fù)制到內(nèi)部路徑的optimizedDirectory磷蛹。而PathClassLoader沒(méi)有optimizedDirectory,所以它只能加載內(nèi)部的dex溪烤,這些大都是存在系統(tǒng)中已經(jīng)安裝過(guò)的apk里面的

所有的操作都是通過(guò)DexPathList實(shí)現(xiàn)的

 protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

DexPathList

DexPathList中有個(gè)dexElements數(shù)組

private final Element[] dexElements;

this.dexElements =
            makeDexElements(splitDexPath(dexPath), optimizedDirectory);

dexElements數(shù)組就是odex文件的集合味咳。odex文件是 dexPath指向的原始dex(.apk,.zip,.jar等)文件在optimizedDirectory文件夾中生成相應(yīng)的優(yōu)化后的文件庇勃。如果不分包一般這個(gè)數(shù)組只有一個(gè)Element元素,也就只有一個(gè)DexFile文件

makeDexElements方法生成一個(gè)Element[] dexElements 數(shù)組槽驶。

if (name.endsWith(DEX_SUFFIX)) {
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }

loadDexFile:

 private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
     if (optimizedDirectory == null) {
         return new DexFile(file);
     } else {
         String optimizedPath = optimizedPathFor(file, optimizedDirectory);
         return DexFile.loadDex(file.getPath(), optimizedPath, 0);
     }
}

DexFile最終是通過(guò)JNI調(diào)用native加載dex文件的匪凉。

加載class:

public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        return null;
    }

這里就出現(xiàn)了可以實(shí)現(xiàn)熱修復(fù)和插件化的hook點(diǎn)。將 dex 文件放到 dexElements 數(shù)組前面捺檬,這樣在加載 class 時(shí),優(yōu)先找到前面的 dex 文件贸铜,加載到 class 之后就不再尋找堡纬。從而原來(lái)的 apk 文件中同名的類就不會(huì)再使用,從而達(dá)到修復(fù)的目的蒿秦。

public Class loadClassBinaryName(String name, ClassLoader loader) {
        return defineClass(name, loader, mCookie);
    }
private native static Class defineClass(String name, ClassLoader loader, int cookie);

標(biāo)準(zhǔn)JVM中烤镐,ClassLoader是用defineClass加載類的,而Android中defineClass被棄用了棍鳖,改用了loadClass方法炮叶,而且加載類的過(guò)程也移動(dòng)到了DexFile中,在DexFile中加載類的具體方法也叫defineClass渡处,這也是為了維護(hù)代碼可讀性

Native library添加

插件中可能包含native library镜悉,為了插件能運(yùn)行,Native library也需要加載医瘫。
DexPathList中有一個(gè)屬性:

private final File[] nativeLibraryDirectories;

nativeLibraryDirectories就是nativeLibrary所在的文件夾數(shù)組侣肄,只需要把插件的nativeLibrary所在的文件夾通過(guò)反射添加進(jìn)去就行了

整個(gè)實(shí)現(xiàn)過(guò)程

要加載插件中的類和native library, 當(dāng)然得實(shí)現(xiàn)一個(gè)DexClassloader,通過(guò)這個(gè)DexClassloader去加載插件中的dex

創(chuàng)建classLoader:

直接new一個(gè)即可醇份。初始化的參數(shù):
dexPath:插件apk的文件的絕對(duì)路徑
optimizedDirectory:dex文件夾的的絕對(duì)路徑稼锅,可通過(guò)context.getDir("dex", Context.MODE_PRIVATE)獲取
libraryPath: 存放native Library文件夾對(duì)應(yīng)的路徑,context.getDir("valibs",Context.MODE_PRIVATE)獲取
parent:默認(rèn)的ClassLoader僚纷,即context.getClassLoader()

將插件中的dex加入到dexElements中:

  1. 通過(guò)反射獲取到默認(rèn)的ClassLoader中的dexElements
  2. 反射獲取到新classLoader中的dexElements
  3. 合并兩個(gè)數(shù)組
  4. 反射將合并之后的數(shù)組賦值到默認(rèn)ClassLoader中的pathList中

native Library操作類似矩距。

這樣操作之后默認(rèn)的ClassLoader中已經(jīng)有了插件中的dex和native Library,可以加載對(duì)應(yīng)的類了怖竭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锥债,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子侵状,更是在濱河造成了極大的恐慌赞弥,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趣兄,死亡現(xiàn)場(chǎng)離奇詭異绽左,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)艇潭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門拼窥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)戏蔑,“玉大人,你說(shuō)我怎么就攤上這事鲁纠∽芸茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵改含,是天一觀的道長(zhǎng)情龄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捍壤,這世上最難降的妖魔是什么骤视? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮鹃觉,結(jié)果婚禮上专酗,老公的妹妹穿的比我還像新娘。我一直安慰自己盗扇,他們只是感情好祷肯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著疗隶,像睡著了一般佑笋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斑鼻,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天允青,我揣著相機(jī)與錄音,去河邊找鬼卵沉。 笑死颠锉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的史汗。 我是一名探鬼主播琼掠,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼停撞!你這毒婦竟也來(lái)了瓷蛙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戈毒,失蹤者是張志新(化名)和其女友劉穎艰猬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體埋市,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冠桃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了道宅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食听。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胸蛛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出樱报,到底是詐尸還是另有隱情葬项,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布迹蛤,位于F島的核電站民珍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盗飒。R本人自食惡果不足惜穷缤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箩兽。 院中可真熱鬧,春花似錦章喉、人聲如沸汗贫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)落包。三九已至,卻和暖如春摊唇,著一層夾襖步出監(jiān)牢的瞬間咐蝇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工巷查, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留有序,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓岛请,卻偏偏與公主長(zhǎng)得像旭寿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崇败,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容