Android插件化入門

Android插件化入門

插件化是什么

用通俗易懂的話就是隔缀,它就像我們的U盤煮剧,可以機(jī)時(shí)的插在個(gè)電腦闻镶。不單單的U盤甚脉,還有顯示器,顯卡和CPU等電腦配件都是可以插入主板提供的接口铆农,這些電腦組件通過主板提示的接口組合在一起就可以組成一部具有完整功能的電腦牺氨。

即然電腦可以以這種形式進(jìn)行組裝,哪我們android程序是不是也可以這樣墩剖?答案是肯定的猴凹,我們的各個(gè)獨(dú)立的功能模塊都可以打包成apk,讓宿主程序把a(bǔ)pk加載進(jìn)來岭皂,再運(yùn)行里面的各個(gè)activity,service等

插件化的分類

插件化在技術(shù)難度上可以為分兩種:獨(dú)立插件化和非獨(dú)立插件

  • 非獨(dú)立插件是宿主程序與插件發(fā)開約定好插件開發(fā)規(guī)則郊霎,插件開發(fā)者要遵循這個(gè)規(guī)劃進(jìn)行開發(fā),這種方式要求的技術(shù)難度相對來說低很多爷绘,但是增加了開發(fā)者的調(diào)用成本书劝。類似比較成熟的解新局面方案有Small,需要說明的是small支持Android和iOS兩個(gè)平臺(tái)

  • 獨(dú)立插件完全支持android的兼容四大組件的大部份的屬性,這種方式對于開發(fā)者可以說是完全透明,是十分完善的插件化解決方案土至。但是偏寫這類框架需要對android底層的代碼非常熟悉购对,要對種種api進(jìn)行hook處理,所以技術(shù)要求也非常的高陶因。類似比較成熟的方案有DroidPlugin等等

插件化優(yōu)缺點(diǎn)

  1. 優(yōu)點(diǎn)

    • 模塊間的解耦

    • 解除單個(gè)dex方法65535的限制

    • 動(dòng)態(tài)更新骡苞,使我們的運(yùn)營更加的靈活

  2. 缺點(diǎn)

    • 增加了程序開發(fā)的復(fù)雜度

    • 技術(shù)門檻更高

非獨(dú)立插件原理

因?yàn)槲覀兿螺d的apk里面activity沒有在宿入的manifest.xml注冊,如果我們直接調(diào)用startActivity方法楷扬,就會(huì)報(bào)activity沒有注冊的異常烙如。我們可以在宿主activity中先注冊一個(gè)代理Activity,然后通過宿主activity去調(diào)用插件里面的activity的方法毅否。

哪我們怎樣去加載我們插件apk里面的類呢亚铁?下面讓我們了解下java里面幾個(gè)類加載器:

  • DexClassLoader :可以加載文件系統(tǒng)上的jar、dex螟加、apk

  • PathClassLoader :可以加載/data/app目錄下的apk徘溢,這也意味著,它只能加載已經(jīng)安裝的apk

  • URLClassLoader :可以加載java中的jar捆探,但是由于dalvik不能直接識(shí)別jar然爆,所以此方法在android中無法使用,盡管還有這個(gè)類

由上面的解釋可以了解到我們可以通過DexClassLoader把插件apk里面的類加載出來讓我們使用黍图。

非獨(dú)立插件實(shí)現(xiàn)

首先我們先摸擬apk下載的流程曾雕,把a(bǔ)ssets目錄下面的apk下載下來,存放在緩存目錄中,但是下載下來我們還不能直接使用助被,我們還要對apk包進(jìn)行以下處理

解密我們下載下來的apk包

校驗(yàn)apk的簽名是否正確

校驗(yàn)apk包所需的權(quán)限是否在主包中都包函


File mApkPath = this.getDir(ProxyActivity.APK_PATH, MODE_PRIVATE);

try {
    String mApkName = "myapplication-release-unsigned.apk";
    InputStream inputStream = getResources().getAssets().open(mApkName);

    byte[] datas = FileUtil.readFromInputStream(inputStream);

    String fullPath = mApkPath.getAbsolutePath() + File.separator + mApkName;
    FileUtil.writeByteToFile(datas, new File(fullPath));
} catch (IOException e) {
    e.printStackTrace();
}

然后我們就啟動(dòng)插件剖张,但是由于非獨(dú)立插件開發(fā)的時(shí)切诀,宿主程序?qū)Σ寮K是沒有用引的,顯示啟動(dòng)啟件的方式顯然是行不通的搔弄,所以只能通過隱式調(diào)用,具體調(diào)用如下幅虑。


Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra(ProxyActivity.APK_FILE_PATH, "myapplication-release-unsigned.apk");
intent.putExtra(ProxyActivity.ACTIVITY_NAME, "com.sundar.myapplication.LoginActivity");
startActivity(intent);

細(xì)心的讀者或者己經(jīng)發(fā)現(xiàn),上面即然提到我們宿主程對插件是沒用引用的顾犹,那為什么intent創(chuàng)建時(shí)我們會(huì)帶上ProxyActivity的類呢倒庵?

其實(shí)我們是通過代理Activity來對插件apk進(jìn)行加載,然后通過宿主ProxyActivity來實(shí)現(xiàn)插件的生命周期調(diào)用炫刷,下面給出實(shí)現(xiàn)代碼


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    try {
        Intent intent = getIntent();
        mApkName = intent.getStringExtra(APK_FILE_PATH);
        mActivityName = intent.getStringExtra(ACTIVITY_NAME);
    } catch (Exception ignore) {
        // 
    }
    
    // 創(chuàng)建相關(guān)路徑
    createFile();
    
    
    // 把當(dāng)前的apk放到資源查找目錄中
    mCustomAssetManager = new CustomAssetManager();
    mCustomAssetManager.addAssetPath(fullPath);
    
    // 創(chuàng)建classLoader加載類
    // dex解壓釋放后的目錄
    File dexOutputDir = getDir(DEX_OP, 0);
    
    // apk存放的路徑
    String fullPath = mApkPath.getAbsolutePath() + File.separator + mApkName;
    
    // 定義DexClassLoader
    // 第一個(gè)參數(shù):是dex壓縮文件的路徑
    // 第二個(gè)參數(shù):是dex解壓縮后存放的目錄
    // 第三個(gè)參數(shù):是C/C++依賴的本地庫文件目錄,可以為null
    // 第四個(gè)參數(shù):是上一級的類加載器
    mDexClassLoader = new DexClassLoader(fullPath, dexOutputDir.getAbsolutePath(), mSoPath.getAbsolutePath(), getClassLoader());
    
    // 通過代理的方法去生成Activity類
    try {
        Class<PluginActivity> pluginActivityClass = (Class<PluginActivity>) mDexClassLoader.loadClass(mActivityName);
        mPluginActivity = pluginActivityClass.newInstance();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // 加載類錯(cuò)誤擎宝,需要顯示錯(cuò)誤信息給用戶
    // 此類是插件activity
    if (mPluginActivity == null) {
        return;
    }
        
    //進(jìn)行必要的初始化
    mPluginActivity.setmBaseActivity(this);
    // 實(shí)現(xiàn)對插件activity方法進(jìn)行調(diào)用
    mPluginActivity.onCreate(savedInstanceState);
}


下面是創(chuàng)建插件Activity所需要的AssetsAssetManager用于加載插件資源的


public CustomAssetManager() {
    try {
        this.mAssetManager = AssetManager.class.newInstance();
        this.mAddedAssetsPath = new HashMap<>();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 添加apk的資源路徑放到AssetManager里面
 */
public void addAssetPath(String apkPath) {
    if (mAssetManager == null) {
        return;
    }

    //先判斷Map里面有沒有己經(jīng)添加的資源
    if (mAddedAssetsPath.containsKey(apkPath)) {
        return;
    }
    try {
        AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
                mAssetManager, apkPath);
        mAddedAssetsPath.put(apkPath, apkPath);
    } catch (Exception e) {
        e.printStackTrace();
    }
}



下面是我們插件Activity類的主要方法,大家或許會(huì)覺得好寄為什么要重寫activity的方法呢浑玛?
答案是因?yàn)槲覀內(nèi)绻恢貙懼苯釉O(shè)置就會(huì)報(bào)找不到相應(yīng)的資源的異常绍申。那我們是不是必須要重寫這個(gè)方法呢?其實(shí)不是锄奢,我們可以把我們的插件apk添加到宿主apk的assetsManager里面,但是這里還會(huì)涉到到資源沖突的問題剧腻,具體解決資源沖突的方式可以自行百度


@Override
public void setContentView(@LayoutRes int layoutResID) {
    Resources resources = mBaseActivity.getmCustomAssetManager().getBundleResource(mBaseActivity);
    XmlResourceParser xmlResourceParser = resources.getLayout(layoutResID);
    View view  = LayoutInflater.from(mBaseActivity).inflate(xmlResourceParser, null);
    mBaseActivity.setContentView(view);
}

下面就是我們的效果圖

6037478-09ae3fe1efed2f4d.jpg
472BD804-6650-49DB-9036-45D844A50881.png

非獨(dú)立插件實(shí)現(xiàn)總結(jié)

以上就是非獨(dú)立插件的實(shí)現(xiàn)過程拘央,但是上面只是做了實(shí)現(xiàn)的原理,在做demo過程中书在,我遇到幾個(gè)坑

  1. 因?yàn)閍ndroid是通過AssetsManager去加載資源的灰伟,此時(shí)如果在配置文件中使用資源id去引用資源,系統(tǒng)則會(huì)拋出找不到資源的異常儒旬,而我們現(xiàn)在只能自己創(chuàng)建AssetsManager去獲取apk包的資源

  2. 從上面的代碼可以知道栏账,插件的Activity的生成周期的調(diào)用只能通過代理Activity方法去調(diào)用

  3. 插件模塊開發(fā)增加難度,插件開發(fā)者必須要尊守開發(fā)的規(guī)則

后續(xù)要做的事情

是否可以參考動(dòng)態(tài)換膚的機(jī)制來實(shí)現(xiàn)對插件的資源進(jìn)行加載栈源,這樣我們就可以直接使用配置文件里的資源挡爵,具體可以參考http://www.reibang.com/p/af7c0585dd5b

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市甚垦,隨后出現(xiàn)的幾起案子茶鹃,更是在濱河造成了極大的恐慌,老刑警劉巖艰亮,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闭翩,死亡現(xiàn)場離奇詭異,居然都是意外死亡迄埃,警方通過查閱死者的電腦和手機(jī)疗韵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侄非,“玉大人蕉汪,你說我怎么就攤上這事流译。” “怎么了肤无?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵先蒋,是天一觀的道長。 經(jīng)常有香客問我宛渐,道長竞漾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任窥翩,我火速辦了婚禮业岁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寇蚊。我一直安慰自己笔时,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布仗岸。 她就那樣靜靜地躺著允耿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扒怖。 梳的紋絲不亂的頭發(fā)上较锡,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音盗痒,去河邊找鬼蚂蕴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛俯邓,可吹牛的內(nèi)容都是我干的骡楼。 我是一名探鬼主播工秩,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼骚亿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了誉券?” 一聲冷哼從身側(cè)響起朦蕴,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吃嘿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后梦重,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兑燥,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年琴拧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了降瞳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挣饥,靈堂內(nèi)的尸體忽然破棺而出除师,到底是詐尸還是另有隱情,我是刑警寧澤扔枫,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布汛聚,位于F島的核電站,受9級特大地震影響短荐,放射性物質(zhì)發(fā)生泄漏倚舀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一忍宋、第九天 我趴在偏房一處隱蔽的房頂上張望痕貌。 院中可真熱鬧,春花似錦糠排、人聲如沸舵稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哺徊。三九已至,卻和暖如春乾闰,著一層夾襖步出監(jiān)牢的瞬間落追,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工汹忠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淋硝,地道東北人雹熬。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓宽菜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親竿报。 傳聞我的和親對象是個(gè)殘疾皇子铅乡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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