標籤:
Android Small外掛程式化架構源碼分析目錄
概述
Small如何使用
外掛程式載入流程
待改進的地方
一、概述
Small是一個寫得非常簡潔的外掛程式化架構,工程源碼位置:https://github.com/wequick/Small
外掛程式化的方案,說到底要解決的核心問題只有三個:
1.1 外掛程式類的載入
- 這個問題的解決和其它外掛程式化架構的解決方案差不多。Android的類是由DexClassLoader載入的,通過反射可以將外掛程式包動態載入進去。Small的gradle外掛程式產生的是.so包,在初始化的時候會通過.so檔案產生.zip檔案,再由.zip檔案產生一個dex元素,反射添加到宿主類載入器的dexPathList裡。
1.2 外掛程式資源的處理
- 這裡各外掛程式化架構解決辦法一般有兩種想法:一是外掛程式間不共用資源訪問,辦法就是每個外掛程式產生一個AssertManager來訪問它自己的資源,這樣就不會存在資源id衝突的問題;另一種是大家都共用一個AssetManager,這樣外掛程式的資源是共用的,可以相互訪問,但是要解決資源id衝突的問題。Small採用的是後者,通過修改aapt的產生產物解決了資源id衝突問題,由於共用資源訪問,可以做到極小或者根本沒有資源冗餘,從而減小外掛程式包的大小;
1.3 Activity註冊和生命週期問題
*大部分外掛程式化架構解決辦法都是採用在宿主工程裡預先註冊Activity占坑,然後通過占坑Activity將生命週期回調傳回給外掛程式Activity的方式。這裡Small處理的比較有特色,通過替換 ActivityThread 裡的mInstrumentation,在Instrumentation的newActivty實現裡面執行個體化了外掛程式Activity,通過較小改動就能完全解決生命週期回調的問題。
Small的功能模組主要有:
gradle-small外掛程式:Small中的一個gradle自訂外掛程式,用於打包組件;
small library:提供給使用者使用的Android Library,主要提供外掛程式載入,解析等功能;
二、使用Small
2.1工程命名
首先Small對工程名稱如下要求:
- app:host工程
- app.*:app外掛程式工程;
- lib.*:library外掛程式工程;
- web.*:web外掛程式工程;
- 其他:其他assert 工程;
2.2 外掛程式引入
在host工程的rootProject的build.gradle中需要引入small外掛程式;
引入small外掛程式後,預設幫你的所有工程引入了一個library依賴small,我們通過small提供的各種介面來實現外掛程式化得一些功能,比如載入外掛程式,開啟某個外掛程式中的ui介面,建立某個外掛程式提供的fragment對象等;
2.3 外掛程式聲明
作為host程式,要做的最重要的事情就是外掛程式管理,外掛程式跳轉uri聲明:
外掛程式聲明在host程式的assert/bundle.json中聲明,格式如下:
{ "version": "1.0.0", "bundles": [ { "uri": "lib.utils", "pkg": "net.wequick.example.small.lib.utils" }, { "uri": "lib.style", "pkg": "com.example.mysmall.lib.style" }, { "uri": "main", "pkg": "net.wequick.example.small.app.main" }, { "uri": "home", "pkg": "net.wequick.example.small.app.home", "rules"{ "page1",".MyPage1", "page2","net.wequick.example.small.app.home.MyPage2" } }, { "uri": "message", "pkg": "net.wequick.example.small.app.message" }, { "uri": "find", "pkg": "net.wequick.example.small.app.find" }, { "uri": "mine", "pkg": "net.wequick.example.small.app.mine" }, { "uri": "detail", "pkg": "net.wequick.example.small.app.detail" }, { "uri": "about", "pkg": "net.wequick.example.small.web.about" } ]}
bundles 中的每個元素都是一個外掛程式的聲明;
{ "uri": "home", "pkg": "net.wequick.example.small.app.home", "rules"{ "page1",".MyPage1", "page2","net.wequick.example.small.app.home.MyPage2" } }
在採用small架構的應用中,跳轉外掛程式的介面都是通過uri來指定的,也就是一個uri唯一對應一個外掛程式;
pkg是外掛程式的包名;
rules:如果外掛程式提供了多個介面供其他人使用,我們需要通過rules將它們區分開來;
舉個例子:
Small.openUri("home", context);
上面這行語句是開啟一個外掛程式的介面,home對應的就是上面的uri欄位,我們通過home,尋找到對應的外掛程式,然後它會開啟這個外掛程式在AndroidManifest.xml中聲明的第一個Activity。
如果你要調起外掛程式中聲明的其他acitivity,你就需要用到rules了,首先是在bundles.json中聲明你要跳轉的acitivity,如上,如果你想調用home外掛程式中的介面activity MyPage1,
你只需要寫如下語句:
Small.openUri("home/page1", context);
這個時候調用到的就是net.wequick.example.small.app.home.MyPage1這個類對應的activity了
你在調用外掛程式的時候也可以通過queryparameter傳參:
Small.openUri("home?from=main", context);在調起的外掛程式工程擷取參數:Uri uri = Small.getUri(this);if (uri != null) { String from = uri.getQueryParameter("from"); // Do stuff by `from‘}
2.4 外掛程式載入管理
在host中我們一般要做兩件事情:
初始化外掛程式的baseUri;
這個一般在Application的onCreate完成:Small.setBaseUri(“http://m.wequick.net/demo/“);
另外是載入所有外掛程式(待最佳化,一次載入所有外掛程式過於耗時,我們應該是只載入必備外掛程式,然後再慢慢載入其他外掛程式)
Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() {
@Override
public void onComplete() {
mContentView.postDelayed(new Runnable() {
@Override
public void run() {
Small.openUri(“main”, LaunchActivity.this);
finish();
}
}, 2000);
}
});
setUp提供了外掛程式載入完成的回調,一般我們等外掛程式載入完才能通過openUri去啟動介面展示;
2.5 常用操作
開啟介面:
Small.openUri(“main”, context);
建立外掛程式提供的fragment :
Fragment fragment = Small.createObject(“fragment-v4”, “home”, context);
如果沒有通過rules指定類名,預設的類名是 包名.MainFragment
如果指定了類名,和前面的規則一樣。
createObject的第一個參數目前僅支援”fragment”或者”fragment-v4”
擷取某個外掛程式介面的Intent
有時候我們不是直接開啟介面,比如通知欄通知,我們需要設定一個PendingIntent ,那這個時候需要的是一個Intent
此時可通過擷取:
Intent intent = Small.getIntentOfUri("main",context)
//調用Small.openUri("home?from=main", context);//參數擷取;Uri uri = Small.getUri(this);if (uri != null) { String from = uri.getQueryParameter("from"); // Do stuff by `from‘}
三、外掛程式載入流程
Small的核心類比較少,主要包含三類:
- Small:介面類,提供使用者能使用的各類介面;
- Bundle: 代表外掛程式,儲存了外掛程式的全部資訊,控制了外掛程式的load流程,以及lauch流程;它會調用各類BundleLauncher來幹活;
- BundleLauncher:有多個子類,比如.app.,.lib.類的外掛程式,對應的是ApkBundleLauncher,.web.*對應的就是WebBundleLauncher,其他對應的就是ActivityLauncher
外掛程式相關的操作主要有load和lauch:
其中應用啟動的時候要準備外掛程式環境,進行的就是load操作,主要是解析外掛程式資訊並緩衝起來,並將外掛程式dex和資源添加到host;load完成才能進行其他外掛程式操作
- 在load外掛程式的時候,一般分兩步:
preloadBundle,一般判斷外掛程式能否被載入,返回false就不需要進行載入了;這裡我們一般進行外掛程式合法性檢查
loadBundle(Bundle bundle):真正載入解析外掛程式的各類資訊並存入Bundle對象;
lauch指的是一般指載入外掛程式介面,典型的就是調用Small.openUri開啟外掛程式介面;
- lauch外掛程式的時候也分兩步:
prelaunchBundle(Bundle bundle):準備Bundle的一些必要資訊:一般是產生bundle的intent資訊,主要是要啟動的類名的產生;(見ApkBundleLauncher)
launchBundle(Bundle bundle, Context context):判斷外掛程式是否能載入,能載入就啟動acitivity;
3.1 load的流程
Small.setup --> Bundle.setupLaunchers (調用各BundleLauncher的setUp,其中ApkBundleLauncher的setUp會替換掉ActivityThread的mInstrumentation成員變數) -->Bundle.loadLaunchableBundles(解析bundle.json,並載入bundle)-->Bundle.loadManifest(讀取了bundle.json) --> Bundle.loadBundles ( 開啟非同步線程進行載入)非同步線程裡面的流程: -->Bundle.prepareForLaunch -->遍曆ActivityLauncher,WebBundleLauncher,ApkBundleLauncher,調用它們的BundleLauncher.resolveBundle 方法尋找合適的BundleLauncher解析外掛程式包; -->BundleLauncher.preloadBundle(主要是判斷能否載入) -->BundleLauncher.loadBundle(正真的載入解析動作)除了直接使用的三個BundleLauncher類(ActivityLauncher,WebBundleLauncher,ApkBundleLauncher),還有兩個中間類,SoBundleLauncher 和AssetBundleLauncher,其中SoBundleLauncher主要是提供了一個preloadBundle函數實現,裡面實現了1 按支援的type與package名對比,快速判斷此BundleLauncher能否解析此外掛程式;2 校正外掛程式簽名是否合法來確定是否要解析次外掛程式;AssetBundleLauncher從SoBundleLauncher繼承,幹了WebBundleLauncher 要做的loadBundle大部分的活。
3.2 啟動介面流程
Small.openUri --> Bundle.getLaunchableBundle -->Bundle.matchesRule(通過uri匹配合適的bundle) --> Bundle.launchFrom-->ApkBundleLauncher.launchBundle-->ApkBundleLauncher.prelaunchBundle-->BundleLauncher.launchBundle->Activity.startActivityForResult -->ApkBundleLauncher.InstrumentationWrapper.execStartActivity--》ApkBundleLauncher.InstrumentationWrapper.wrapIntent(將外掛程式activity類儲存在intent的category中,同時將intent的component裡面的類替換為host 中聲明的佔位Activity,以通過ActivityManager的檢查)--》ApkBundleLauncher.InstrumentationWrapper.newActivity(取出intent中的外掛程式activity類,並執行個體化返回,用於接收生命週期回調) --》ApkBundleLauncher.InstrumentationWrapper.callActivityOnCreate(加入外掛程式apk到AssertManager中,用於讀取外掛程式資源,應用外掛程式Theme)
3.3 外掛程式打包過程
todo
四、需要改進的點
載入外掛程式優先順序
目前load外掛程式的時候是把所有外掛程式都載入進來才算準備好,應該改為只載入必備外掛程式就可以發準備好回調讓主流程繼續跑,其他外掛程式在後台繼續載入;
最佳化通過PackageManager擷取包內Activity資訊
經實際測試,通過PackageManager擷取包內Activity資訊會耗時很大(700ms),但是載入外掛程式又需要外掛程式包裡的activity的資訊,擬通過在打包時提取相應資訊放入檔案,載入外掛程式只需要讀取檔案解析就行了;
外掛程式合法性校正
目前的外掛程式合法性是通過包的簽名對比來實現的,也是調用的PackageManager,效率比較低.
字串資源超過128編譯報錯問題
目前發現外掛程式處理資源的時候,如果工程中存在strings.xml中有字串資源超過128字元就會報錯,還有存在字串樣式的也會有問題;應該是作者對資源索引表中的StringPoll結構理解有誤導致。
嵌入式企鵝圈原創團隊由阿里、魅族、nvidia、龍芯、炬力、拓爾思等資深工程師組成。百分百原創,每周兩篇,分享嵌入式、Linux、物聯網、GPU、Android、自動駕駛等技術。歡迎掃碼關注公眾號:嵌入式企鵝圈,即時推送原創文章!
Android Small外掛程式化架構源碼分析