要解決這個問題,首先回顧一下啟動一個新的Activity的流程:
- 要啟動ActivityB,将要啟動的ActivityB資訊告訴AMS;
- AMS收到資訊後,記錄下ActivityB的相關資訊,同時檢查Manifest中是否已經注冊了ActivityB,如果一切正常,則回傳消息給ActivityThread:我接收到了!你可以paused了!
- ActivityA進入Paused狀态,再告訴AMS:我已經休眠了!
- AMS開始啟動的相關準備操作,檢視ActivityA和ActivityB是否在同一程序,然後取出之前儲存的ActivityB的相關資訊,發送給ActivityThread,說:你建立并啟動ActivityB。
- ActivityThread接收到資訊之後,通過Instrumentation反射建立ActivityB,并且啟動ActivityB。
要啟動沒有在Manifest中聲明的Activity有一個難點:
要怎樣規避上述步驟2中的AMS檢查Manifest中是否有注冊?
對于這個問題,可能有的會說:hook AMS可以麼? 答案肯定是不行的,因為AMS如果可以進行Hook,那麼Android系統早就崩潰了,要知道所有App底層都是公用的AMS。 既然這樣,隻能從上層動手,也就是上面的步驟中想解決辦法。
“欺騙”AMS方案
對于“欺騙”AMS方案,可以這樣了解,因為AMS是被動接收資料的,也就是說AMS負責校驗ActivityThread發送過來的資料,想一下:如果我發送過來的确确實實是已經在Manifest中已經注冊過的ActivityC呢? 然後我把ActivityB的相關資訊都儲存在ActivityC的啟動資訊中,這樣AMS是不是就被騙了?答案是肯定的!
上述步驟1,就順利的執行了,等到了步驟5, 我們又可以做手腳了,我們通過hook,攔截Instrumentation的啟動ActivityC的方法,改為建立ActivityB,也就是偷梁換柱。
說白了我們“欺騙”AMS的方案就兩步驟:
- 攔截上述步驟1,把要啟動的ActivityB的相關資訊更換為已經在Manifest中注冊的ActivityC,并且把ActivityB相關資訊儲存起來,塞入ActivityC的Intent中,因為之後我們還要啟動ActivityB,這樣一來就成功的“欺騙”了AMS。
- 攔截上述步驟5, 取出ActivityB的相關資訊,并且建立ActivityB。
下面是流程圖:
Hook步驟1: 替換Activity
對于攔截替換Activity,上一篇部落格有講述,我們可以選擇一勞永逸的方法:Hook ActivityManager。
Android 插件化開發——對startActivity方法進行hook(一)
這種方法同時适用于Activity和Content的startActivity。
/**
* hookManifest
*/
private void hookManifest() {
//擷取IActivityManagerSingleton對象
Object iActivityManagerSingleton =
ReflexUtil.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
//擷取單例對象中的mInstance字段
Object mSingletonInstance =
ReflexUtil.getFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance");
Class<?> classInterface = null;
try {
classInterface = Class.forName("android.app.IActivityManager");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//建立自己的代理對象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{classInterface},
new HookManifest(mSingletonInstance));
//将單例對象中的字段替換
ReflexUtil.setFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance", proxyInstance);
}
/**
* author: liumengqiang
* Date : 2019/7/26
* Description : 替換Activity,欺騙AMS
*/
public class HookManifest implements InvocationHandler {
private Object mBase;
public HookManifest(Object mBase) {
this.mBase = mBase;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if("startActivity".equals(method.getName())) {
Log.e("--------:", "hook successful ");
Intent replaceIntent = null;
int i = 0;
for(int index = 0; index < objects.length; index ++) {
if(objects[index] instanceof Intent) {
i = index;
replaceIntent = (Intent) objects[index];
break;
}
}
Intent newIntent = new Intent();
String stubPackage = replaceIntent.getComponent().getPackageName();
ComponentName componentName = new ComponentName(stubPackage, SecondActivity.class.getName());
newIntent.setComponent(componentName);
newIntent.putExtra("Extra_data", replaceIntent);
objects[i] = newIntent;
return method.invoke(mBase, objects);
}
return method.invoke(mBase, objects);
}
}
對于HookManifest類的作用,也就是替換Activity,替換成已經在Manifest中已經注冊的SecondActivity.
Hook步驟2: 替換我們真正要啟動的Activity
對于步驟2 , 我們需要明白一點:在之前Activity啟動流程分析中,我們知道,Activity的建立是在Instrumentation中建立的,并且是:
public Activity newActivity(ClassLoader cl, String className, Intent intent)
是以,我們可以攔截Instrumentation的newActivity 方法,包裝一下:取出我們要啟動的ThreadActivity, 最後執行真正Instrumentation的newActivity方法,
/**
*
*/
private void hookActivityThread() {
Object currentActivityThread = ReflexUtil.invokeStaticMethod("android.app.ActivityThread",
"currentActivityThread");
Instrumentation mInstrumentation =
(Instrumentation) ReflexUtil.getFieldValue("android.app.ActivityThread",
currentActivityThread, "mInstrumentation");
HookNextHelper hookNextHelper = new HookNextHelper(mInstrumentation);
ReflexUtil.setFieldValue("android.app.ActivityThread",
currentActivityThread, "mInstrumentation",hookNextHelper);
}
/**
* author: liumengqiang
* Date : 2019/7/26
* Description :
*/
public class HookNextHelper {
private Instrumentation mBase;
public HookNextHelper(Instrumentation mBase) {
this.mBase = mBase;
}
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Intent newIntent = intent.getParcelableExtra("Extra_data");
if(newIntent != null) {
String newClassName = newIntent.getComponent().getClassName();
return mBase.newActivity(cl, newClassName, newIntent);
}
return mBase.newActivity(cl, className, intent);
}
}
這樣就啟動了沒有在ActivityManifest中聲明的Activity!
相關博文:
- Android 插件化開發——基礎底層知識(Binder,AIDL)
- Android 插件化開發——基礎底層知識(Context家族史,Activity啟動流程)
- Android 插件化開發——基礎底層知識(Service)
- Android 插件化開發——基礎底層知識(反射)
- Android 插件化開發——基礎底層知識(代理模式)
- Android 插件化開發——對startActivity方法進行hook(一)