有時(shí)候稍不注意, 忘記在 Manifest 文件中注冊(cè) Activity诬乞,在運(yùn)行的時(shí)候啟動(dòng) Activity 時(shí)就會(huì)觸發(fā) ActivityNotFoundException
的異常。對(duì)于每一個(gè)運(yùn)行的 Activity 都需要進(jìn)行注冊(cè),這個(gè)常識(shí)我們都很清楚,但是在插件中這樣的要求就有些難以實(shí)現(xiàn)部宿,由于宿主程序在設(shè)計(jì)的時(shí)候彤叉,不知道插件的細(xì)節(jié),更不用說(shuō)在宿主程序的 Manifest 里面提前注冊(cè)插件 Activity看铆。
在這篇文章中,介紹了幾種可以繞過(guò) Android 對(duì) Activity 需要注冊(cè)的限制的實(shí)現(xiàn)方式笆搓。對(duì)這些實(shí)現(xiàn)方式的了解性湿,有助于理解 Activity 背后的原理,加深對(duì) ActivityManagerService 等等重要系統(tǒng)服務(wù)的認(rèn)知满败,是不錯(cuò)的進(jìn)階知識(shí)肤频。
在正式開(kāi)始寫之前,我還是想額外地扯扯淡算墨。就我自身看來(lái)宵荒,插件化技術(shù)本身的未來(lái)是不明朗的,在后續(xù)日趨穩(wěn)定的類 Reactive Native 技術(shù)穩(wěn)定(國(guó)內(nèi)有 Weex)后净嘀,可以幫助我們屏蔽不同版本的兼容性問(wèn)題报咳,實(shí)現(xiàn)動(dòng)態(tài)功能的成本也更低,可能更適合于長(zhǎng)遠(yuǎn)方向挖藏。但我依舊還在學(xué)習(xí)插件化技術(shù)暑刃,是在于插件化技術(shù)的深入理解需要依托于對(duì) Android Framework 層的透徹了解上,通過(guò)對(duì)此的學(xué)習(xí)膜眠,對(duì)自身內(nèi)功的修煉很有裨益岩臣。Android 技術(shù)也日新月異的發(fā)展溜嗜,而背后的 Framework 層則相對(duì)穩(wěn)定,設(shè)計(jì)理念也是大體相同架谎,對(duì)于 Framework 層的理解能幫我們構(gòu)建出更好的程序炸宵。這就是你所不能被其他人替代的地方,因?yàn)槟愕牟豢商娲怨瓤郏材苴A得更好的機(jī)會(huì)土全。
利用接口偽裝
dynamic-load-apk 作為第一個(gè)將 Android 插件化開(kāi)源方案出去的項(xiàng)目,提供了最初的解決方案会涎,
Manifest 注入代理 ProxyActivity
<activity
android:name="com.ryg.dynamicload.DLProxyActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="com.ryg.dynamicload.proxy.activity.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
既然不能提前在 Manifest 里面注冊(cè)相應(yīng)的 Activity 裹匙,那么就提前注冊(cè)代理 ProxyActivity,這個(gè)代理 Activity 在啟動(dòng)后在塔,會(huì)通過(guò)靜態(tài)代理的方式幻件,再實(shí)際調(diào)用真實(shí) Activity 的方法。
這里一定要在宿主程序中蛔溃,聲明 DLProxyActivity绰沥,目前還沒(méi)有什么方案可以繞過(guò)不需要聲明的限制。
啟動(dòng)插件 Activity
通常啟動(dòng) Activity 的時(shí)候贺待,代碼是這樣實(shí)現(xiàn)的徽曲。
Intent intent = new Intent(context, TargetActivity.class);
context.startActivity(intent);
dynamic-load-apk 在實(shí)現(xiàn)的時(shí)候?yàn)榱藢?shí)現(xiàn)自己代理的效果,進(jìn)行了自己的封裝麸塞,將 Intent 封裝成 DLIntent秃臣。如下面的代碼所示,DLIntent 將插件包名和對(duì)應(yīng)插件的 Activity 類傳遞進(jìn)來(lái)哪工。
public DLIntent(String pluginPackage, String pluginClass) {
super();
this.mPluginPackage = pluginPackage;
this.mPluginClass = pluginClass;
}
public DLIntent(String pluginPackage, Class<?> clazz) {
super();
this.mPluginPackage = pluginPackage;
this.mPluginClass = clazz.getName();
}
接下來(lái)看看 dynamic-load-apk 如何實(shí)現(xiàn)啟動(dòng) startActivity 的奥此。
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
String packageName = dlIntent.getPluginPackage();
//驗(yàn)證intent的包名
if (TextUtils.isEmpty(packageName)) {
throw new NullPointerException("disallow null packageName.");
}
//檢測(cè)插件是否加載
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
if (pluginPackage == null) {
return START_RESULT_NO_PKG;
}
//要調(diào)用的插件Activity的class完整路徑
final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
//Class.forName
Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
if (clazz == null) {
return START_RESULT_NO_CLASS;
}
//獲取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity
Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);
if (proxyActivityClass == null) {
return START_RESULT_TYPE_ERROR;
}
//put extra data
dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
dlIntent.setClass(mContext, proxyActivityClass);
//通過(guò)context啟動(dòng)宿主Activity
performStartActivityForResult(context, dlIntent, requestCode);
return START_RESULT_SUCCESS;
}
首先通過(guò) ClassLoader 的方式(這里有具體介紹 Android ClassLoader 加載機(jī)制) 加載插件雁比。同樣在 DLIntent 里面將實(shí)際的插件包名和插件對(duì)象傳遞進(jìn)去稚虎,以便后續(xù)代理 Activity 調(diào)用。注意這里的 getProxyActivityClass
方法返回的是 DLProxyActivity
, 也就是說(shuō)將要啟動(dòng)的 Activity 替換為了代理 Activity偎捎。
處理插件生命周期
當(dāng) ProxyActivity 啟動(dòng)后蠢终,在相應(yīng)的生命周期時(shí)通過(guò)反射的方式調(diào)用實(shí)際 Activity 中生命周期的方法,但反射這種方式存在兩個(gè)方法的問(wèn)題茴她,一是頻繁地調(diào)用反射會(huì)有不可忽視的性能開(kāi)銷寻拂,另一方面反射的使用會(huì)使得代碼難以維護(hù),而且可能存在兼容性問(wèn)題丈牢。就先定義了如下的接口:
public interface DLPlugin {
public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
public boolean onCreateOptionsMenu(Menu menu);
public boolean onOptionsItemSelected(MenuItem item);
}
代理 Activity 實(shí)現(xiàn)了這個(gè)接口祭钉,并在相應(yīng)的接口中,去調(diào)用插件 Activity 的方法己沛,從而實(shí)現(xiàn)偷天換日的功效朴皆。下面以 finish
函數(shù)為例帕识,說(shuō)明如何實(shí)現(xiàn)的泛粹。
public class DLBasePluginActivity extends Activity implements DLPlugin {
// ...
@Override
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
mProxyActivity = (Activity) proxyActivity;
that = mProxyActivity;
mPluginPackage = pluginPackage;
}
@Override
public void finish() {
if (mFrom == DLConstants.FROM_INTERNAL) {
super.finish();
} else {
mProxyActivity.finish();
}
}
// ...
}
這種方案只差最后遂铡,怎么將 TargetActivity 和 ProxyActivity 綁定在一起了? dynamic-load-apk 中 launchTargetActivity 實(shí)現(xiàn)了這個(gè)功能,在其中的 attach 函數(shù)里面晶姊,將 ProxyActivity 和 PluginActivity 綁定在一起扒接。
protected void launchTargetActivity() {
try {
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
mPluginActivity = (DLPlugin) instance;
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
// attach the proxy activity and plugin package to the mPluginActivity
mPluginActivity.attach(mProxyActivity, mPluginPackage);
Bundle bundle = new Bundle();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
經(jīng)過(guò)上訴的步驟,就可以繞過(guò) Activity 需要注冊(cè)的限制了们衙,但這個(gè)方案也有一定的限制钾怔。不僅要求,插件和宿主都必須同時(shí)依賴于一個(gè)接口工程蒙挑,這樣會(huì)嚴(yán)重制約插件的實(shí)現(xiàn)宗侦。另一方面,對(duì)于 Activity 可以這么實(shí)現(xiàn)忆蚀,但是對(duì)于 Service 等等其他組件矾利,也需要進(jìn)行同樣的接口代理,在代碼的可讀性上不是很好馋袜。于是男旗,插件化在發(fā)展一段時(shí)間后,有了如下的解決方案欣鳖。
構(gòu)建 APP 虛擬運(yùn)行環(huán)境
構(gòu)建虛擬運(yùn)行環(huán)境的方式察皇,提供了一種一勞永逸的方案,這種方案通過(guò)反射泽台、動(dòng)態(tài)代理等技術(shù)什荣,將 APP 需要運(yùn)行的系統(tǒng)服務(wù)進(jìn)行了特殊處理,欺上瞞下怀酷,使得 APP 能在不安裝的情況下稻爬,運(yùn)行起來(lái)。在這種情況下胰坟,自然而然就沒(méi)有前面方案中因篇,需要額外依賴工程的弊端,也不需要插件為了繼承做什么特殊處理笔横。而實(shí)現(xiàn)這種虛擬運(yùn)行環(huán)境竞滓,需要大量的工程,對(duì)每個(gè)系統(tǒng)服務(wù)都進(jìn)行特殊處理吹缔,在這里就不展開(kāi)敘述了商佑,只說(shuō)明 Activity 如何在這個(gè)虛擬環(huán)境下跑起來(lái)。
攔截 startActivity 請(qǐng)求
為了不讓插件做額外的工作厢塘,我們必須對(duì)攔截 startActivity, 進(jìn)行偷天換日的工作茶没。這里用到的技術(shù)就是動(dòng)態(tài)代理肌幽,關(guān)于動(dòng)態(tài)代理如何實(shí)現(xiàn),網(wǎng)上有不少的文章可以參考抓半,不再詳述喂急。 startActivty 眾多簽名的方法中,最后都會(huì)進(jìn)入到 ActivityManager 中去笛求,因而對(duì) ActivityManager 進(jìn)行代理是不錯(cuò)的選擇廊移,而實(shí)際上 ActivityManager 所做的工作是通過(guò) Binder 機(jī)制對(duì) ActivityManagerService 的調(diào)用。最后的代理工作探入,還是要回到 ActivityManagerService 里面來(lái)壳炎。
if (ActivityManagerNative.gDefault.type() == IActivityManager.class) {
ActivityManagerNative.gDefault.set(getHookObject().getProxyObject());
} else if (ActivityManagerNative.gDefault.type() == android.util.Singleton.class) {
Object gDefault = ActivityManagerNative.gDefault.get();
Singleton.mInstance.set(gDefault, getHookObject().getProxyObject());
}
上面的代碼中禁漓,ActivtyManagerNative 是 ActivityManagerService 的基類绿淋,對(duì) ActivityManagerNative 的修改作用于 ActivityManagerService博其。這里針對(duì)了不同 SDK 版本做了不同的處理,總之在這個(gè)替換后植旧,ActivityManagerService 中的 gDefault 變量已經(jīng)變成我們 hook 后的對(duì)象了辱揭,getHookObject().getProxyObject()
。
HookBinder<IActivityManager> hookAMBinder = new HookBinder<IActivityManager>() {
@Override
protected IBinder queryBaseBinder() {
return ServiceManager.getService(Context.ACTIVITY_SERVICE);
}
@Override
protected IActivityManager createInterface(IBinder baseBinder) {
return getHookObject().getProxyObject();
}
};
hookAMBinder.injectService(Context.ACTIVITY_SERVICE);
public void injectService(String name) throws Throwable {
Map<String, IBinder> sCache = mirror.android.os.ServiceManager.sCache.get();
if (sCache != null) {
sCache.remove(name);
sCache.put(name, this);
} else {
throw new IllegalStateException("ServiceManager is invisible.");
}
}
在接下來(lái)的這段代碼里面隆嗅,是替換 SystemServer 中存放的 ActivityManagerService 變量界阁,這樣在上面兩個(gè)地方進(jìn)行代理之后,已經(jīng)將所有和 ActivityManagerService (以下簡(jiǎn)稱 AMS) 相關(guān)的入口都進(jìn)行了處理胖喳,這樣當(dāng)我們調(diào)用 startActivity 方法時(shí)泡躯,就能進(jìn)入到我們代理的對(duì)象中。接下來(lái)看看丽焊,這個(gè)代理對(duì)象應(yīng)該如何實(shí)現(xiàn)较剃。
代理所完成的工作是對(duì) AMS 進(jìn)行各種改動(dòng),已達(dá)成完成啟動(dòng)插件 Activity 的目的技健,這里就只從 startActivity 這個(gè)方法入手写穴,其他方法可以逐類旁通。
我們先看看 startActivity 的方法簽名雌贱,這個(gè)函數(shù)在不同版本的簽名也各不相同啊送,下面演示的是基于 SDK-23 源碼。
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
// ...
}
其中 intent 參數(shù)就是我們傳入的啟動(dòng) Intent欣孤;
caller 是傳入到 AMS 中的 binder, 當(dāng)通知 Activity 啟動(dòng)或者其他事件完成的時(shí)候馋没,就可以通過(guò)這個(gè) Binder 對(duì)象進(jìn)行通知 Activty 進(jìn)行生命周期處理了;
resultTo 這個(gè)參數(shù)是 ActivtyRecord 中的變量降传,這里用來(lái)表征一個(gè) Activity篷朵。 resultTo 本事是 Binder 對(duì)象,而 Binder 對(duì)象可以在跨進(jìn)程中起到唯一標(biāo)示的作用。
其余參數(shù)就不再敘述了声旺,現(xiàn)在看看 startActivity 具體是怎么攔截的笔链。Java 一般使用 InvocationHandler 來(lái)進(jìn)行動(dòng)態(tài)代理,代理過(guò)后會(huì)調(diào)用到 invoke
方法 , 當(dāng) method.getName 是 startActivity 時(shí)腮猖,我們就可以進(jìn)行攔截了鉴扫。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
}
獲取必要的參數(shù),并保存下來(lái)缚够。
int intentIndex = ArrayUtils.indexOfFirst(args, Intent.class);
int resultToIndex = ArrayUtils.indexOfObject(args, IBinder.class, 2);
String resolvedType = (String) args[intentIndex + 1];
Intent targetIntent = (Intent) args[intentIndex];
targetIntent.setDataAndType(targetIntent.getData(), resolvedType);
IBinder resultTo = resultToIndex != -1 ? (IBinder) args[resultToIndex] : null;
String resultWho = null;
int requestCode = 0;
Bundle options = ArrayUtils.getFirst(args, Bundle.class);
if (resultTo != null) {
resultWho = (String) args[resultToIndex + 1];
requestCode = (int) args[resultToIndex + 2];
}
int userId = getUserId(targetIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
args[intentIndex - 1] = getHostPkg();
}
解析 startActivity 中的 Intent
在上一篇文章中幔妨,講解到如何不經(jīng)過(guò)安裝過(guò)程,解析 APK 的信息谍椅。在得到這些信息之后,VirtualApp(以下簡(jiǎn)稱VA) 會(huì)將這些信息組織起來(lái)古话,存放在本地的包服務(wù)中雏吭。信息的組織形式,與系統(tǒng)的 PackageManagerService 類似陪踩,將各大組件杖们、權(quán)限等信息保留下來(lái),當(dāng)調(diào)用到 startActivity 時(shí)肩狂,解析其中的 Intent摘完,查看是否有相應(yīng)的組件匹配對(duì)應(yīng)的 Intent。
ActivityInfo targetActInfo = VirtualCore.getCore().resolveActivityInfo(targetIntent, userId);
if (targetActInfo == null) {
return method.invoke(who, args);
}
String packageName = targetActInfo.packageName;
if (!isAppPkg(packageName)) {
return method.invoke(who, args);
}
Intent resultIntent = VActivityManager.get().startActivity(targetIntent, targetActInfo, resultTo, options, userId);
if (resultIntent == null) {
if (resultTo != null) {
VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
}
return 0;
}
上面的代碼中獲取到 intent 對(duì)應(yīng)的 targetActInfo, 如果為空傻谁,或者沒(méi)有在 VirtualApp 里面孝治,就直接調(diào)用原有 API 的方法,否則則進(jìn)入我們的攔截邏輯审磁,看起來(lái) VActivityManager.get().startActivity
是重中之重谈飒。
創(chuàng)建應(yīng)用進(jìn)程
在我之前的一篇文章里,Android 應(yīng)用進(jìn)程啟動(dòng)流程, Android 組件的運(yùn)行都是需要相應(yīng)的進(jìn)程的态蒂。我們?cè)谥v如何啟動(dòng)插件 Activity 的時(shí)候杭措,也要處理好這個(gè)問(wèn)題。單獨(dú)的進(jìn)程有助于進(jìn)行數(shù)據(jù)隔離钾恢,當(dāng)發(fā)生意外情況時(shí)手素,不至于影響主進(jìn)程。皮之不存瘩蚪,毛將焉附泉懦,現(xiàn)在看看創(chuàng)建進(jìn)程,讓插件 Activity 可以依附募舟。
VirtualApp 會(huì)預(yù)先在 AndroidManifest 通過(guò) android:process
來(lái)預(yù)置一些進(jìn)程祠斧,當(dāng)有需要的時(shí)候,會(huì)查看這些進(jìn)程是否存在拱礁,利用其未占用的進(jìn)程給插件使用琢锋。下面截取了一段 AndroidManifest 中的代碼辕漂,可以注意其中的 android:process
字段。
<activity
android:name="com.lody.virtual.client.stub.StubActivity$C0"
android:configChanges="mcc|mnc|locale|...|fontScale"
android:process=":p0"
android:taskAffinity="com.lody.virtual.vt"
android:theme="@style/VATheme">
<meta-data
android:name="X-Identity"
android:value="Stub-User"/>
</activity>
<activity
android:name="com.lody.virtual.client.stub.StubActivity$C1"
android:configChanges="mcc|mnc|locale|...|fontScale"
android:process=":p1"
android:taskAffinity="com.lody.virtual.vt"
android:theme="@style/VATheme">
<meta-data
android:name="X-Identity"
android:value="Stub-User"/>
</activity>
VActivityManagerService(以下簡(jiǎn)稱VAMS) 在啟動(dòng)后吴超,會(huì)遍歷對(duì)應(yīng) Manifest 文件中的 Activity 和 Provider 組件钉嘹,并查看其中的 processName,通過(guò) Map 建立起 processName 和對(duì)應(yīng) Activity 和 Provider 之間的關(guān)系鲸阻。
PackageManager pm = context.getPackageManager();
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(context.getPackageName(),
PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS
| PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
ActivityInfo[] activityInfos = packageInfo.activities;
for (ActivityInfo activityInfo : activityInfos) {
if (isStubComponent(activityInfo)) {
String processName = activityInfo.processName;
stubProcessList.add(processName);
StubInfo stubInfo = stubInfoMap.get(processName);
if (stubInfo == null) {
stubInfo = new StubInfo();
stubInfo.processName = processName;
stubInfoMap.put(processName, stubInfo);
}
String name = activityInfo.name;
if (name.endsWith("_")) {
stubInfo.dialogActivityInfos.add(activityInfo);
} else {
stubInfo.standardActivityInfos.add(activityInfo);
}
}
}
當(dāng)需要啟動(dòng)進(jìn)程時(shí)跋涣,就從 stubInfoMap 里面去查看是否有空閑的進(jìn)程可供使用。如果存在空閑的進(jìn)程鸟悴,則通過(guò)前面提到的 Map陈辱,從進(jìn)程名得到相應(yīng)的 Stub 信息。進(jìn)程的創(chuàng)建是相對(duì)重量級(jí)的事情细诸,而 VA 只用了幾行代碼沛贪,就完成了這個(gè)事情,利用的正是 Android Provider 的機(jī)制震贵。當(dāng) Provider 啟動(dòng)的時(shí)候利赋,可以同步地啟動(dòng)對(duì)應(yīng)的進(jìn)程,具體原理可以參看 這篇文章.
public static Bundle call(String authority, Context context, String methodName, String arg, Bundle bundle) {
Uri uri = Uri.parse("content://" + authority);
ContentResolver contentResolver = context.getContentResolver();
return contentResolver.call(uri, methodName, arg, bundle);
}
進(jìn)程啟動(dòng)后猩系,還需要將這個(gè)進(jìn)程和插件具體綁定起來(lái)媚送,使得這個(gè)進(jìn)程能夠當(dāng)做 Application 來(lái)運(yùn)行,這段邏輯也相對(duì)復(fù)雜寇甸,有興趣的可以看看 VClientImpl
的實(shí)現(xiàn)塘偎。
斗轉(zhuǎn)星移繞過(guò) Manifest 的限制
在進(jìn)程創(chuàng)建成功后,startProcessIfNeedLocked
可以得到對(duì)應(yīng)的 ProcessRecord 對(duì)象幽纷,這個(gè)對(duì)象中存放著相應(yīng)的 Activity式塌、Service 等等組件信息,當(dāng)然也包括用于偷換概念的 Stub 信息友浸。在進(jìn)程啟動(dòng)后峰尝,將對(duì)應(yīng)的 Stub 放置在 intent 中,并通過(guò) Binder 機(jī)制返回給 Client 端收恢。
ProcessRecord processRecord = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
if (processRecord == null) {
return null;
}
StubInfo selectedStub = processRecord.stubInfo;
ActivityInfo stubActInfo = selectedStub.fetchStubActivityInfo(info);
if (stubActInfo == null) {
return null;
}
newIntent.setClassName(stubActInfo.packageName, stubActInfo.name);
newIntent.putExtra("_VA_|_intent_", intent);
newIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
newIntent.putExtra("_VA_|_target_activity_", info);
newIntent.putExtra("_VA_|_user_id_", userId);
return newIntent;
重點(diǎn)來(lái)啦武学,在上面的 newIntent 中,將 className 設(shè)置成為代理 Activity 的信息伦意。在 Client 端火窒,獲取到這個(gè) resultIntent 后,將這個(gè)值注入到 startActivity 的 intent 里面去驮肉,在 args[intentIndex] = resultIntent 這里進(jìn)行的替換熏矿。而在這里進(jìn)行替換過(guò)后,就可以繞過(guò) AMS 的對(duì) Activity 需要注冊(cè)的限制了。
@Override
public Object onHook(Object who, Method method, Object... args) throws Throwable {
super.onHook(who, method, args);
// ...
Intent resultIntent =
VActivityManager.get().startActivity(
targetIntent, targetActInfo, resultTo, options, userId);
if (resultIntent == null) {
if (resultTo != null) {
VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
}
return 0;
}
args[intentIndex] = resultIntent;
return method.invoke(who, args);
}
給插件 Activity 注入生命
上段代碼中的 method.invoke(who, args)
, 執(zhí)行的是 SDK 中 startActivity 的邏輯票编。這個(gè)邏輯里面執(zhí)行的是啟動(dòng)邏輯褪储,在這里篇幅的限制,就不再詳細(xì)說(shuō)明了慧域,大家可以看我的這篇博文鲤竹,Android Activity 生命周期是如何實(shí)現(xiàn)的, 最終程序會(huì)執(zhí)行到 ActivityThread.mH 中去,對(duì)應(yīng)的消息就是 LAUNCH_ACTIVITY
昔榴,其后執(zhí)行的方法就是下面代碼所描述的這樣辛藻。
private boolean handleLaunchActivity(Message msg) {
Object r = msg.obj;
// StubIntent
Intent stubIntent = ActivityThread.ActivityClientRecord.intent.get(r);
// TargetIntent
Intent targetIntent = stubIntent.getParcelableExtra("_VA_|_intent_");
ComponentName component = targetIntent.getComponent();
String packageName = component.getPackageName();
AppSetting appSetting = VirtualCore.getCore().findApp(packageName);
if (appSetting == null) {
return true;
}
// 從 Intent 中獲取的 stub 和 target 的信息
ActivityInfo stubActInfo = stubIntent.getParcelableExtra("_VA_|_stub_activity_");
ActivityInfo targetActInfo = stubIntent.getParcelableExtra("_VA_|_target_activity_");
if (stubActInfo == null || targetActInfo == null) {
return true;
}
String processName = ComponentUtils.getProcessName(targetActInfo);
// 保證 Process 已經(jīng)與 Application 綁定起來(lái)
if (!VClientImpl.getClient().isBound()) {
int targetUser = stubIntent.getIntExtra("_VA_|_user_id_", 0);
VActivityManager.get().ensureAppBound(processName, appSetting.packageName, targetUser);
getH().sendMessageDelayed(Message.obtain(msg), 5);
return false;
}
// 設(shè)置對(duì)應(yīng)的 classLoader
ClassLoader appClassLoader = VClientImpl.getClient().getClassLoader(targetActInfo.applicationInfo);
targetIntent.setExtrasClassLoader(appClassLoader);
boolean error = false;
try {
targetIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
targetIntent.putExtra("_VA_|_target_activity_", targetActInfo);
} catch (Throwable e) {
error = true;
VLog.w(TAG, "Directly putExtra failed: %s.", e.getMessage());
}
if (error && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
ClassLoader oldParent = getClass().getClassLoader().getParent();
mirror.java.lang.ClassLoader.parent.set(getClass().getClassLoader(), appClassLoader);
try {
targetIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
targetIntent.putExtra("_VA_|_target_activity_", targetActInfo);
} catch (Throwable e) {
VLog.w(TAG, "Secondly putExtra failed: %s.", e.getMessage());
}
mirror.java.lang.ClassLoader.parent.set(getClass().getClassLoader(), oldParent);
}
// 反射替換其中的 intent 和 activityInfo, 將 Stub 相關(guān)的信息換成 target 相關(guān)的信息
ActivityThread.ActivityClientRecord.intent.set(r, targetIntent);
ActivityThread.ActivityClientRecord.activityInfo.set(r, targetActInfo);
return true;
}
同樣也是重點(diǎn),這里將 ActivityClientRecord 中的相應(yīng)信息替換為了插件 Activity互订,從這一步過(guò)后吱肌,對(duì)應(yīng)的插件 Activity 就能通過(guò)這個(gè) H Callback 接收到相應(yīng)的生命周期回調(diào),從這一刻開(kāi)始屁奏,插件 Activity 就是有血有肉的存在了岩榆。
總結(jié) VA 的實(shí)現(xiàn)方式
可能大家在看前面的描述過(guò)后,如果對(duì) AMS 這一塊比較熟悉的話坟瓢,就會(huì)發(fā)現(xiàn)所做的工作其實(shí)特別簡(jiǎn)單。第一步犹撒,就是將 startActivity 中的 intent 參數(shù)折联,替換為插件 Activity 的信息;第二步识颊,是在欺騙完系統(tǒng)后诚镰,在 H Callback 的 LAUNCH_ACTIVITY
消息中,將對(duì)應(yīng) Record 中的信息祥款,還原為插件的 Activity 信息清笨。
讀者也許會(huì)問(wèn),難道真的就這么簡(jiǎn)單刃跛,就可以欺騙系統(tǒng)了嗎抠艾?我們先通過(guò) adb shell dumpsys activity
的方式,看看在 AMS 這個(gè)視角上桨昙,運(yùn)行的是哪個(gè) Activity检号?
看來(lái),AMS 還真天真地運(yùn)行著 StubActivity蛙酪,在前面欺騙的環(huán)節(jié)中齐苛,傳遞進(jìn)去的確實(shí)是 StubActivity,而為何在實(shí)際運(yùn)行的時(shí)候桂塞,客戶端還能繼續(xù)使用插件 Activity 了凹蜂?在Android Activity 生命周期是如何實(shí)現(xiàn)的 這篇文章里面講到,在 ActivityThread 中的 performLaunchActivity 方法里面,實(shí)際去調(diào)用 Activity 的 onCreate 方法玛痊,而在這個(gè) performLaunchActivity 里面有這樣一段代碼汰瘫。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
// other code.
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
}
這里調(diào)用方法時(shí),已經(jīng)在 H Callback 中將 ActivityClientRecord 替換為插件 Activity卿啡,而在 attach 的時(shí)候吟吝,也是將這個(gè) token 作為參數(shù)寫入進(jìn)去。因而后續(xù)在 Client 段實(shí)際使用的是插件 Activity颈娜,盡管系統(tǒng)依然用著 StubActivity剑逃。
360 的 DP 方案,采用的也是類似的技術(shù)官辽,大家可以最后進(jìn)行下對(duì)比蛹磺。
文檔信息
- 版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享3.0許可證)
- 發(fā)表日期:2016年8月25日
- 社交媒體:weibo.com/woaitqs
- Feed訂閱:www.woaitqs.cc/feed.xml