日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android 插件化原理解析——插件加载机制

發(fā)布時(shí)間:2025/3/15 Android 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android 插件化原理解析——插件加载机制 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

上文?Activity生命周期管理?中我們地完成了『?jiǎn)?dòng)沒(méi)有在AndroidManifest.xml中顯式聲明的Activity』的任務(wù);通過(guò)Hook?AMS和攔截ActivityThread中H類對(duì)于組件調(diào)度我們成功地繞過(guò)了AndroidMAnifest.xml的限制。

但是我們啟動(dòng)的『沒(méi)有在AndroidManifet.xml中顯式聲明』的Activity和宿主程序存在于同一個(gè)Apk中;通常情況下,插件均以獨(dú)立的文件存在甚至通過(guò)網(wǎng)絡(luò)獲取,這時(shí)候插件中的Activity能否成功啟動(dòng)呢?

要啟動(dòng)Activity組件肯定先要?jiǎng)?chuàng)建對(duì)應(yīng)的Activity類的對(duì)象,從上文?Activity生命周期管理?知道,創(chuàng)建Activity類對(duì)象的過(guò)程如下:

1 2 3 4 5 java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl);

也就是說(shuō),系統(tǒng)通過(guò)ClassLoader加載了需要的Activity類并通過(guò)反射調(diào)用構(gòu)造函數(shù)創(chuàng)建出了Activity對(duì)象。如果Activity組件存在于獨(dú)立于宿主程序的文件之中,系統(tǒng)的ClassLoader怎么知道去哪里加載呢?因此,如果不做額外的處理,插件中的Activity對(duì)象甚至都沒(méi)有辦法創(chuàng)建出來(lái),談何啟動(dòng)?

因此,要使存在于獨(dú)立文件或者網(wǎng)絡(luò)中的插件被成功啟動(dòng),首先就需要解決這個(gè)插件類加載的問(wèn)題。
下文將圍繞此問(wèn)題展開(kāi),完成『?jiǎn)?dòng)沒(méi)有在AndroidManifest.xml中顯示聲明,并且存在于外部插件中的Activity』的任務(wù)。

閱讀本文之前,可以先clone一份?understand-plugin-framework,參考此項(xiàng)目的classloader-hook?模塊。另外,插件框架原理解析系列文章見(jiàn)索引。

ClassLoader機(jī)制

或許有的童鞋還不太了解Java的ClassLoader機(jī)制,我這里簡(jiǎn)要介紹一下。

Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校檢、轉(zhuǎn)換解析和初始化的,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。
與那些在編譯時(shí)進(jìn)行鏈連接工作的語(yǔ)言不同,在Java語(yǔ)言里面,類型的加載、連接和初始化都是在程序運(yùn)行期間完成的,這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開(kāi)銷,但是會(huì)為Java應(yīng)用程序提供高度的靈活性,Java里天生可以同代拓展的語(yǔ)言特性就是依賴運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)鏈接這個(gè)特點(diǎn)實(shí)現(xiàn)的。例如,如果編寫一個(gè)面相接口的應(yīng)用程序,可以等到運(yùn)行時(shí)在制定實(shí)際的實(shí)現(xiàn)類;用戶可以通過(guò)Java與定義的和自定義的類加載器,讓一個(gè)本地的應(yīng)用程序可以在運(yùn)行時(shí)從網(wǎng)絡(luò)或其他地方加載一個(gè)二進(jìn)制流作為代碼的一部分,這種組裝應(yīng)用程序的方式目前已經(jīng)廣泛應(yīng)用于Java程序之中。從最基礎(chǔ)的Applet,JSP到復(fù)雜的OSGi技術(shù),都使用了Java語(yǔ)言運(yùn)行期類加載的特性。

Java的類加載是一個(gè)相對(duì)復(fù)雜的過(guò)程;它包括加載、驗(yàn)證、準(zhǔn)備、解析和初始化五個(gè)階段;對(duì)于開(kāi)發(fā)者來(lái)說(shuō),可控性最強(qiáng)的是加載階段;加載階段主要完成三件事:

  • 根據(jù)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流
  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為JVM方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
  • 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口。
  • 『通過(guò)一個(gè)類的全限定名獲取描述此類的二進(jìn)制字節(jié)流』這個(gè)過(guò)程被抽象出來(lái),就是Java的類加載器模塊,也即JDK中ClassLoader API。

    Android Framework提供了DexClassLoader這個(gè)類,簡(jiǎn)化了『通過(guò)一個(gè)類的全限定名獲取描述次類的二進(jìn)制字節(jié)流』這個(gè)過(guò)程;我們只需要告訴DexClassLoader一個(gè)dex文件或者apk文件的路徑就能完成類的加載。因此本文的內(nèi)容用一句話就可以概括:

    將插件的dex或者apk文件告訴『合適的』DexClassLoader,借助它完成插件類的加載

    關(guān)于CLassLoader機(jī)制更多的內(nèi)容,請(qǐng)參閱『深入理解Java虛擬機(jī)』這本書。

    思路分析

    Android系統(tǒng)使用了ClassLoader機(jī)制來(lái)進(jìn)行Activity等組件的加載;apk被安裝之后,APK文件的代碼以及資源會(huì)被系統(tǒng)存放在固定的目錄(比如/data/app/package_name/base-1.apk )系統(tǒng)在進(jìn)行類加載的時(shí)候,會(huì)自動(dòng)去這一個(gè)或者幾個(gè)特定的路徑來(lái)尋找這個(gè)類;但是系統(tǒng)并不知道存在于插件中的Activity組件的信息(插件可以是任意位置,甚至是網(wǎng)絡(luò),系統(tǒng)無(wú)法提前預(yù)知),因此正常情況下系統(tǒng)無(wú)法加載我們插件中的類;因此也沒(méi)有辦法創(chuàng)建Activity的對(duì)象,更不用談啟動(dòng)組件了。

    解決這個(gè)問(wèn)題有兩個(gè)思路,要么全盤接管這個(gè)類加載的過(guò)程;要么告知系統(tǒng)我們使用的插件存在于哪里,讓系統(tǒng)幫忙加載;這兩種方式或多或少都需要干預(yù)這個(gè)類加載的過(guò)程。老規(guī)矩,知己知彼,百戰(zhàn)不殆。我們首先分析一下,系統(tǒng)是如果完成這個(gè)類加載過(guò)程的。

    我們?cè)俅伟岢鯝ctivity的創(chuàng)建過(guò)程的代碼:

    1 2 3 4 java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl);

    這里可以很明顯地看到,系統(tǒng)通過(guò)待啟動(dòng)的Activity的類名className,然后使用ClassLoader對(duì)象cl把這個(gè)類加載進(jìn)虛擬機(jī),最后使用反射創(chuàng)建了這個(gè)Activity類的實(shí)例對(duì)象。要想干預(yù)這個(gè)ClassLoader(告知它我們的路徑或者替換他),我們首先得看看這玩意到底是個(gè)什么來(lái)頭。(從哪里創(chuàng)建的)

    cl這個(gè)ClasssLoader對(duì)象通過(guò)r.packageInfo對(duì)象的getClassLoader()方法得到,r.packageInfo是一個(gè)LoadedApk類的對(duì)象;那么,LoadedApk到底是個(gè)什么東西??

    我們查閱LoadedApk類的文檔,只有一句話,不過(guò)說(shuō)的很明白:

    Local state maintained about a currently loaded .apk.

    LoadedApk對(duì)象是APK文件在內(nèi)存中的表示。?Apk文件的相關(guān)信息,諸如Apk文件的代碼和資源,甚至代碼里面的Activity,Service等組件的信息我們都可以通過(guò)此對(duì)象獲取。

    OK, 我們知道這個(gè)LoadedApk是何方神圣了;接下來(lái)我們要搞清楚的是:這個(gè)?r.packageInfo?到底是從哪里獲取的?

    我們順著 performLaunchActivity上溯,輾轉(zhuǎn)handleLaunchActivity回到了?H?類的LAUNCH_ACTIVITY消息,找到了r.packageInfo的來(lái)源:

    1 2 3 4 final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null);

    getPackageInfoNoCheck方法很簡(jiǎn)單,直接調(diào)用了getPackageInfo方法:

    1 2 3 4 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { return getPackageInfo(ai, compatInfo, null, false, true, false); }

    在這個(gè)getPackageInfo方法里面我們發(fā)現(xiàn)了端倪:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { // 獲取userid信息 final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); synchronized (mResourcesManager) { // 嘗試獲取緩存信息 WeakReference<LoadedApk> ref; if (differentUser) { // Caching not supported across users ref = null; } else if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { // 緩存沒(méi)有命中,直接new packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); // 省略。。更新緩存 return packageInfo; } }

    這個(gè)方法很重要,我們必須弄清楚每一步;

    首先,它判斷了調(diào)用方和或許App信息的一方是不是同一個(gè)userId;如果是同一個(gè)user,那么可以共享緩存數(shù)據(jù)(要么緩存的代碼數(shù)據(jù),要么緩存的資源數(shù)據(jù))

    接下來(lái)嘗試獲取緩存數(shù)據(jù);如果沒(méi)有命中緩存數(shù)據(jù),才通過(guò)LoadedApk的構(gòu)造函數(shù)創(chuàng)建了LoadedApk對(duì)象;創(chuàng)建成功之后,如果是同一個(gè)uid還放入了緩存。

    提到緩存數(shù)據(jù),看過(guò)Hook機(jī)制之Binder Hook的童鞋可能就知道了,我們之前成功借助ServiceManager的本地代理使用緩存的機(jī)制Hook了各種Binder;因此這里完全可以如法炮制——我們拿到這一份緩存數(shù)據(jù),修改里面的ClassLoader;自己控制類加載的過(guò)程,這樣加載插件中的Activity類的問(wèn)題就解決了。這就引出了我們加載插件類的第一種方案:

    激進(jìn)方案:Hook掉ClassLoader,自己操刀

    從上述分析中我們得知,在獲取LoadedApk的過(guò)程中使用了一份緩存數(shù)據(jù);這個(gè)緩存數(shù)據(jù)是一個(gè)Map,從包名到LoadedApk的一個(gè)映射。正常情況下,我們的插件肯定不會(huì)存在于這個(gè)對(duì)象里面;但是如果我們手動(dòng)把我們插件的信息添加到里面呢?系統(tǒng)在查找緩存的過(guò)程中,會(huì)直接命中緩存!進(jìn)而使用我們添加進(jìn)去的LoadedApk的ClassLoader來(lái)加載這個(gè)特定的Activity類!這樣我們就能接管我們自己插件類的加載過(guò)程了!

    這個(gè)緩存對(duì)象mPackages存在于ActivityThread類中;老方法,我們首先獲取這個(gè)對(duì)象:

    1 2 3 4 5 6 7 8 9 10 // 先獲取到當(dāng)前的ActivityThread對(duì)象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 獲取到 mPackages 這個(gè)靜態(tài)成員變量, 這里緩存了dex包的信息 Field mPackagesField = activityThreadClass.getDeclaredField("mPackages"); mPackagesField.setAccessible(true); Map mPackages = (Map) mPackagesField.get(currentActivityThread);

    拿到這個(gè)Map之后接下來(lái)怎么辦呢?我們需要填充這個(gè)map,把插件的信息塞進(jìn)這個(gè)map里面,以便系統(tǒng)在查找的時(shí)候能命中緩存。但是這個(gè)填充這個(gè)Map我們出了需要包名之外,還需要一個(gè)LoadedApk對(duì)象;如何創(chuàng)建一個(gè)LoadedApk對(duì)象呢?

    我們當(dāng)然可以直接反射調(diào)用它的構(gòu)造函數(shù)直接創(chuàng)建出需要的對(duì)象,但是萬(wàn)一哪里有疏漏,構(gòu)造參數(shù)填錯(cuò)了怎么辦?又或者Android的不同版本使用了不同的參數(shù),導(dǎo)致我們創(chuàng)建出來(lái)的對(duì)象與系統(tǒng)創(chuàng)建出的對(duì)象不一致,無(wú)法work怎么辦?

    因此我們需要使用與系統(tǒng)完全相同的方式創(chuàng)建LoadedApk對(duì)象;從上文分析得知,系統(tǒng)創(chuàng)建LoadedApk對(duì)象是通過(guò)getPackageInfo來(lái)完成的,因此我們可以調(diào)用這個(gè)函數(shù)來(lái)創(chuàng)建LoadedApk對(duì)象;但是這個(gè)函數(shù)是private的,我們無(wú)法使用。

    有的童鞋可能會(huì)有疑問(wèn)了,private不是也能反射到嗎?我們確實(shí)能夠調(diào)用這個(gè)函數(shù),但是private表明這個(gè)函數(shù)是內(nèi)部實(shí)現(xiàn),或許那一天Google高興,把這個(gè)函數(shù)改個(gè)名字我們就直接GG了;但是public函數(shù)不同,public被導(dǎo)出的函數(shù)你無(wú)法保證是否有別人調(diào)用它,因此大部分情況下不會(huì)修改;我們最好調(diào)用public函數(shù)來(lái)保證盡可能少的遇到兼容性問(wèn)題。(當(dāng)然,如果實(shí)在木有路可以考慮調(diào)用私有方法,自己處理兼容性問(wèn)題,這個(gè)我們以后也會(huì)遇到)

    間接調(diào)用getPackageInfo這個(gè)私有函數(shù)的public函數(shù)有同名的getPackageInfo系列和getPackageInfoNoCheck;簡(jiǎn)單查看源代碼發(fā)現(xiàn),getPackageInfo除了獲取包的信息,還檢查了包的一些組件;為了繞過(guò)這些驗(yàn)證,我們選擇使用getPackageInfoNoCheck獲取LoadedApk信息。

    構(gòu)建插件LoadedApk對(duì)象

    我們這一步的目的很明確,通過(guò)getPackageInfoNoCheck函數(shù)創(chuàng)建出我們需要的LoadedApk對(duì)象,以供接下來(lái)使用。

    這個(gè)函數(shù)的簽名如下:

    1 2 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {

    因此,為了調(diào)用這個(gè)函數(shù),我們需要構(gòu)造兩個(gè)參數(shù)。其一是ApplicationInfo,其二是CompatibilityInfo;第二個(gè)參數(shù)顧名思義,代表這個(gè)App的兼容性信息,比如targetSDK版本等等,這里我們只需要提取出app的信息,因此直接使用默認(rèn)的兼容性即可;在CompatibilityInfo類里面有一個(gè)公有字段DEFAULT_COMPATIBILITY_INFO代表默認(rèn)兼容性信息;因此,我們的首要目標(biāo)是獲取這個(gè)ApplicationInfo信息。

    構(gòu)建插件ApplicationInfo信息

    我們首先看看ApplicationInfo代表什么,這個(gè)類的文檔說(shuō)的很清楚:

    Information you can retrieve about a particular application. This corresponds to information collected from the AndroidManifest.xml’s <application> tag.

    也就是說(shuō),這個(gè)類就是AndroidManifest.xml里面的?這個(gè)標(biāo)簽下面的信息;這個(gè)AndroidManifest.xml無(wú)疑是一個(gè)標(biāo)準(zhǔn)的xml文件,因此我們完全可以自己使用parse來(lái)解析這個(gè)信息。

    那么,系統(tǒng)是如何獲取這個(gè)信息的呢?其實(shí)Framework就有一個(gè)這樣的parser,也即PackageParser;理論上,我們也可以借用系統(tǒng)的parser來(lái)解析AndroidMAnifest.xml從而得到ApplicationInfo的信息。但遺憾的是,這個(gè)類的兼容性很差;Google幾乎在每一個(gè)Android版本都對(duì)這個(gè)類動(dòng)刀子,如果堅(jiān)持使用系統(tǒng)的解析方式,必須寫一系列兼容行代碼!!DroidPlugin就選擇了這種方式,相關(guān)類如下:

    DroidPlugin的PackageParser

    看到這里我就問(wèn)你怕不怕!!!這也是我們之前提到的私有或者隱藏的API可以使用,但必須處理好兼容性問(wèn)題;如果Android 7.0發(fā)布,這里估計(jì)得添加一個(gè)新的類PackageParseApi24。

    我這里使用API 23作為演示,版本不同的可能無(wú)法運(yùn)行請(qǐng)自行查閱 DroidPlugin 不同版本如何處理。

    OK回到正題,我們決定使用PackageParser類來(lái)提取ApplicationInfo信息。下圖是API 23上,PackageParser的部分類結(jié)構(gòu)圖:

    看起來(lái)有我們需要的方法 generateApplication;確實(shí)如此,依靠這個(gè)方法我們可以成功地拿到ApplicationInfo。
    由于PackageParser是@hide的,因此我們需要通過(guò)反射進(jìn)行調(diào)用。我們根據(jù)這個(gè)generateApplicationInfo方法的簽名:

    1 2 public static ApplicationInfo generateApplicationInfo(Package p, int flags, PackageUserState state)

    可以寫出調(diào)用generateApplicationInfo的反射代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser"); // 首先拿到我們得終極目標(biāo): generateApplicationInfo方法 // API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // public static ApplicationInfo generateApplicationInfo(Package p, int flags, // PackageUserState state) { // 其他Android版本不保證也是如此. Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package"); Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo", packageParser$PackageClass, int.class, packageUserStateClass);

    要成功調(diào)用這個(gè)方法,還需要三個(gè)參數(shù);因此接下來(lái)我們需要一步一步構(gòu)建調(diào)用此函數(shù)的參數(shù)信息。

    構(gòu)建PackageParser.Package

    generateApplicationInfo方法需要的第一個(gè)參數(shù)是PackageParser.Package;從名字上看這個(gè)類代表某個(gè)apk包的信息,我們看看文檔怎么解釋:

    Representation of a full package parsed from APK files on disk. A package consists of a single base APK, and zero or more split APKs.

    果然,這個(gè)類代表從PackageParser中解析得到的某個(gè)apk包的信息,是磁盤上apk文件在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)表示;因此,要獲取這個(gè)類,肯定需要解析整個(gè)apk文件。PackageParser中解析apk的核心方法是parsePackage,這個(gè)方法返回的就是一個(gè)Package類型的實(shí)例,因此我們調(diào)用這個(gè)方法即可;使用反射代碼如下:

    1 2 3 4 5 6 7 8 9 // 首先, 我們得創(chuàng)建出一個(gè)Package對(duì)象出來(lái)供這個(gè)方法調(diào)用 // 而這個(gè)需要得對(duì)象可以通過(guò) android.content.pm.PackageParser#parsePackage 這個(gè)方法返回得 Package對(duì)象得字段獲取得到 // 創(chuàng)建出一個(gè)PackageParser對(duì)象供使用 Object packageParser = packageParserClass.newInstance(); // 調(diào)用 PackageParser.parsePackage 解析apk的信息 Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); // 實(shí)際上是一個(gè) android.content.pm.PackageParser.Package 對(duì)象 Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);

    這樣,我們就得到了generateApplicationInfo的第一個(gè)參數(shù);第二個(gè)參數(shù)是解析包使用的flag,我們直接選擇解析全部信息,也就是0;

    構(gòu)建PackageUserState

    第三個(gè)參數(shù)是PackageUserState,代表不同用戶中包的信息。由于Android是一個(gè)多任務(wù)多用戶系統(tǒng),因此不同的用戶同一個(gè)包可能有不同的狀態(tài);這里我們只需要獲取包的信息,因此直接使用默認(rèn)的即可;

    至此,generateApplicaionInfo的參數(shù)我們已經(jīng)全部構(gòu)造完成,直接調(diào)用此方法即可得到我們需要的applicationInfo對(duì)象;在返回之前我們需要做一點(diǎn)小小的修改:使用系統(tǒng)系統(tǒng)的這個(gè)方法解析得到的ApplicationInfo對(duì)象中并沒(méi)有apk文件本身的信息,所以我們把解析的apk文件的路徑設(shè)置一下(ClassLoader依賴dex文件以及apk的路徑):

    1 2 3 4 5 6 7 8 9 // 第三個(gè)參數(shù) mDefaultPackageUserState 我們直接使用默認(rèn)構(gòu)造函數(shù)構(gòu)造一個(gè)出來(lái)即可 Object defaultPackageUserState = packageUserStateClass.newInstance(); // 萬(wàn)事具備!!!!!!!!!!!!!! ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser, packageObj, 0, defaultPackageUserState); String apkPath = apkFile.getPath(); applicationInfo.sourceDir = apkPath; applicationInfo.publicSourceDir = apkPath;

    替換ClassLoader

    獲取LoadedApk信息

    方才為了獲取ApplicationInfo我們費(fèi)了好大一番精力;回顧一下我們的初衷:

    我們最終的目的是調(diào)用getPackageInfoNoCheck得到LoadedApk的信息,并替換其中的mClassLoader然后把把添加到ActivityThread的mPackages緩存中;從而達(dá)到我們使用自己的ClassLoader加載插件中的類的目的。

    現(xiàn)在我們已經(jīng)拿到了getPackageInfoNoCheck這個(gè)方法中至關(guān)重要的第一個(gè)參數(shù)applicationInfo;上文提到第二個(gè)參數(shù)CompatibilityInfo代表設(shè)備兼容性信息,直接使用默認(rèn)的值即可;因此,兩個(gè)參數(shù)都已經(jīng)構(gòu)造出來(lái),我們可以調(diào)用getPackageInfoNoCheck獲取LoadedApk:

    1 2 3 4 5 6 7 8 9 10 11 // android.content.res.CompatibilityInfo Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo"); Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass); Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO"); defaultCompatibilityInfoField.setAccessible(true); Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null); ApplicationInfo applicationInfo = generateApplicationInfo(apkFile); Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);

    我們成功地構(gòu)造出了LoadedAPK, 接下來(lái)我們需要替換其中的ClassLoader,然后把它添加進(jìn)ActivityThread的mPackages中:

    1 2 3 4 5 6 7 8 9 10 11 12 String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath(); String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath(); ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader()); Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader"); mClassLoaderField.setAccessible(true); mClassLoaderField.set(loadedApk, classLoader); // 由于是弱引用, 因此我們必須在某個(gè)地方存一份, 不然容易被GC; 那么就前功盡棄了. sLoadedApk.put(applicationInfo.packageName, loadedApk); WeakReference weakReference = new WeakReference(loadedApk); mPackages.put(applicationInfo.packageName, weakReference);

    我們的這個(gè)CustomClassLoader非常簡(jiǎn)單,直接繼承了DexClassLoader,什么都沒(méi)有做;當(dāng)然這里可以直接使用DexClassLoader,這里重新創(chuàng)建一個(gè)類是為了更有區(qū)分度;以后也可以通過(guò)修改這個(gè)類實(shí)現(xiàn)對(duì)于類加載的控制:

    1 2 3 4 5 6 public class CustomClassLoader extends DexClassLoader { public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } }

    到這里,我們已經(jīng)成功地把把插件的信息放入ActivityThread中,這樣我們插件中的類能夠成功地被加載;因此插件中的Activity實(shí)例能被成功第創(chuàng)建;由于整個(gè)流程較為復(fù)雜,我們簡(jiǎn)單梳理一下:

  • 在ActivityThread接收到IApplication的 scheduleLaunchActivity遠(yuǎn)程調(diào)用之后,將消息轉(zhuǎn)發(fā)給H
  • H類在handleMessage的時(shí)候,調(diào)用了getPackageInfoNoCheck方法來(lái)獲取待啟動(dòng)的組件信息。在這個(gè)方法中會(huì)優(yōu)先查找mPackages中的緩存信息,而我們已經(jīng)手動(dòng)把插件信息添加進(jìn)去;因此能夠成功命中緩存,獲取到獨(dú)立存在的插件信息。
  • H類然后調(diào)用handleLaunchActivity最終轉(zhuǎn)發(fā)到performLaunchActivity方法;這個(gè)方法使用從getPackageInfoNoCheck中拿到LoadedApk中的mClassLoader來(lái)加載Activity類,進(jìn)而使用反射創(chuàng)建Activity實(shí)例;接著創(chuàng)建Application,Context等完成Activity組件的啟動(dòng)。
  • 看起來(lái)好像已經(jīng)天衣無(wú)縫萬(wàn)事大吉了;但是運(yùn)行一下會(huì)出現(xiàn)一個(gè)異常,如下:

    1 2 3 04-05 02:49:53.742 11759-11759/com.weishu.upf.hook_classloader E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.weishu.upf.hook_classloader, PID: 11759 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.weishu.upf.ams_pms_hook.app/com.weishu.upf.ams_pms_hook.app.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.weishu.upf.ams_pms_hook.app; is package not installed?

    錯(cuò)誤提示說(shuō)是無(wú)法實(shí)例化?Application,而Application的創(chuàng)建也是在performLaunchActivity中進(jìn)行的,這里有些蹊蹺,我們仔細(xì)查看一下。

    繞過(guò)系統(tǒng)檢查

    通過(guò)ActivityThread的performLaunchActivity方法可以得知,Application通過(guò)LoadedApk的makeApplication方法創(chuàng)建,我們查看這個(gè)方法,在源碼中發(fā)現(xiàn)了上文異常拋出的位置:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { initializeJavaContextClassLoader(); } ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } }

    木有辦法,我們只有一行一行地查看到底是哪里拋出這個(gè)異常的了;所幸代碼不多。(所以說(shuō),縮小異常范圍是一件多么重要的事情!!!)

    第一句 getClassLoader() 沒(méi)什么可疑的,雖然方法很長(zhǎng),但是它木有拋出任何異常(當(dāng)然,它調(diào)用的代碼可能拋出異常,萬(wàn)一找不到只能進(jìn)一步深搜了;所以我覺(jué)得這里應(yīng)該使用受檢異常)。

    然后我們看第二句,如果包名不是android開(kāi)頭,那么調(diào)用了一個(gè)叫做initializeJavaContextClassLoader的方法;我們查閱這個(gè)方法:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void initializeJavaContextClassLoader() { IPackageManager pm = ActivityThread.getPackageManager(); android.content.pm.PackageInfo pi; try { pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId()); } catch (RemoteException e) { throw new IllegalStateException("Unable to get package info for " + mPackageName + "; is system dying?", e); } if (pi == null) { throw new IllegalStateException("Unable to get package info for " + mPackageName + "; is package not installed?"); } boolean sharedUserIdSet = (pi.sharedUserId != null); boolean processNameNotDefault = (pi.applicationInfo != null && !mPackageName.equals(pi.applicationInfo.processName)); boolean sharable = (sharedUserIdSet || processNameNotDefault); ClassLoader contextClassLoader = (sharable) ? new WarningContextClassLoader() : mClassLoader; Thread.currentThread().setContextClassLoader(contextClassLoader); }

    這里,我們找出了這個(gè)異常的來(lái)源:原來(lái)這里調(diào)用了getPackageInfo方法獲取包的信息;而我們的插件并沒(méi)有安裝在系統(tǒng)上,因此系統(tǒng)肯定認(rèn)為插件沒(méi)有安裝,這個(gè)方法肯定返回null。所以,我們還要欺騙一下PMS,讓系統(tǒng)覺(jué)得插件已經(jīng)安裝在系統(tǒng)上了;至于如何欺騙 PMS,Hook機(jī)制之AMS&PMS?有詳細(xì)解釋,這里直接給出代碼,不贅述了:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private static void hookPackageManager() throws Exception { // 這一步是因?yàn)?initializeJavaContextClassLoader 這個(gè)方法內(nèi)部無(wú)意中檢查了這個(gè)包是否在系統(tǒng)安裝 // 如果沒(méi)有安裝, 直接拋出異常, 這里需要臨時(shí)Hook掉 PMS, 繞過(guò)這個(gè)檢查. Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 獲取ActivityThread里面原始的 sPackageManager Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); sPackageManagerField.setAccessible(true); Object sPackageManager = sPackageManagerField.get(currentActivityThread); // 準(zhǔn)備好代理對(duì)象, 用來(lái)替換原始的對(duì)象 Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager"); Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(), new Class<?>[] { iPackageManagerInterface }, new IPackageManagerHookHandler(sPackageManager)); // 1. 替換掉ActivityThread里面的 sPackageManager 字段 sPackageManagerField.set(currentActivityThread, proxy); }

    OK到這里,我們已經(jīng)能夠成功地加載簡(jiǎn)單的獨(dú)立的存在于外部文件系統(tǒng)中的apk了。至此 關(guān)于 DroidPlugin 對(duì)于Activity生命周期的管理已經(jīng)完全講解完畢了;這是一種極其復(fù)雜的Activity管理方案,我們僅僅寫一個(gè)用來(lái)理解的demo就Hook了相當(dāng)多的東西,在Framework層來(lái)回牽扯;這其中的來(lái)龍去脈要完全把握清楚還請(qǐng)讀者親自翻閱源碼。另外,我在此 對(duì)DroidPlugin 作者獻(xiàn)上我的膝蓋~這其中的玄妙讓人嘆為觀止!

    上文給出的方案中,我們?nèi)P接管了插件中類的加載過(guò)程,這是一種相對(duì)暴力的解決方案;能不能更溫柔一點(diǎn)呢?通俗來(lái)說(shuō),我們可以選擇改革,而不是革命——告訴系統(tǒng)ClassLoader一些必要信息,讓它幫忙完成插件類的加載。

    保守方案:委托系統(tǒng),讓系統(tǒng)幫忙加載

    我們?cè)俅伟岢鯝ctivityThread中加載Activity類的代碼:

    1 2 3 4 5 java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl);

    我們知道 這個(gè)r.packageInfo中的r是通過(guò)getPackageInfoNoCheck獲取到的;在『激進(jìn)方案』中我們把插件apk手動(dòng)添加進(jìn)緩存,采用自己加載辦法解決;如果我們不干預(yù)這個(gè)過(guò)程,導(dǎo)致無(wú)法命中mPackages中的緩存,會(huì)發(fā)生什么?

    查閱 getPackageInfo方法如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; if (differentUser) { // Caching not supported across users ref = null; } else if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); // 略 } }

    可以看到,沒(méi)有命中緩存的情況下,系統(tǒng)直接new了一個(gè)LoadedApk;注意這個(gè)構(gòu)造函數(shù)的第二個(gè)參數(shù)aInfo,這是一個(gè)ApplicationInfo類型的對(duì)象。在『激進(jìn)方案』中我們?yōu)榱双@取獨(dú)立插件的ApplicationInfo花了不少心思;那么如果不做任何處理這里傳入的這個(gè)aInfo參數(shù)是什么?

    追本溯源不難發(fā)現(xiàn),這個(gè)aInfo是從我們的替身StubActivity中獲取的!而StubActivity存在于宿主程序中,所以,這個(gè)aInfo對(duì)象代表的實(shí)際上就是宿主程序的Application信息!

    我們知道,接下來(lái)會(huì)使用new出來(lái)的這個(gè)LoadedApk的getClassLoader()方法獲取到ClassLoader來(lái)對(duì)插件的類進(jìn)行加載;而獲取到的這個(gè)ClassLoader是宿主程序使用的ClassLoader,因此現(xiàn)在還無(wú)法加載插件的類;那么,我們能不能讓宿主的ClasLoader獲得加載插件類的能力呢?;如果我們告訴宿主使用的ClassLoader插件使用的類在哪里,就能幫助他完成加載!

    宿主的ClassLoader在哪里,是唯一的嗎?

    上面說(shuō)到,我們可以通過(guò)告訴宿主程序的ClassLoader插件使用的類,讓宿主的ClasLoader完成對(duì)于插件類的加載;那么問(wèn)題來(lái)了,我們?nèi)绾潍@取到宿主的ClassLoader?宿主程序使用的ClasLoader默認(rèn)情況下是全局唯一的嗎?

    答案是肯定的。

    因?yàn)樵贔rameWork中宿主程序也是使用LoadedApk表示的,如同Activity啟動(dòng)是加載Activity類一樣,宿主中的類也都是通過(guò)LoadedApk的getClassLoader()方法得到的ClassLoader加載的;由類加載機(jī)制的『雙親委派』特性,只要有一個(gè)應(yīng)用程序類由某一個(gè)ClassLoader加載,那么它引用到的別的類除非父加載器能加載,否則都是由這同一個(gè)加載器加載的(不遵循雙親委派模型的除外)。

    表示宿主的LoadedApk在Application類中有一個(gè)成員變量mLoadedApk,而這個(gè)變量是從ContextImpl中獲取的;ContextImpl重寫了getClassLoader方法,因此我們?cè)贑ontext環(huán)境中直接getClassLoader()獲取到的就是宿主程序唯一的ClassLoader

    LoadedApk的ClassLoader到底是什么?

    現(xiàn)在我們確保了『使用宿主ClassLoader幫助加載插件類』可行性;那么我們應(yīng)該如何完成這個(gè)過(guò)程呢?

    知己知彼,百戰(zhàn)不殆。

    不論是宿主程序還是插件程序都是通過(guò)LoadedApk的getClassLoader()方法返回的ClassLoader進(jìn)行類加載的,返回的這個(gè)ClassLoader到底是個(gè)什么東西??這個(gè)方法源碼如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { return mClassLoader; } if (mIncludeCode && !mPackageName.equals("android")) { // 略... mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib, mBaseClassLoader); StrictMode.setThreadPolicy(oldPolicy); } else { if (mBaseClassLoader == null) { mClassLoader = ClassLoader.getSystemClassLoader(); } else { mClassLoader = mBaseClassLoader; } } return mClassLoader; } }

    可以看到,非android開(kāi)頭的包和android開(kāi)頭的包分別使用了兩種不同的ClassLoader,我們只關(guān)心第一種;因此繼續(xù)跟蹤ApplicationLoaders類:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent) { ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent(); synchronized (mLoaders) { if (parent == null) { parent = baseParent; } if (parent == baseParent) { ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); mLoaders.put(zip, pathClassloader); return pathClassloader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return pathClassloader; } }

    可以看到,應(yīng)用程序使用的ClassLoader都是PathClassLoader類的實(shí)例。那么,這個(gè)PathClassLoader是什么呢?從Android SDK給出的源碼只能看出這么多:

    1 2 3 4 5 6 7 8 9 10 11 public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } }

    SDK沒(méi)有導(dǎo)出這個(gè)類的源碼,我們?nèi)ndroidxref上面看;發(fā)現(xiàn)其實(shí)這個(gè)類真的就這么多內(nèi)容;我們繼續(xù)查看它的父類BaseDexClassLoader;ClassLoader嘛,我們查看findClass或者defineClass方法,BaseDexClassLoader的findClass方法如下:

    1 2 3 4 5 6 7 8 9 10 11 12 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; }

    可以看到,查找Class的任務(wù)通過(guò)pathList完成;這個(gè)pathList是一個(gè)DexPathList類的對(duì)象,它的findClass方法如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Class findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }

    這個(gè)DexPathList內(nèi)部有一個(gè)叫做dexElements的數(shù)組,然后findClass的時(shí)候會(huì)遍歷這個(gè)數(shù)組來(lái)查找Class;如果我們把插件的信息塞進(jìn)這個(gè)數(shù)組里面,那么不就能夠完成類的加載過(guò)程嗎?!!

    給默認(rèn)ClassLoader打補(bǔ)丁

    通過(guò)上述分析,我們知道,可以把插件的相關(guān)信息放入BaseDexClassLoader的表示dex文件的數(shù)組里面,這樣宿主程序的ClassLoader在進(jìn)行類加載,遍歷這個(gè)數(shù)組的時(shí)候,會(huì)自動(dòng)遍歷到我們添加進(jìn)去的插件信息,從而完成插件類的加載!

    接下來(lái),我們實(shí)現(xiàn)這個(gè)過(guò)程;我們會(huì)用到一些較為復(fù)雜的反射技術(shù)哦~不過(guò)代碼非常短:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile) throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 獲取 BaseDexClassLoader : pathList Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathListObj = pathListField.get(cl); // 獲取 PathList: Element[] dexElements Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements"); dexElementArray.setAccessible(true); Object[] dexElements = (Object[]) dexElementArray.get(pathListObj); // Element 類型 Class<?> elementClass = dexElements.getClass().getComponentType(); // 創(chuàng)建一個(gè)數(shù)組, 用來(lái)替換原始的數(shù)組 Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); // 構(gòu)造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 這個(gè)構(gòu)造函數(shù) Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class); Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)); Object[] toAddElementArray = new Object[] { o }; // 把原始的elements復(fù)制進(jìn)去 System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); // 插件的那個(gè)element復(fù)制進(jìn)去 System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); // 替換 dexElementArray.set(pathListObj, newElements); }

    短短的二十幾行代碼,我們就完成了『委托宿主ClassLoader加載插件類』的任務(wù);因此第二種方案也宣告完成!我們簡(jiǎn)要總結(jié)一下這種方式的原理:

  • 默認(rèn)情況下performLacunchActivity會(huì)使用替身StubActivity的ApplicationInfo也就是宿主程序的CLassLoader加載所有的類;我們的思路是告訴宿主ClassLoader我們?cè)谀?#xff0c;讓其幫助完成類加載的過(guò)程。
  • 宿主程序的ClassLoader最終繼承自BaseDexClassLoader,BaseDexClassLoader通過(guò)DexPathList進(jìn)行類的查找過(guò)程;而這個(gè)查找通過(guò)遍歷一個(gè)dexElements的數(shù)組完成;我們通過(guò)把插件dex添加進(jìn)這個(gè)數(shù)組就讓宿主ClasLoader獲取了加載插件類的能力。
  • 小結(jié)

    本文中我們采用兩種方案成功完成了『?jiǎn)?dòng)沒(méi)有在AndroidManifest.xml中顯示聲明,并且存在于外部插件中的Activity』的任務(wù)。

    『激進(jìn)方案』中我們自定義了插件的ClassLoader,并且繞開(kāi)了Framework的檢測(cè);利用ActivityThread對(duì)于LoadedApk的緩存機(jī)制,我們把攜帶這個(gè)自定義的ClassLoader的插件信息添加進(jìn)mPackages中,進(jìn)而完成了類的加載過(guò)程。

    『保守方案』中我們深入探究了系統(tǒng)使用ClassLoader findClass的過(guò)程,發(fā)現(xiàn)應(yīng)用程序使用的非系統(tǒng)類都是通過(guò)同一個(gè)PathClassLoader加載的;而這個(gè)類的最終父類BaseDexClassLoader通過(guò)DexPathList完成類的查找過(guò)程;我們hack了這個(gè)查找過(guò)程,從而完成了插件類的加載。

    這兩種方案孰優(yōu)孰劣呢?

    很顯然,『激進(jìn)方案』比較麻煩,從代碼量和分析過(guò)程就可以看出來(lái),這種機(jī)制異常復(fù)雜;而且在解析apk的時(shí)候我們使用的PackageParser的兼容性非常差,我們不得不手動(dòng)處理每一個(gè)版本的apk解析api;另外,它Hook的地方也有點(diǎn)多:不僅需要Hook AMS和H,還需要Hook ActivityThread的mPackages和PackageManager!

    『保守方案』則簡(jiǎn)單得多(雖然原理也不簡(jiǎn)單),不僅代碼很少,而且Hook的地方也不多;有一點(diǎn)正本清源的意思,從最最上層Hook住了整個(gè)類的加載過(guò)程。

    但是,我們不能簡(jiǎn)單地說(shuō)『保守方案』比『激進(jìn)方案』好。從根本上說(shuō),這兩種方案的差異在哪呢?

    『激進(jìn)方案』是多ClassLoader構(gòu)架,每一個(gè)插件都有一個(gè)自己的ClassLoader,因此類的隔離性非常好——如果不同的插件使用了同一個(gè)庫(kù)的不同版本,它們相安無(wú)事!『保守方案』是單ClassLoader方案,插件和宿主程序的類全部都通過(guò)宿主的ClasLoader加載,雖然代碼簡(jiǎn)單,但是魯棒性很差;一旦插件之間甚至插件與宿主之間使用的類庫(kù)有沖突,那么直接GG。

    多ClassLoader還有一個(gè)優(yōu)點(diǎn):可以真正完成代碼的熱加載!如果插件需要升級(jí),直接重新創(chuàng)建一個(gè)自定的ClassLoader加載新的插件,然后替換掉原來(lái)的版本即可(Java中,不同ClassLoader加載的同一個(gè)類被認(rèn)為是不同的類);單ClassLoader的話實(shí)現(xiàn)非常麻煩,有可能需要重啟進(jìn)程。

    在J2EE領(lǐng)域中廣泛使用ClasLoader的地方均采用多ClassLoader架構(gòu),比如Tomcat服務(wù)器,Java模塊化事實(shí)標(biāo)準(zhǔn)的OSGi技術(shù);所以,我們有足夠的理由認(rèn)為選擇多ClassLoader架構(gòu)在大多數(shù)情況下是明智之舉

    目前開(kāi)源的插件方案中,DroidPlugin采用的『激進(jìn)方案』,Small采用的『保守方案』那么,有沒(méi)有兩種優(yōu)點(diǎn)兼顧的方案呢??

    答案自然是有的。

    DroidPlugin和Small的共同點(diǎn)是兩者都是非侵入式的插件框架;什么是『非侵入式』呢?打個(gè)比方,你啟動(dòng)一個(gè)插件Activity,直接使用startActivity即可,就跟開(kāi)發(fā)普通的apk一樣,開(kāi)發(fā)插件和普通的程序?qū)τ陂_(kāi)發(fā)者來(lái)說(shuō)沒(méi)有什么區(qū)別。

    如果我們一定程度上放棄這種『侵入性』,那么我們就能實(shí)現(xiàn)一個(gè)兩者優(yōu)點(diǎn)兼而有之的插件框架!這里我先賣個(gè)關(guān)子~

    OK,本文的內(nèi)容就到這里了;關(guān)于『插件機(jī)制對(duì)于Activity的處理方式』也就此完結(jié)。要說(shuō)明的是,在本文的『保守方案』其實(shí)只處理了代碼的加載過(guò)程,它并不能加載有資源的apk!所以目前我這個(gè)實(shí)現(xiàn)基本沒(méi)什么暖用;當(dāng)然我這里只是就『代碼加載』進(jìn)行舉例;至于資源,那牽扯到另外一個(gè)問(wèn)題——插件系統(tǒng)的資源管理機(jī)制這個(gè)在后續(xù)文章的合適機(jī)會(huì)我會(huì)單獨(dú)講解。

    原文出處: http://weishu.me/2016/04/05/understand-plugin-framework-classloader/

    總結(jié)

    以上是生活随笔為你收集整理的Android 插件化原理解析——插件加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。