一京办、前言:
隨著APP到開發(fā)到后期眾多功能模塊的加入瞒津,方法數越來越多面臨著超出65535的可能。雖然谷歌后來提供了multidex分包功能,但它還是有一些局限性比如分包過大響應慢彪腔、占用過大內存、低版本兼容等問題进栽。
頻繁的增加新模塊更新每次都要重新下載APP德挣、出現(xiàn)緊急BUG需要修復,在這種時候使用插件化開發(fā)就很有必要了快毛。
在去年阿里的云棲大會上格嗅,阿里宣布他們使用的插件化框架Atlas將于今年年初開源。最近我在看關于插件開發(fā)的技術唠帝,還想著Atlas什么時候開源屯掖,沒過幾天就發(fā)現(xiàn)Atlas框架在這個月13號開源了。Atlas使用入門教程
上面都是題外話襟衰,本文主要是對以下兩款框架中代理應用解析:
二贴铜、功能比較:
(1)DynamicLoadApk
-
簡介:
插件不依賴宿主,但插件必須遵守一定的規(guī)則右蒲。
提供了宿主和插件交互的方式阀湿。
兼容性好很大程度是通過代理的方式進行插件化。
</br>
-
原理:
1瑰妄、通過反射調用AssetManager的addAssetPath方法陷嘴,將一個插件apk中的資源加載到AssetManager中,然后再通過AssetManager來創(chuàng)建一個新的Resources對象间坐,就可以通過這個Resources對象來訪問插件apk中的資源了灾挨。
2、在宿主Manifest中預注冊代理組件Activity竹宋,當啟動插件組件時首先啟動一個代理組件劳澄,然后通過這個代理組件來構建、啟動插件組件蜈七。
插件里的activity其實是通過反射初始化的普通對象是沒有生命周期的秒拔,通過代理activity去調用插件activity實現(xiàn)的生命周期方法。
(2)DroidPlugin
-
簡介:
宿主和插件完全隔離飒硅。
插件不依賴宿主可以獨立運行砂缩。
接入簡單,插件不需要修改三娩。
</br>
-
原理:
1庵芭、基于動態(tài)代理的Hook,劫持了系統(tǒng)的大部分與系統(tǒng)服務進程通訊的方法雀监,欺騙系統(tǒng)以為只有一個apk在運行双吆,瞞過插件讓其認為自己已經安裝。
2、基于Android的多個apk可以運行在同一個進程的原理好乐。
3匾竿、需要預注冊Activity等組件實現(xiàn)免注冊。
通過以上簡單的介紹曹宴,可以很清楚的發(fā)現(xiàn)它們的核心實現(xiàn)都是基于代理來完成核心功能的搂橙。
三歉提、代理:
代理主要分為動態(tài)代理與靜態(tài)代理笛坦,動態(tài)代理與靜態(tài)代理相比較,最大的區(qū)別是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理苔巨。
DynamicLoadApk通過靜態(tài)代理技術偽裝成Activity版扩,DroidPlugin通過動態(tài)代理技術Hook劫持了系統(tǒng)管理。
以下我以自己的理解以故事的形式來描述靜態(tài)代理與動態(tài)代理侄泽。
靜態(tài)代理以賣香煙為例:
- 小明去便利店買煙礁芦,店長讓店小二去取煙然后偷偷的取出假煙。在店小二把煙交給店長的時候店長用他嫻熟的技法掉了包并交給了小明悼尾,然而小明并不知道自己買了假煙反而歡喜的拿回家了柿扣。
- 這個故事里小明是客戶、便利店是代理接口提供賣煙的功能闺魏、店長是代理通過操作店小二并做一些手腳實現(xiàn)賣煙功能未状、店小二是委托類他負責去倉庫取煙。
這個故事里還有個問題析桥,小明再去這個店買假水果的時候司草,店長必須要重新學習掉包水果的技能很是麻煩。在這個時候動態(tài)代理站出來了泡仗。
動態(tài)代理以賣香煙為例:
- 小明活到了22世紀這時候都是無人售賣店埋虹,他又去買煙了。店長嫌每次學技術太麻煩為了快速賺錢研發(fā)了掉包處理器娩怎,小明投幣到自動售賣機后掉包處理器全自動掉包后交給了小明搔课,最后小明又歡樂的回家了。
- 這個故事里小明還是客戶截亦、無人售賣店是代理接口爬泥、掉包處理器是代理(只要監(jiān)聽到有人調用售賣機買東西自動化掉包)、自動售賣機是委托類它負責取貨魁巩。
文字說明也描述完了我想應該可以幫助小伙伴更容易理解代理的含義急灭,在第四節(jié)我將以代碼的方式更直觀的展示代理的實際應用。
</br>
四谷遂、源碼模擬
(1)DynamicLoadApk
通過查看了DynamicLoadApk的源碼葬馋,我以自己的理解在這里寫了簡化版的代理Activity的代碼,了解下面代碼可以更容易理解DynamicLoadApk。
/**
* 客戶類
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 靜態(tài)代理 模擬跳轉到指定的插件Activity
Intent dlIntent = new Intent();
dlIntent.putExtra(DLProxyActivity.CLASS_NAME, PluginActivity.class.getName());
DLPluginManager.startPluginActivity(this, dlIntent);
}
}
/**
* 靜態(tài)代理類工廠
*/
public class DLPluginManager {
public static void startPluginActivity(Context context, Intent dlIntent) {
//封裝跳轉真正的Activity
dlIntent.setClass(context, DLProxyActivity.class);
context.startActivity(dlIntent);
}
}
/**
* 代理接口 實現(xiàn)模擬Activity的生命周期
*/
public interface DLPlugin {
public void onStart();
public void onResume();
public void onStop();
public void onDestroy();
public void onCreate(Bundle savedInstanceState);
public void attach(Activity proxyActivity);
}
/**
* 靜態(tài)代理類 真正啟動的Activity
*/
public class DLProxyActivity extends Activity{
protected DLPlugin mRemoteActivity;
public static final String CLASS_NAME = "className";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String mClass = getIntent().getStringExtra(DLProxyActivity.CLASS_NAME);
try {
// 通過反射創(chuàng)建委托類
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
mRemoteActivity = (DLPlugin) instance;
// 綁定委托類
mRemoteActivity.attach(this);
mRemoteActivity.onCreate(new Bundle());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 關聯(lián)生命周期
*/
@Override
protected void onStart() {
//.....預處理
mRemoteActivity.onStart();
//.....事后處理
super.onStart();
}
@Override
protected void onResume() {
mRemoteActivity.onResume();
super.onResume();
}
@Override
protected void onStop() {
mRemoteActivity.onStop();
super.onStop();
}
}
/**
* 委托類基類畴嘶,對代理類功能的封裝
*/
public abstract class DLBasePluginActivity extends Activity implements DLPlugin {
// 代理activity
protected Activity mProxyActivity;
protected Activity that;
@Override
public void attach(Activity proxyActivity) {
mProxyActivity = (Activity) proxyActivity;
that = mProxyActivity;
}
@Override
public void setContentView(int layoutResID) {
mProxyActivity.setContentView(layoutResID);
}
@Override
public View findViewById(int id) {
return mProxyActivity.findViewById(id);
}
public void startPluginActivity(Intent dlIntent) {
DLPluginManager.startPluginActivity(that, dlIntent);
}
@Override
public void onCreate(Bundle savedInstanceState) {}
@Override
public void onStart() {}
@Override
public void onResume() {}
@Override
public void onStop() {}
@Override
public void onDestroy() {}
}
/**
* 委托類蛋逾,具體處理業(yè)務。
* 其實就是一個假的Activity并沒有真正的生命周期窗悯,每個方法的調用都是通過代理Activity來實現(xiàn)的区匣。
*/
public class PluginActivity extends DLBasePluginActivity{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
findViewById(R.id.click).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(that, "didid", 0).show();
}
});
}
}
以上就是DynamicLoadApk啟動插件Activity的簡化版流程,想要更深入的理解可以查看DynamicLoadApk的源碼蒋院。
(2)DroidPlugin
DroidPlugin更偏向與framework層亏钩,要對Android源代碼有一定的理解。
我這里推薦一個在線查看Android源碼的地址:http://www.grepcode.com/
通過查看源碼可以看到啟動Activity的時候欺旧,是通過ActivityManagerNative類里面的ActivityManagerProxy類來啟動Activity的姑丑,我們只要在這里動點手腳就可以了。
我們只要劫持了gDefault就可以做我們想要做的事情了辞友。
以啟動Activity為例:
/**
* 客戶類
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 動態(tài)代理 以4.x以上版本為例
try {
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
// 找到Hook點 通過反射獲取gDefault字段
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
// gDefault是私有的 需要暴利反射
gDefaultField.setAccessible(true);
// 因為是靜態(tài)方法所以傳入null就可以了
Object gDefault = gDefaultField.get(null);
// gDefault是一個 android.util.Singleton對象
Class<?> singleton = Class.forName("android.util.Singleton");
// 里面有一個成員變量IActivityManager mInstance 反射獲取它
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 獲取到IActivityManager對象
Object rawIActivityManager = mInstanceField.get(gDefault);
// 創(chuàng)建動態(tài)代理Hook
ActivityManagerHook hooks = new ActivityManagerHook();
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
// 傳入委托類栅哀、委托類名
Object proxy = hooks.newProxyInstance(rawIActivityManager, iActivityManagerInterface);
// 替換成我們的代理
mInstanceField.set(gDefault, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 動態(tài)代理IActivityManager
*/
public class ActivityManagerHook implements InvocationHandler {
private Object mHookedObject;
/**
* @param hookedObject 委托類
* @param clazz 代理接口
* @return 代理
*/
public Object newProxyInstance(Object hookedObject, Class<?> clazz){
this.mHookedObject = hookedObject;
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { clazz }, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 如果調用了startActivity方法 我們劫持傳過來的Intent替換成我想要跳轉的Activity
if("startActivity".equals(method.getName()) && args[2] instanceof Intent){
Intent i = new Intent();
i.setClassName("com.example.plugindemo", "com.example.plugindemo.dynamicplugin.HomeActivity");
args[2] = i;
}
Object result = method.invoke(mHookedObject, args);
return result;
}
}
五、結尾:
通過查看Android源碼并劫持它干一些事情称龙,我覺得還是挺有意思的留拾。好了就到這了希望這篇文章能幫助到各位小伙伴。