在學(xué)習(xí)插件化之前需要看前面幾篇文章:
- Android進(jìn)階解密①—activity的啟動(dòng)過(guò)程
- Android進(jìn)階解密②—Service的啟動(dòng)
- Android進(jìn)階解密③—Hook
動(dòng)態(tài)加載技術(shù):
在程序運(yùn)行時(shí),動(dòng)態(tài)加載一些程序中原本不存在的可執(zhí)行文件并運(yùn)行起來(lái)佛嬉,疾棵,隨著應(yīng)用技術(shù)的發(fā)展,動(dòng)態(tài)加載技術(shù)逐漸派生出兩個(gè)分支砰琢,熱修復(fù)和插件化;
- 熱修復(fù):用于修復(fù)bug
- 插件化:解決應(yīng)用龐大,功能模塊解耦,復(fù)用其他apk的代碼
插件化思想:
將復(fù)用的apk作為插件佩厚,插入另一個(gè)apk中,比如淘寶中會(huì)有咸魚(yú)的頁(yè)面说订,用淘寶為咸魚(yú)引流抄瓦,使用插件化技術(shù),可以直接使用咸魚(yú)apk中的dex文件陶冷,這樣省去再次開(kāi)發(fā)一套咸魚(yú)頁(yè)面的成本钙姊,并且有效的降低了淘寶apk的耦合度;
Activity插件化原理:
插件化activity的目的是直接使用另一個(gè)apk的activity埂伦,而activity的啟動(dòng)和生命周期的管理需要經(jīng)過(guò)AMS的處理煞额,另一個(gè)apk的activity沒(méi)有在本項(xiàng)目的manifest注冊(cè),肯定是無(wú)法通過(guò)的,所以我們需要hook startActivity的流程立镶,繞過(guò)ams的驗(yàn)證,可以在本項(xiàng)目使用一個(gè)占坑activity类早,在發(fā)送給ams前將插件activity換成占坑activity去通過(guò)ams的驗(yàn)證媚媒,驗(yàn)證好以后在真實(shí)的啟動(dòng)時(shí)再將插件activity換回來(lái);
步驟:
- 事先在本項(xiàng)目準(zhǔn)備好占坑activity
- 使用占坑activity繞過(guò)ams驗(yàn)證
- 還原插件activity
1. 準(zhǔn)備占坑activity
直接在原項(xiàng)目準(zhǔn)備一個(gè)空白的activity即可涩僻,記得必須在manifest注冊(cè),下文叫他SubActivity
2. 使用插件activity替換占坑activity
在交給ams進(jìn)程驗(yàn)證之前缭召,在用戶進(jìn)程會(huì)經(jīng)過(guò)兩個(gè)類(lèi)的傳遞,Instrumentation逆日, iActivityManager嵌巷,者兩個(gè)類(lèi)都可以作為hook點(diǎn),這里介紹hook iActivityManager的這種方法室抽;
2.1 創(chuàng)建hook點(diǎn)的代理類(lèi)搪哪,iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())){
// 首先找到,原本需要啟動(dòng)的插件activity的原始intent
Intent originIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
originIntent = (Intent) args[i];
index = i;
break;
}
}
// 新建欺騙ams的占坑activity的intent
Intent fakeIntent = new Intent();
fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
// 將真實(shí)的intent保存在fakeIntent中用于第三步的還原操作
fakeIntent.putExtra("real_intent",originIntent);
// 將fakeIntent寫(xiě)回原來(lái)的arges數(shù)組中
args[index] = fakeIntent;
}
return method.invoke(realActivityManager,args);
}
}
這里使用的動(dòng)態(tài)代理創(chuàng)建iActivityManager的代理坪圾,首先找到原本啟動(dòng)的插件Activity的Intent晓折,然后新建一個(gè)啟動(dòng)SubActivity的intent替換它;
2.2替換原本的iActivityManager:
public void hookAMS() throws Exception {
// 獲取ActivityManager getService 返回的單例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通過(guò)單例.get()獲取iActivityManager, 這兩步需要參考源碼的iActivityManager的獲取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成動(dòng)態(tài)代理對(duì)象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 將代理對(duì)象設(shè)置到單例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
- 這個(gè)方法需要在startActivity前調(diào)用
3. 還原插件Activity
繞開(kāi)ams驗(yàn)證后兽泄,我們還需要真實(shí)的啟動(dòng)TargetActivity漓概,再學(xué)習(xí)了Handler機(jī)制后,我們知道m(xù)essage的處理順序是首先會(huì)判斷當(dāng)前message.callback有沒(méi)有邏輯病梢,會(huì)首先執(zhí)行callback胃珍;我們可以將Message作為Hook點(diǎn)
3.1 創(chuàng)建自定義CallBack,在handleMessage處理前蜓陌,將fakeIntent換成真實(shí)的intent
class MCallBack implements android.os.Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
try {
Object activityClientRecord = msg.obj;
// 獲取fakeIntent
Class acrClazz = activityClientRecord.getClass();
Field intentField = acrClazz.getDeclaredField("intent");
Intent intent = (Intent) intentField.get(activityClientRecord);
// 取出targetActivity的Intent
Intent realIntent = intent.getParcelableExtra("real_intent");
// 將realIntent的內(nèi)容設(shè)置到fakeIntent
intent.setComponent(realIntent.getComponent());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
msg.getTarget().handleMessage(msg);
return true;
}
}
3.2 hook ActivityThread觅彰, 修改主線程的H(Handler)的CallBack屬性,原理參考dispatchMessage方法
private void hookActivityThread() throws Exception {
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
Object activityThreadInstance = singleInstanceField.get(null);
Field mHField = activityThreadClass.getDeclaredField("mH");
Handler handler = (Handler) mHField.get(activityThreadInstance);
// 修改handler 的callback
Class handlerClazz = handler.getClass();
Field callbackField = handlerClazz.getDeclaredField("mCallback");
callbackField.set(handler,new MCallBack());
}
在Handler機(jī)制中有兩個(gè)callback护奈,一個(gè)是Handler.mCallback缔莲,一個(gè)是Message.callback
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在loop中輪詢處理message的時(shí)候會(huì)調(diào)用dispatchMessage;如果Message.callback不會(huì)null就處理Runnable的回調(diào)然后結(jié)束霉旗,如果msg.callback為null痴奏,則先執(zhí)行Handler的mCallback,并根據(jù)Handler的mCallback.handleMessage的返回值判斷是否執(zhí)行Handler.handleMessage;
根據(jù)上面的流程厌秒,我們可以在ActivityThread的H處理startActivity這個(gè)Message的handleMessage前读拆,在H的Callback中插入修改intent的代碼,做到真實(shí)的開(kāi)啟TargetActivity
3.3 插件Activity的生命周期管理:
上面的操作只做到了開(kāi)啟activity鸵闪,插件activity的生命周期是如何管理的檐晕,AMS通過(guò)token來(lái)對(duì)activity進(jìn)行識(shí)別管理,而插件activity token的綁定是不受影響的,所以插件activity是具有生命周期的辟灰;
Service插件化原理
代理分發(fā)實(shí)現(xiàn):
當(dāng)啟動(dòng)插件Service時(shí)个榕,就會(huì)先啟動(dòng)代理Service,當(dāng)代理Service運(yùn)行后芥喇,在其onStartCommand中啟動(dòng)插件Service西采;
步驟:
- 項(xiàng)目中準(zhǔn)備好代理Service
- hook iActivityManager 啟動(dòng)代理Service
- 代理分發(fā):
- ProxyService需要長(zhǎng)時(shí)間對(duì)插件Service進(jìn)行分發(fā),所以需要return START_STICKY ProxyService重新創(chuàng)建
- 創(chuàng)建插件Service继控,attach械馆,onCreate;
1. 在項(xiàng)目中創(chuàng)建一個(gè)ProxyService武通,在manifest中注冊(cè)霹崎;
2. hook iActivityManager,將要啟動(dòng)的TargetService換成ProxyService
2.1 創(chuàng)建自定義iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())){
Intent targetIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
targetIntent = (Intent) args[i];
index = i;
break;
}
}
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
proxyIntent.putExtra("target_intent",targetIntent);
args[index] = proxyIntent;
}
return method.invoke(realActivityManager,args);
}
}
2.2 hook AMS替換原來(lái)的IActivityManager 同上
public void hookAMS() throws Exception {
// 獲取ActivityManager getService 返回的單例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通過(guò)單例.get()獲取iActivityManager, 這兩步需要參考源碼的iActivityManager的獲取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成動(dòng)態(tài)代理對(duì)象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 將代理對(duì)象設(shè)置到單例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
只要在startService前調(diào)用這段代碼冶忱,就會(huì)啟動(dòng)proxyService尾菇,下面我們?cè)趐roxyService中對(duì)targetService進(jìn)行分發(fā);
2.3 在proxyService中啟動(dòng)TargetService:
- 調(diào)用attach綁定Context
- 調(diào)用onCreate
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
// 準(zhǔn)備attach方法的參數(shù)
Class activityThreadClazz = null;
activityThreadClazz = Class.forName("android.app.ActivityThread");
Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
// activityThread
Object activityThread = sCurrentActivityThreadField.get(null);
// applicationThread
Object applicationThread = getApplicationMethod.invoke(activityThread, null);
Class iInterFaceClazz = Class.forName("android.os.IInterface");
Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
asBinderMethod.setAccessible(true);
// token
Object token = asBinderMethod.invoke(applicationThread);
// iActivityManager
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);
// targetService
Class serviceClazz = Class.forName("android.app.Service");
Service targetService = (Service) serviceClazz.newInstance();
// attach
Method attachMethod = serviceClazz.getDeclaredMethod("attach");
attachMethod.invoke(targetService, this,
activityThread, intent.getComponent().getClassName(),
token, getApplication(), iActivityManager);
targetService.onCreate();
} catch (Exception e) {
e.printStackTrace();
}
return START_STICKY;
}
}
- 首先準(zhǔn)備attach需要的參數(shù)朗和,通過(guò)反射獲取
- 調(diào)用targetService的attach方法
- 調(diào)用targetService的onCreate方法
資源的插件化
參考這邊換膚文章:安卓換膚實(shí)現(xiàn)原理
某東的插件化實(shí)踐
1. 插件化做的事情:
插件化的目的就是在主工程使用插件工程的代碼/類(lèi)
和資源
1.1插件中四大組件的處理:
當(dāng)使用插件的四大組件類(lèi)時(shí)错沽,比如插件activity的使用必須做特殊的處理,一般的處理方式有:
- hook AMS眶拉,繞過(guò)AMS對(duì)四大組件的驗(yàn)證千埃,手動(dòng)管理生命周期
- 直接在主工程的manifest注冊(cè)插件的activity
方式1的特點(diǎn)是實(shí)現(xiàn)難度高,靈活性高忆植,但是隨著谷歌對(duì)于系統(tǒng)非sdk調(diào)用的限制放可,這個(gè)方式可能會(huì)在未來(lái)失效;
方式2的特點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單朝刊,但是不夠靈活耀里,必須在manifest中寫(xiě)死
某東采用的方式2,直接在manifest中寫(xiě)死插件的四大組件注冊(cè)
1.2插件中類(lèi)的加載和使用:
每一個(gè)插件都設(shè)置一個(gè)ClassLoader拾氓,目的是整個(gè)插件的類(lèi)都是由一個(gè)加載器加載冯挎,所有的插件的ClassLoader都在雙親委派中繼承了另一個(gè)ClassLoader,這個(gè)目的是便于主工程的統(tǒng)一管理咙鞍;
1.3DelegateClassLoader的替換:
替換LoadedAPK的ClassLoader為DelegateClassLoader即可房官;
1.4插件資源的引用
添加一個(gè)path給AssetManager就行,詳見(jiàn)上文
2. 插件如何打包進(jìn)主工程续滋,即主工程如何集成插件包:
- 將插件apk放入asset目錄翰守,通過(guò)assetManager去加載
- 將插件apk文件修改后綴為.so 放入lib/armeabi目錄,主工程apk在安裝的時(shí)候會(huì)自動(dòng)將這個(gè)目錄的文件加載到data/data/< package_name >/lib/目錄下疲酌,可以直接獲壤濉了袁;
某東使用的第二種以so的形式放入lib目錄自動(dòng)加載,因?yàn)樵谶\(yùn)行時(shí)去使用AssetManager加載asset資源會(huì)影響程序的運(yùn)行時(shí)速度
3. 插件和主工程如何通信
主要借鑒的airbnb的DeepLinkDispatch
DeepLinkDispatch類(lèi)似Android原生的scheme協(xié)議湿颅,用于跳轉(zhuǎn)到另一個(gè)APP的頁(yè)面载绿,比如在美團(tuán)中打開(kāi)高德地圖,或者在微信中打開(kāi)京東油航,
DeepLinkDispatch的實(shí)現(xiàn)思路:首先在插件工程中需要被打開(kāi)的Activity添加注解卢鹦,然后在主工程調(diào)用DeepLinkDispatch.startActivityDirect()
傳入注解中設(shè)置好的參數(shù),最后會(huì)通過(guò)系統(tǒng)的apiContext.startActivity()
開(kāi)啟頁(yè)面
4. 插件化的未來(lái)
隨著谷歌對(duì)于系統(tǒng)API的限制越來(lái)越嚴(yán)格劝堪,并且現(xiàn)在已經(jīng)分成黑名單,深灰名單揉稚,淺灰名單留時(shí)間開(kāi)發(fā)者調(diào)整秒啦,插件化應(yīng)該是沒(méi)有未來(lái)的,我們想想插件化到底是為了什么:
- 獨(dú)立編譯搀玖,提高開(kāi)發(fā)效率
- 模塊解耦余境,復(fù)用代碼
東東的解決方案:
組件化:
傳統(tǒng)的組件話是新建一個(gè)Android Library,在開(kāi)發(fā)調(diào)試和實(shí)際引用的時(shí)候在Application和Library之間切換灌诅,東東的組件化是將每一個(gè)組件單獨(dú)做成一個(gè)項(xiàng)目芳来,然后在項(xiàng)目結(jié)構(gòu)中保留Application和Android Library,library用于實(shí)現(xiàn)組件的功能猜拾,app用于開(kāi)發(fā)調(diào)試即舌,在主工程使用時(shí)直接通過(guò)依賴從云端的maven sync;