隨著應(yīng)用程序的功能模塊越來(lái)越多,復(fù)雜度越來(lái)越高份乒,導(dǎo)致了應(yīng)用程序模塊之間的耦合度越來(lái)越高恕汇,App的體積也隨之越來(lái)越大。與此同時(shí)或辖,隨著應(yīng)用程序代碼量的不斷增大瘾英,引入的庫(kù)越來(lái)越多,那么方法數(shù)很容易就超過(guò)了65535個(gè)颂暇,占用內(nèi)存也會(huì)隨之增大缺谴。于是,為了解決上述困境耳鸯,出現(xiàn)了插件化的思想湿蛔,其核心理念就是由宿主App去加載和運(yùn)行插件App。
宿主App是指預(yù)先被安裝在我們手機(jī)上的App县爬,可以獨(dú)立運(yùn)行阳啥,同時(shí)也可以加載插件。插件App是指那些子功能模塊的App财喳,它們可以被宿主App加載和運(yùn)行察迟,同時(shí)也可以作為獨(dú)立App進(jìn)行單獨(dú)運(yùn)行斩狱。這樣,各個(gè)功能模塊就可以單獨(dú)開(kāi)發(fā)扎瓶,宿主與插件之間所踊,以及插件與插件之間的耦合度就會(huì)大大降低,而且靈活性大大提高概荷。與此同時(shí)污筷,dex的體積也會(huì)隨之減小,從而避免65535問(wèn)題乍赫。在內(nèi)存占用方面瓣蛀,由于我們是只有在使用到某個(gè)插件時(shí)才會(huì)去進(jìn)行相應(yīng)的加載,這樣就可以減少內(nèi)存的使用雷厂。
插件化的知識(shí)體系還是比較多的惋增,包括Java反射原理,ClassLoader加載Dex原理改鲫,Android資源的加載诈皿,四大組件的加載,Android系統(tǒng)服務(wù)的運(yùn)行原理等等像棘。其中稽亏,四大組件的加載是插件化技術(shù)的核心,而Activity的插件化則更是重中之重缕题,因此本文主要介紹Activity的插件化截歉。Activity的插件化主要有3種實(shí)現(xiàn)方式,分別是反射實(shí)現(xiàn)烟零、接口實(shí)現(xiàn)和Hook技術(shù)實(shí)現(xiàn)瘪松。目前Hook技術(shù)實(shí)現(xiàn)是主流,對(duì)于前2種技術(shù)實(shí)現(xiàn)自身了解也很有限锨阿,因此本文主要介紹如何利用Hook技術(shù)實(shí)現(xiàn)Activity的插件化宵睦。
貼一下Demo的地址
https://github.com/lxbnjupt/PluginActivityDemo
一、Activity啟動(dòng)流程
首先墅诡,需要說(shuō)明的是壳嚎,如果一個(gè)Activity沒(méi)有在AndroidManifest中注冊(cè),此時(shí)如果去啟動(dòng)它的話將會(huì)得到ActivityNotFoundException末早,因?yàn)樵趩?dòng)的過(guò)程中烟馅,存在一個(gè)校驗(yàn)的過(guò)程,而這個(gè)校驗(yàn)則是由AMS來(lái)完成的荐吉,這個(gè)我們無(wú)法干預(yù)焙糟。而我們也很明確的知道,插件App中的Activity預(yù)先是不可能在宿主App的AndroidManifest中進(jìn)行注冊(cè)的样屠。所以,我們要想要實(shí)現(xiàn)Activity的插件化,就要重點(diǎn)去解決這個(gè)問(wèn)題痪欲。
那么如何實(shí)現(xiàn)Activity的插件化呢悦穿,我們的首要任務(wù)就是要徹底搞清楚Activity的啟動(dòng)流程,只有這樣才能從中找出解決問(wèn)題的實(shí)現(xiàn)方案业踢。在Activity啟動(dòng)流程源碼解析一文中栗柒,我們主要從源碼的角度分析了普通Activity的啟動(dòng)流程。這個(gè)過(guò)程主要涉及了兩個(gè)進(jìn)程知举,分別是AMS所在SystemServer進(jìn)程和應(yīng)用程序進(jìn)程瞬沦,通過(guò)Binder機(jī)制進(jìn)行跨進(jìn)程通信,相互配合雇锡,最終完成Activity的啟動(dòng)逛钻。
對(duì)于我們而言,AMS在SystemServer進(jìn)程中锰提,我們無(wú)法直接進(jìn)行修改曙痘,只能在應(yīng)用程序進(jìn)程中做文章。因此立肘,Activity的插件化方案大多是采用占坑的思想边坤,即預(yù)先在AndroidManifest.xml中顯示注冊(cè)一個(gè)Activity來(lái)進(jìn)行占坑,用來(lái)通過(guò)AMS的校驗(yàn)谅年,在通過(guò)校驗(yàn)之后再用插件Activity替換占坑的Activity茧痒。
通過(guò)分析Activity的啟動(dòng)流程,目前Hook技術(shù)實(shí)現(xiàn)Activity的插件化主要有2種解決方案 融蹂,一種是通過(guò)Hook IActivityManager來(lái)實(shí)現(xiàn)文黎,而另一種則是Hook Instrumentation實(shí)現(xiàn)。接下來(lái)殿较,我們來(lái)看一下具體的實(shí)現(xiàn)過(guò)程耸峭。聲明一下,我這邊的源碼版本是基于Android 8.0的淋纲。
二劳闹、Hook IActivityManager實(shí)現(xiàn)Activity插件化
2.1 AndroidManifest.xml中注冊(cè)占坑Activity
很簡(jiǎn)單,我們創(chuàng)建一個(gè)SubActivity洽瞬,并且在AndroidManifest.xml中進(jìn)行注冊(cè)本涕,目的就是用來(lái)占坑。
<application
android:name=".application.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.StubActivity" />
</application>
2.2 使用占坑Activity通過(guò)AMS校驗(yàn)
仔細(xì)想一下伙窃,我們的目標(biāo)其實(shí)很簡(jiǎn)單菩颖,就是在AMS執(zhí)行startActivity()方法之前,將要啟動(dòng)的插件Activity替換成占坑Activity为障。而調(diào)用AMS的startActivity()方法是由AMS在本地的代理對(duì)象來(lái)完成的晦闰,所以我們就把目光聚焦到了這個(gè)AMS本地代理對(duì)象放祟。
在Activity啟動(dòng)流程源碼解析中我們提到,關(guān)于獲取AMS代理對(duì)象的方式呻右,Android 8.0和之前的版本是有一些差別的跪妥。Android 8.0采用的是AIDL的實(shí)現(xiàn)方式獲取AMS的代理對(duì)象,而Android 8.0之前是通過(guò)ActivityManagerNative.getDefault()來(lái)獲取AMS的代理對(duì)象的声滥。不過(guò)這個(gè)對(duì)我們影響不是很大眉撵,做好兼容處理就行。
// Android 8.0源碼
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
// Android 8.0之前源碼
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
由上述源碼可知落塑,不管是Android 8.0纽疟,還是Android之前,最終返回的AMS本地代理對(duì)象都是IActivityManager類型的對(duì)象憾赁。因此污朽,IActivityManager就是一個(gè)很好的Hook點(diǎn),我們只需要去攔截它的startActivity()方法缠沈,并且將要啟動(dòng)的插件Activity替換成占坑Activity膘壶。為了簡(jiǎn)單起見(jiàn),省略了加載插件Activity的過(guò)程洲愤,直接創(chuàng)建了一個(gè)PluginActivity來(lái)代表已經(jīng)加載進(jìn)來(lái)的插件Activity颓芭,并且沒(méi)有在AndroidManifest.xml中進(jìn)行注冊(cè)。同時(shí)柬赐,由于IActivityManager又是一個(gè)接口亡问,所以我們完全可以采用動(dòng)態(tài)代理來(lái)攔截它的startActivity()方法,具體實(shí)現(xiàn)如下:
public class IActivityManagerProxy implements InvocationHandler {
private static final String TAG = "IActivityManagerProxy";
private Object mActivityManager;
public IActivityManagerProxy(Object activityManager) {
this.mActivityManager = activityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
Log.e(TAG, "invoke startActivity");
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 獲取啟動(dòng)PluginActivity的Intent
Intent pluginIntent = (Intent) args[index];
// 新建用來(lái)啟動(dòng)StubActivity的Intent
Intent stubIntent = new Intent();
stubIntent.setClassName("com.lxbnjupt.pluginactivitydemo",
"com.lxbnjupt.pluginactivitydemo.activity.StubActivity");
// 將啟動(dòng)PluginActivity的Intent保存在subIntent中肛宋,便于之后還原
stubIntent.putExtra(HookHelper.PLUGIN_INTENT, pluginIntent);
// 通過(guò)stubIntent賦值給args州藕,從而將啟動(dòng)目標(biāo)變?yōu)镾tubActivity,以此達(dá)到通過(guò)AMS校驗(yàn)的目的
args[index] = stubIntent;
}
return method.invoke(mActivityManager, args);
}
}
之后酝陈,創(chuàng)建代理類IActivityManagerProxy床玻,并且使用IActivityManagerProxy去替換原來(lái)的IActivityManager即可:
/**
* Hook IActivityManager
*
* @throws Exception
*/
public static void hookAMS() throws Exception {
Log.e(TAG, "hookAMS");
Object singleton = null;
if (Build.VERSION.SDK_INT >= 26) {
Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
// 獲取ActivityManager中的IActivityManagerSingleton字段
Field iActivityManagerSingletonField = ReflectUtils.getField(activityManageClazz, "IActivityManagerSingleton");
singleton = iActivityManagerSingletonField.get(activityManageClazz);
} else {
Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
// 獲取ActivityManagerNative中的gDefault字段
Field gDefaultField = ReflectUtils.getField(activityManagerNativeClazz, "gDefault");
singleton = gDefaultField.get(activityManagerNativeClazz);
}
Class<?> singletonClazz = Class.forName("android.util.Singleton");
// 獲取Singleton中mInstance字段
Field mInstanceField = ReflectUtils.getField(singletonClazz, "mInstance");
// 獲取IActivityManager
Object iActivityManager = mInstanceField.get(singleton);
Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
// 獲取IActivityManager代理對(duì)象
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iActivityManagerClazz}, new IActivityManagerProxy(iActivityManager));
// 將IActivityManager代理對(duì)象賦值給Singleton中mInstance字段
mInstanceField.set(singleton, proxy);
}
2.3 還原插件Activity
在AMS執(zhí)行startActivity()方法之前,我們使用占坑Activity替換插件Activity沉帮,從而通過(guò)了AMS的校驗(yàn)锈死。但是,我們真正要啟動(dòng)的是插件Activity穆壕,那么勢(shì)必還是要替換回來(lái)的待牵。那么,回想一下Activity的啟動(dòng)流程喇勋,我們的目標(biāo)就換成了要在ActivityThread執(zhí)行handleLaunchActivity()方法之前缨该,將占坑Activity替換回插件Activity。ActivityThread會(huì)通過(guò)Handler內(nèi)部類H將代碼的邏輯切換到主線程中川背,H中重寫(xiě)的handleMessage方法會(huì)對(duì)LAUNCH_ACTIVITY類型的消息進(jìn)行處理贰拿,我們可以將H的mCallback作為Hook點(diǎn)蛤袒。
public class HCallback implements Handler.Callback {
private static final int LAUNCH_ACTIVITY = 100;
Handler mHandler;
public HCallback(Handler handler) {
mHandler = handler;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
Object obj = msg.obj;
try {
// 獲取啟動(dòng)SubActivity的Intent
Intent stubIntent = (Intent) ReflectUtils.getField(obj.getClass(), "intent", obj);
// 獲取啟動(dòng)PluginActivity的Intent(之前保存在啟動(dòng)SubActivity的Intent之中)
Intent pluginIntent = stubIntent.getParcelableExtra(HookHelper.PLUGIN_INTENT);
// 將啟動(dòng)SubActivity的Intent替換為啟動(dòng)PluginActivity的Intent
stubIntent.setComponent(pluginIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
mHandler.handleMessage(msg);
return true;
}
}
由上述代碼可知,HCallback實(shí)現(xiàn)了Handler.Callback壮不,并重寫(xiě)了handleMessage方法汗盘,當(dāng)收到消息的類型為L(zhǎng)AUNCH_ACTIVITY時(shí)皱碘,將啟動(dòng)占坑Activity的Intent替換為啟動(dòng)插件Activity的Intent询一。
之后,我們創(chuàng)建HCallback的實(shí)例癌椿,并且用它來(lái)替換H的mCallback:
/**
* Hook ActivityThread中Handler成員變量mH
*
* @throws Exception
*/
public static void hookHandler() throws Exception {
Log.e(TAG, "hookHandler");
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
// 獲取ActivityThread中成員變量sCurrentActivityThread字段
Field sCurrentActivityThreadField = ReflectUtils.getField(activityThreadClazz, "sCurrentActivityThread");
// 獲取ActivityThread主線程對(duì)象
Object currentActivityThread = sCurrentActivityThreadField.get(activityThreadClazz);
// 獲取ActivityThread中成員變量mH字段
Field mHField = ReflectUtils.getField(activityThreadClazz, "mH");
// 獲取ActivityThread主線程中Handler對(duì)象
Handler mH = (Handler) mHField.get(currentActivityThread);
// 將我們自己的HCallback對(duì)象賦值給mH的mCallback
ReflectUtils.setField(Handler.class, "mCallback", mH, new HCallback(mH));
}
2.4 測(cè)試運(yùn)行
自定義Application健蕊,調(diào)用hookAMS()方法、hookHandler() 方法:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
// 通過(guò)Hook IActivityManager實(shí)現(xiàn)Activity插件化
HookHelper.hookAMS();
HookHelper.hookHandler();
} catch (Exception e) {
e.printStackTrace();
}
}
}
MainActivity代碼:
public class MainActivity extends AppCompatActivity {
private Button btnStartPluginActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStartPluginActivity = (Button) findViewById(R.id.tv_start_plugin_activity);
btnStartPluginActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, PluginActivity.class);
startActivity(intent);
}
});
}
}
運(yùn)行程序缩功,當(dāng)我們點(diǎn)擊啟動(dòng)插件Activity按鈕,發(fā)現(xiàn)啟動(dòng)的是插件PluginActivity嫡锌。
三、Hook Instrumentation實(shí)現(xiàn)Activity插件化
3.1 替換和還原插件Activity
Hook Instrumentation實(shí)現(xiàn)Activity插件化的思想同樣也是使用占坑Activity势木,與Hook IActivityManager不同的地方是替換和還原的地方不同而已,而且相對(duì)來(lái)說(shuō)會(huì)稍微簡(jiǎn)潔一些啦桌。
由Activity啟動(dòng)流程可知,啟動(dòng)一個(gè)Activity的過(guò)程中會(huì)調(diào)用到Instrumentation的execStartActivity()方法甫男,在此方法中我們可以用占坑Activity來(lái)替換插件Activity,以此來(lái)通過(guò)AMS的驗(yàn)證验烧。然后板驳,在回到ActivityThread主線程的performLaunchActivity方法中時(shí),會(huì)調(diào)用Instrumentation的newActivity方法創(chuàng)建Activity碍拆,在此方法中我們可以用插件Activity來(lái)替換占坑Activity若治。
public class InstrumentationProxy extends Instrumentation {
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;
public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
this.mInstrumentation = instrumentation;
this.mPackageManager = packageManager;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// 查找要啟動(dòng)的Activity是否已經(jīng)在AndroidManifest.xml中注冊(cè)
List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
// 要啟動(dòng)的Activity沒(méi)有注冊(cè),則將啟動(dòng)它的Intent保存在Intent中倔监,便于之后還原
intent.putExtra(HookHelper.PLUGIN_INTENT, intent.getComponent().getClassName());
// 替換要啟動(dòng)的Activity為StubActivity
intent.setClassName(who, "com.lxbnjupt.pluginactivitydemo.activity.StubActivity");
}
try {
Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
// 通過(guò)反射調(diào)用execStartActivity方法直砂,將啟動(dòng)目標(biāo)變?yōu)镾tubActivity,以此達(dá)到通過(guò)AMS校驗(yàn)的目的
return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
String intentName = intent.getStringExtra(HookHelper.PLUGIN_INTENT);
if (!TextUtils.isEmpty(intentName)) {
// 還原啟動(dòng)目標(biāo)Activity
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
}
接著,我們需要?jiǎng)?chuàng)建InstrumentationProxy對(duì)象浩习,并且讓其替換主線程中的Instrumentation對(duì)象即可:
/**
/**
* Hook Instrumentation
*
* @param context 上下文環(huán)境
* @throws Exception
*/
public static void hookInstrumentation(Context context) throws Exception {
Log.e(TAG, "hookInstrumentation");
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
// 獲取ActivityThread中成員變量sCurrentActivityThread字段
Field sCurrentActivityThreadField = ReflectUtils.getField(activityThreadClazz, "sCurrentActivityThread");
// 獲取ActivityThread中成員變量mInstrumentation字段
Field mInstrumentationField = ReflectUtils.getField(activityThreadClazz, "mInstrumentation");
// 獲取ActivityThread主線程對(duì)象(應(yīng)用程序啟動(dòng)后就會(huì)在attach方法中賦值)
Object currentActivityThread = sCurrentActivityThreadField.get(activityThreadClazz);
// 獲取Instrumentation對(duì)象
Instrumentation instrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
PackageManager packageManager = context.getPackageManager();
// 創(chuàng)建Instrumentation代理對(duì)象
InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation, packageManager);
// 用InstrumentationProxy代理對(duì)象替換原來(lái)的Instrumentation對(duì)象
ReflectUtils.setField(activityThreadClazz, "mInstrumentation", currentActivityThread, instrumentationProxy);
}
3.2 測(cè)試運(yùn)行
自定義Application静暂,調(diào)用hookInstrumentation()方法:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
// 通過(guò)Hook Instrumentation實(shí)現(xiàn)Activity插件化
HookHelper.hookInstrumentation(base);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MainActivity的代碼我就不貼了,跟Hook IActivityManager實(shí)現(xiàn)Activity插件化里面是一毛一樣的谱秽。同樣洽蛀,運(yùn)行程序摹迷,當(dāng)我們點(diǎn)擊啟動(dòng)插件Activity按鈕,發(fā)現(xiàn)啟動(dòng)的是插件PluginActivity郊供。
總結(jié)
Activity的插件化實(shí)現(xiàn)過(guò)程峡碉,實(shí)質(zhì)就是兩個(gè)字,那就是模仿驮审。通過(guò)對(duì)Activity啟動(dòng)流程的源碼分析鲫寄,了解系統(tǒng)啟動(dòng)Activity的整個(gè)過(guò)程,并且模仿系統(tǒng)的行為疯淫,找到其中的Hook點(diǎn)地来,從而最終實(shí)現(xiàn)Activity的插件化。