small的插件化
最近在看插件化的相關(guān)內(nèi)容童漩,就研究了一下非臣ㄖ裕火的small插件化(Github地址:https://github.com/wequick/Small), 這篇文章主要分析small插件化的Sample的主要的開發(fā)步驟,和插件化的基本的使用淘正,原理會在后來的文章中持續(xù)更新古徒。
1.插件化和組件化
組件化和插件化開發(fā)上其實(shí)大體思路是差不多拓提,但是還是有本質(zhì)上的區(qū)別。組件化開發(fā)就是將一個項(xiàng)目app拆分成多個模塊隧膘,每個模塊都是一個組件代态,組件化開發(fā)過程中相互依賴或單獨(dú)調(diào)試,最終發(fā)布的時候是將這些組件合并統(tǒng)一成一個apk疹吃。插件化開發(fā)也是將一個項(xiàng)目app拆分成多個模塊蹦疑,這些模塊包括宿主和插件。本質(zhì)上插件化開發(fā)的每個模塊相當(dāng)于一個apk萨驶,而組件化相當(dāng)于一個個lib歉摧。插件化最終發(fā)布的時候?qū)⑺拗鱝pk和插件apk單獨(dú)打包或者聯(lián)合打包,而組件化則是把所有的lib打包成一個apk。
2.插件化的作用
①.并發(fā)開發(fā) 各個模塊之間可以單獨(dú)開發(fā)叁温,極大地提高了開發(fā)效率
②.動態(tài)更新插件或者遠(yuǎn)端調(diào)試 app每次啟動回去校驗(yàn)是否有插件更新再悼,有的話,就去服務(wù)器上下載最新的插件替換掉已有的膝但。熱修復(fù)其實(shí)是插件化的一個小小的應(yīng)用冲九。
③.按需下載模塊 其實(shí)和2的思路一樣,也屬于動態(tài)加載的功能跟束,例如可能一個apk我們對于不同的賬號設(shè)置了不同的權(quán)限莺奸,相應(yīng)的就會有不同的功能模塊對于不同的權(quán)限人員。
④.可以針對不同的早期設(shè)計(jì)推出不同的版本冀宴,觀察用戶的使用反饋灭贷,來確定最終的版本。
3.small插件化的開發(fā)步驟(這兩種開發(fā)方式Github上都有相關(guān)的說明)
(1) 手動創(chuàng)建工程
①. Create Project
File->New->New Project...
②.加入Small編譯庫
1.加入classpath
classpath 'net.wequick.tools.build:gradle-small:1.0.0-alpha2'
apply plugin: 'net.wequick.small'
2.配置Small DSL (可選)
small {
aarVersion = '1.1.0-beta5'
}
③.創(chuàng)建Moudle
File->New->Module來創(chuàng)建插件模塊花鹅,需要滿足:
模塊名形如:app., lib.或者web.*
包名包含:.app., .lib.或者.web.
命名規(guī)則如下:
app 宿主工程
app.* 包含Activity/Fragment的組件
lib.* 公共庫組件
web.* 本地網(wǎng)頁組件
sign 簽名文件
為什么要這樣氧腰?因?yàn)镾mall會根據(jù)包名對插件進(jìn)行歸類,特殊的域名空間如:“.app.” 會讓這變得容易刨肃。
④. Configure UI route
右鍵app模塊->New->Folder->Assets Folder古拴,新建assets目錄,
右鍵assets目錄->New->File真友,新建bundle.json文件黄痪,加入:
{
"version": "1.0.0",
"bundles": [
{
"uri": "main",
"pkg": "com.example.mysmall.app.main"
}
]
}
使用uri定位,加載插件中的Activity或者Fragment
⑤.配置apk的簽名
⑥.在宿主App中初始化Small
Small.preSetUp(this);
⑦.加載插件
重載宿主Activity的onStart()方法盔然。
Small.setBaseUri("http://example.com/");
Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() {
@Override
public void onComplete() {
Small.openUri("main", LaunchActivity.this);//啟動默認(rèn)的Activity桅打,參考wiki中的UI route啟動其他Activity
}
});
(1)使用模板創(chuàng)建工程
①.導(dǎo)入工程模板
②.新建宿主Activity的時候選擇@Small模板,修改build.gradle文件引入small
buildscript {
dependencies {
classpath 'net.wequick.tools.build:gradle-small:1.0.0-alpha2'
}
}
apply plugin: 'net.wequick.small'
small {
aarVersion = '1.1.0-beta5'
}
③.新建插件的app愈案,在as中和普通的創(chuàng)建moudle的方式一樣
④.gradle進(jìn)行打包的相關(guān)操作]gradlew buildLib -q(準(zhǔn)備基礎(chǔ)庫) gradlew buildBundle -q(打包所有組件)挺尾。
⑤在插件的app中加載其他的插件頁面 Small.openUri("detail?from=app.home", getContext());
插件之間的通信用本地廣播。
4.small到底在做了什么站绪,可以讓我們動態(tài)的加載看似沒有太多聯(lián)系的apk中的文件遭铺?
難道我們所有的apk都被安裝了,打開手機(jī)的所有應(yīng)用程序并沒有發(fā)現(xiàn)有除了宿主之外的其他的apk被安裝恢准,打開我們的app工程也就是入口工程魂挂,可以看到我們正常開發(fā)有所不同的是,文件夾里多了一個smallLibs文件夾馁筐,可以看到里面有一個armeabi文件涂召,發(fā)現(xiàn)里面是.so文件,看一下文件的后綴名敏沉,app_main.so(對應(yīng)我們工程的app.main),app_home.so(對應(yīng)我們工程的app.home).....,其實(shí)就是通過buildLib和buildBundle來把非宿主的app中的文件打包為.so文件果正,我們最終安裝到手機(jī)上的apk其實(shí)就是只有宿主的apk和其內(nèi)部的.so文件炎码,通過加載.so文件來實(shí)現(xiàn)加載插件中的文件。但是當(dāng)我們安裝到電腦模擬器的過程中會出現(xiàn)一個異常舱卡,如下圖:
關(guān)于這一點(diǎn)是因?yàn)樵谖覀兊哪M器上的CPU架構(gòu)的問題辅肾,因?yàn)槲覀儎?chuàng)建模擬器的時候會有選擇讓我們選擇是選擇armeabi還是x86類型的模擬器队萤,目前手機(jī)處理器主要是高通和三星轮锥,MTK,華為等要尔,主要用的都是ARM公司的arm架構(gòu)舍杜,而英特爾公司也有自己的手機(jī)處理器,他用的是x86的架構(gòu)赵辕,目前很多平板產(chǎn)品在使用既绩。而small產(chǎn)生的.so文件默認(rèn)是產(chǎn)生在armeabi文件夾的,而我們創(chuàng)建模擬器的過程中还惠,一般都會選擇x86架構(gòu)的饲握,因?yàn)槲覀冸娔X的CPU都是x86架構(gòu)的,能讓模擬器的速度飛快蚕键,但是當(dāng)我們安裝的small的Sample的過程中救欧,則會出錯,所以我們的解決方法就是在libs目錄下新建一個x86的文件夾锣光,然后把我們aremabi文件夾下的.so文件全部復(fù)制到x86文件夾下就可以了笆怠。如下圖所示我們創(chuàng)建的模擬器時選擇架構(gòu)的問題:
我們自己開發(fā)的過程中,通過 gradlew buildLib -q(準(zhǔn)備基礎(chǔ)庫) gradlew buildBundle -q(打包所有組件) 的操作誊爹,我們也就可以看到在我們的宿主app的目錄下產(chǎn)生一個smallLibs目錄蹬刷,里面的armeabi的文件中就有有我們的非宿主的插件中的文件產(chǎn)生的.so文件。
5.small的動態(tài)加載和實(shí)現(xiàn)熱修復(fù)是如何實(shí)現(xiàn)的频丘?
我們打開Sample的主頁面
可以看到有一個更新的按鈕办成,但是點(diǎn)擊后好像并沒有什么效果,打開Sample工程中的相關(guān)按鈕操作的邏輯部分搂漠,有一個這樣的方法
private void requestUpgradeInfo(Map versions, OnResponseListener listener) {
System.out.println(versions); // this should be passed as HTTP parameters
mResponseHandler = new ResponseHandler(listener);
new Thread() {
@Override
public void run() {
try {
// Example HTTP request to get the upgrade bundles information.
// Json format see http://wequick.github.io/small/upgrade/bundles.json
URL url = new URL("http://wequick.github.io/small/upgrade/bundles.json");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
StringBuilder sb = new StringBuilder();
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
sb.append(new String(buffer, 0, length));
}
// Parse json
JSONObject jo = new JSONObject(sb.toString());
JSONObject mf = jo.has("manifest") ? jo.getJSONObject("manifest") : null;
JSONArray updates = jo.getJSONArray("updates");
int N = updates.length();
List<UpdateInfo> infos = new ArrayList<UpdateInfo>(N);
for (int i = 0; i < N; i++) {
JSONObject o = updates.getJSONObject(i);
UpdateInfo info = new UpdateInfo();
info.packageName = o.getString("pkg");
info.downloadUrl = o.getString("url");
infos.add(info);
}
// Post message
UpgradeInfo ui = new UpgradeInfo();
ui.manifest = mf;
ui.updates = infos;
Message.obtain(mResponseHandler, 1, ui).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
可以看到其內(nèi)部有一個url迂卢,然后我們打開url看一下里面是什么東西
可以看到底部有一個updates數(shù)組,然后數(shù)組內(nèi)部是.so文件的地址状答,然后我們在看一下代碼中的下載的部分冷守,下載的核心部分是requestUpgradeInfo()這個方法。
里面主要是通過http請求獲取服務(wù)器bundles.json這個文件惊科,然后去解析json.把信息存儲到UpgradeInfo里面拍摇。mResponseHandler完成回調(diào)
然后就是調(diào)用upgradeBundles方法。我們跟進(jìn)去看看馆截,這個方法具體做什么充活。
這個方法主要是校驗(yàn)服務(wù)器上bundles.json的信息蜂莉,然后開始下載插件和加載插件。插件下載完成后我們就實(shí)現(xiàn)了動態(tài)的更新操作混卵。
我們開發(fā)的過程中怎么實(shí)現(xiàn)插件化的動態(tài)更新呢映穗,我們可以在我們的原有的工程中直接修改代碼,使用small的命令的打包后取出.so文件幕随,然后將.so文件上傳到我們的目標(biāo)服務(wù)器地址就可以了蚁滋,我們的程序就可以實(shí)現(xiàn)動態(tài)的更新,其實(shí)這個.so文件你可以直接解壓赘淮,發(fā)現(xiàn)解壓后的格式和我們apk直接解壓后格式是一樣的辕录。
基本的用法就先到這里,small原理我們團(tuán)隊(duì)有不同的人在研究梢卸,后續(xù)可以更新走诞。