相信很多人再開發(fā)過(guò)程會(huì)遇到方法數(shù)超過(guò)65535的問(wèn)題,通過(guò)學(xué)習(xí)我們通過(guò)一個(gè)簡(jiǎn)單的例子利用Android的插件化開發(fā)去解決這個(gè)問(wèn)題塌计。所謂插件化就是由宿主APP去加載以及運(yùn)行插件APP挺身。
下面是一些插件化的優(yōu)勢(shì):
1.模塊解耦
2.解除單個(gè)dex函數(shù)不能超過(guò)65535的限制
3.動(dòng)態(tài)升級(jí)
4.高效開發(fā)(編譯速度更快)
下面是插件化開發(fā)的缺點(diǎn):
1.插件化開發(fā)的APP不能在Google Play上線,也就是沒(méi)有海外市場(chǎng)锌仅。
2.技術(shù)有難度章钾,目前一些成熟的框架都是閉源的
下面開始通過(guò)一個(gè)簡(jiǎn)單的demo 實(shí)現(xiàn)Android插件化開發(fā)(類似支付寶中在未安裝滴滴軟件的情況下能夠跳轉(zhuǎn)到滴滴的頁(yè)面)
我們通過(guò)簡(jiǎn)單的圖片設(shè)置到頁(yè)面驗(yàn)證跳轉(zhuǎn)是否成功
1、首先我們創(chuàng)建一個(gè)宿主工程->MainActivity
用一個(gè)點(diǎn)擊事件做插件的Activity的跳轉(zhuǎn)技扼。
2伍玖、創(chuàng)建一個(gè)插件工程module->MainActivity(一定要是module不是Android library)
3、創(chuàng)建一個(gè)插件遵循的標(biāo)準(zhǔn)工程library(DidiInterface 和 BasePlugActivity)
例如 滴滴想要在支付寶上集成剿吻,那么就必須遵循這個(gè)標(biāo)準(zhǔn)做事
創(chuàng)建一個(gè) BasePlugActivity類 實(shí)現(xiàn)接口
緊接著插件的MainActivity去繼承這個(gè)BasePlugActivity窍箍。
看似以上代碼并沒(méi)有什么問(wèn)題,我們平常寫的最簡(jiǎn)單的代碼不就是這樣的嗎丽旅?? ?
其實(shí)坑馬上就來(lái)了椰棘。我們知道 setContenView以及 findViewById都是調(diào)用系統(tǒng)提供的上下文的,(包括BasePlugActivity中的類似onCreate都是調(diào)用系統(tǒng)的 super.Oncreate(saveInstanceState))然而插件apk我們是沒(méi)有安裝的榄笙,所以凡是調(diào)用系統(tǒng)上下文的方法都是不靠譜的邪狞,我們必須都要重寫他們。然后注入一個(gè)新的context.
好了茅撞,插件類也創(chuàng)建完了那么我們到底要怎么在宿主中去跳轉(zhuǎn)到這個(gè)沒(méi)有安裝的插件Activity呢帆卓?既然要跳轉(zhuǎn)那么第一步我們要做的可能是先去配置文件中注冊(cè)這個(gè)類。那么在宿主項(xiàng)目中并沒(méi)有這個(gè)Activity那么我們要怎么實(shí)現(xiàn)以上的需求呢米丘?
其實(shí)剑令,除了Hook我們還可以用插樁的方式,通過(guò)一個(gè)代理類專門做類的跳轉(zhuǎn)拄查。
我們?cè)谒拗黜?xiàng)目中創(chuàng)建一個(gè)ProxyActivity吁津,我們知道加載一個(gè)頁(yè)面除了要加載它的ClassLoader 還要重寫它的getResources。
所以首先我們創(chuàng)建一個(gè)管理單例類 ProxyManager專門去做這些東西
解釋下:DexclassLoader
在Java環(huán)境中,有個(gè)概念叫做”類裝載器(Class Loader)”,其作用是動(dòng)態(tài)加載Class文件.標(biāo)準(zhǔn)的Java SDK中有一個(gè)ClassLoader類,借助他可以裝載想要的Class文件,每個(gè)ClassLoader對(duì)象在初始化的時(shí)候必須指定Class文件的路徑.
但我們?cè)谑褂胘ava的時(shí)候,基本上沒(méi)有使用過(guò)ClassLoader,僅僅使用import就可以加載類文件了,簡(jiǎn)單的講,import中所引用的類文件有兩個(gè)特點(diǎn):
1:必須存在于本地,當(dāng)程序運(yùn)行需要該類的時(shí)候,內(nèi)部類裝載器會(huì)自動(dòng)裝載該類,這對(duì)程序員來(lái)說(shuō)是透明的,即程序員感知不到該過(guò)程
2:編譯時(shí)必須在現(xiàn)場(chǎng),否則編譯過(guò)程會(huì)因找不到引用文件而不能正常編譯.
但在有些情況下,所需要的類卻不能滿足以上兩個(gè)條件.比如當(dāng)該類是從遠(yuǎn)程下載并在本地執(zhí)行的時(shí)候,典型的例子就是通過(guò)瀏覽器中的AppletLet執(zhí)行的java程序,這些要執(zhí)行的程序是在服務(wù)器端.另一種情況是,要引用的Class文件不方便在編譯的時(shí)候直接參與,而只能在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用.例如,在Android Framework中,所包含的Class文件是一些通用的類文件,但對(duì)于一些設(shè)備商而言,他們需要擴(kuò)充Framework,擴(kuò)充的具體工作包括兩點(diǎn):
1:需要增加一些額外的類文件,這些類文件提供廠商自定義的功能,這些文件一般以獨(dú)立的jar包存在
2:需要修改Framework中的已有的類文件,比如WindowManagerService類,在該類中添加使用自定義jar包中的代碼.使用自定義jar常用的方法是使用import關(guān)鍵字包含自定義的類,但為了保持和原生Framework的兼容性,對(duì)原聲Framework最少化修改,可以使類裝載器動(dòng)態(tài)裝載自定義jar包.
這就是使用ClassLoader的原因.
在一般情況下,應(yīng)用程序不需要?jiǎng)?chuàng)建一個(gè)全新的ClassLoader對(duì)象,而是使用當(dāng)前環(huán)境已經(jīng)存在的ClassLoader.因?yàn)閖ava的Runtime環(huán)境在初始化時(shí),其內(nèi)部會(huì)創(chuàng)建一個(gè)ClassLoader對(duì)象用于加載Runtime所需的各種java類.
每個(gè)ClassLoader必須有一個(gè)父ClasLoader,在裝載Class文件的時(shí)候,子ClassLoader會(huì)先請(qǐng)求其父ClassLoader加載該Class文件,只有當(dāng)其父ClassLoader找不到該Class的時(shí)候,子ClassLoader才會(huì)急促裝載該類,這是一種安全機(jī)制.
對(duì)于Android的應(yīng)用程序,本質(zhì)上雖然也是用Java開發(fā),并且使用標(biāo)準(zhǔn)的Java編譯器編譯出Class文件,但最終的APK文件中包含的確實(shí)dex類型的文件.dex文件是將所需的所有Class文件重新打包,打包的規(guī)則不是簡(jiǎn)單的壓縮,而是完全對(duì)Class文件內(nèi)部的各種函數(shù)表,變量表等進(jìn)行優(yōu)化,并產(chǎn)生一個(gè)新的文件,這就是dex文件.由于dex文件是一種經(jīng)過(guò)優(yōu)化的Class文件,因此要加載這樣特殊的Class文件就需要特殊的類裝載器,這就是DexClassLoader.Android SDK中提供了DexClassLoader類就是處于這個(gè)目的.
轉(zhuǎn)至?DexClassLoader的使用 - hongbochen1223的專欄 - CSDN博客
創(chuàng)建DexClassLoader 需要四個(gè)參數(shù)堕扶,分別代表的意義:
1:dexPath,指目標(biāo)類所在的APK或jar文件的路徑.類裝載器將從該路徑中尋找指定的目標(biāo)類,該類必須是APK或jar的全路徑.如果要包含多個(gè)路徑,路徑之間必須使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)獲得.
2:dexOutputDir,由于dex文件被包含在APK或者Jar文件中,因此在裝載目標(biāo)類之前需要先從APK或Jar文件中解壓出dex文件,該參數(shù)就是制定解壓出的dex 文件存放的路徑.在Android系統(tǒng)中,一個(gè)應(yīng)用程序一般對(duì)應(yīng)一個(gè)Linux用戶id,應(yīng)用程序僅對(duì)屬于自己的數(shù)據(jù)目錄路徑有寫的權(quán)限,因此,該參數(shù)可以使用該程序的數(shù)據(jù)路徑.
3:libPath,指目標(biāo)類中所使用的C/C++庫(kù)存放的路徑
4:最后一個(gè)參數(shù)是指該裝載器的父裝載器,一般為當(dāng)前執(zhí)行類的裝載器
這里我們可以把libPath置為null碍脏。
創(chuàng)建Resouces時(shí)我們需要AssetManager梭依。
通過(guò)源碼我們可以查看到addAssetPath這個(gè)方法和AssetManager的構(gòu)造方法注釋上面都有{@hide},這個(gè)表示我們不能在應(yīng)用層直接調(diào)用,如果我們還是要調(diào)用的話就需要用到反射:
寫完工具類,現(xiàn)在我們轉(zhuǎn)到宿主的MainActivity中典尾,我們需要跳轉(zhuǎn)到特定的activity中役拴,將ActivityName傳遞給proxyActivity。那么我們要怎么得到這個(gè)類名呢急黎?
我們?cè)赑roxyManager中通過(guò)context得到packmanager從而獲取
最后我們將插件apk導(dǎo)出來(lái)扎狱,然后放入宿主項(xiàng)目中的assert文件夾底下,寫入我們手機(jī)sd卡下勃教,這一步模擬的是插件apk的下載
好了淤击,結(jié)束了以上的步驟我們就可以運(yùn)行下看看到底跳是否成功?
然后點(diǎn)擊了宿主頁(yè)面后確實(shí)一片空白故源!不過(guò)索性沒(méi)有出現(xiàn)頁(yè)面崩潰和無(wú)法跳轉(zhuǎn)的現(xiàn)象污抬,這說(shuō)明我們的做法是沒(méi)有問(wèn)題。那么問(wèn)題到底出來(lái)哪里呢绳军?
我再來(lái)看下這個(gè)代理類印机。似乎沒(méi)什么問(wèn)題? 實(shí)際上我們界面顯示以及顯示后能夠交互是走了生命周期的onStart()以及onResume的方法门驾,換句話說(shuō)這個(gè)代理類實(shí)際上就是一個(gè)空殼 顯示不了界面射赛。
那么我們就得去執(zhí)行它的生命周期方法,至少要執(zhí)行 onCreate()奶是、onStart()以及onResume楣责。我們同樣的可以通過(guò)反射的方式得到這個(gè)Activity以及它的構(gòu)造方法。
我們可以看到object被強(qiáng)轉(zhuǎn)成了一開始的 interface,實(shí)際上這里的生命周期都可以通過(guò)反射的方式去執(zhí)行聂沙,但是一開始我們就制定了interface的規(guī)則秆麸,所以這個(gè)時(shí)候這個(gè)規(guī)則就派上了用場(chǎng)?
我們重新運(yùn)行下代碼看下情況。
以上就是插件化的一個(gè)小例子及汉,其實(shí)它的用到的無(wú)外乎是反射沮趣,DexClassLoader等機(jī)制。
動(dòng)態(tài)換膚:https://blog.csdn.net/MAGIC_JSS/article/details/52403778?utm_source=blogxgwz5