1. 概述
了解了Java的動态代理設計模式之後,配合上一期的文章Android插件化架構 - Activity的啟動流程分析,那麼接下來就需要親自操刀去攔截Activity的啟動流程了。前面好事沒少幹,那麼現在就來幹幹壞事,到底怎樣才能讓沒有注冊的Activity啟動不報錯呢?答案就是Hook下鈎子。
2. Hook啟動流程
怎麼樣去找Hook點是個問題,把鈎子下在哪裡呢?一般的套路肯定最好是靜态,然後是接口,配合反射注入就可以了。Activity啟動流程的源碼我就不再貼了,如果不了解請移步這裡Android插件化架構 - Activity的啟動流程分析,我這裡直接下鈎子。
/**
* hook start activity
*/
public void hookStartActivity() throws Exception{
// 先擷取ActivityManagerNative中的gDefault
Class<?> amnClazz = Class.forName("android.app.ActivityManagerNative");
Field defaultField = amnClazz.getDeclaredField("gDefault");
defaultField.setAccessible(true);
Object gDefaultObj = defaultField.get(null);
// 擷取Singleton裡面的mInstance
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field amsField = singletonClazz.getDeclaredField("mInstance");
amsField.setAccessible(true);
Object amsObj = amsField.get(gDefaultObj);
// 動态代理Hook下鈎子
amsObj = Proxy.newProxyInstance(mContext.getClass().getClassLoader(),
amsObj.getClass().getInterfaces(),
new StartActivityInvocationHandler(amsObj));
// 注入
amsField.set(gDefaultObj,amsObj);
}
/**
* Start Activity Invocation Handler
*/
private class StartActivityInvocationHandler implements InvocationHandler{
private Object mAmsObj;
public StartActivityInvocationHandler(Object amsObj){
this.mAmsObj = amsObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 攔截到所有ActivityManagerService的方法
Log.e("TAG","methodName"+method.getName());
return method.invoke(mAmsObj,args);
}
}
3. 借屍還魂
上面我們已經攔截到了Activity的啟動了,也能夠看到startActivity方法的列印。但是如果不做任何處理還是會蹦,那麼我們需要有一個Activity預先在AndroidMnifest.xml中注冊一下,它是不怕太陽的,通過它可以做到借屍還魂。
/**
* Start Activity Invocation Handler
*/
private class StartActivityInvocationHandler implements InvocationHandler{
private Object mAmsObj;
public StartActivityInvocationHandler(Object amsObj){
this.mAmsObj = amsObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 攔截到所有ActivityManagerService的方法
Log.e("TAG","methodName"+method.getName());
if(method.getName().equals("startActivity")){
// 啟動Activity的方法,找到原來的Intent
Intent realIntent = (Intent) args[];
// 代理的Intent
Intent proxyIntent = new Intent();
proxyIntent.setComponent(new ComponentName(mContext,mProxyActivity));
// 把原來的Intent綁在代理Intent上面
proxyIntent.putExtra("realIntent",realIntent);
// 讓proxyIntent去曬太陽,借屍
args[] = proxyIntent;
}
return method.invoke(mAmsObj,args);
}
}
還魂
/**
* hook Launch Activity
*/
public void hookLaunchActivity() throws Exception{
// 擷取ActivityThread
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);
// 擷取Handler mH
Field mHField = activityThreadClazz.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj);
// 設定Callback
Field callBackField = Handler.class.getDeclaredField("mCallback");
callBackField.setAccessible(true);
callBackField.set(mH, new ActivityThreadHandlerCallBack());
}
class ActivityThreadHandlerCallBack implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
handleLaunchActivity(msg);
}
return false;
}
}
// 還魂
private void handleLaunchActivity(Message msg) {
// final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
try {
Object obj = msg.obj;
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(obj);
// 代理意圖
Intent originIntent = proxyIntent.getParcelableExtra(EXTRA_ORIGIN_INTENT);
if (originIntent != null) {
// 替換意圖
intentField.set(obj, originIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
3. 相容AppCompatActivity
繼承自Activity是百試百靈,再也不需要在AndroidMnifest中注冊了,但是發現繼承AppCompatActivity還是會報錯,我都不記得當時是怎麼解決這個問題的,反正搞了好幾天,我選擇遺忘那段操蛋的時光。
// 相容AppCompatActivity報錯問題
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThread = field.get(null);
Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
Object iPackageManager = getPackageManager.invoke(activityThread);
PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iPackageManagerIntercept}, handler);
// 擷取 sPackageManager 屬性
Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
iPackageManagerField.setAccessible(true);
iPackageManagerField.set(activityThread, proxy);
總算走出了插件化架構的一小步,過程對于一般人來講還是有點痛苦的,但是結果帶來那種成就感還是值得的,後面我們解決一下資源和布局的加載問題,然後介紹一下360開源的插件化架構DroidPlugin,分析一下它的源碼就直接拿過用吧。
所有分享大綱:2017Android進階之路與你同行
視訊講解位址:http://pan.baidu.com/s/1o8bPZ9C