Android系統的每次版本升級,都會對原有代碼進行重構,這就爲插件化帶來了麻煩。java
Android P對插件化的影響,主要體如今兩方面,一是它重構了H類中Activity相關的邏輯,另外一個是它重構了Instrumentation。android
3.1.1 從Message和Handler提及面試
對於App開發人員而言,Message和Handler是耳熟能詳的兩個概念。咱們簡單回顧一下,一條消息是怎麼發送和接收的。設計模式
首先,在App啓動的時候,會建立ActivityThread,這就是主線程,也叫UI線程。App的入口——main函數,就藏在ActivityThread中,架構
在main函數中,會建立MainLooper。MainLooper是一個死循環,專門負責接收消息,也就是Message類。app
Message類的定義以下,除了耳熟能詳的what和obj屬性外,還有一個不對App開放的變量target,它是一個Handler:ide
public final class Message implements Parcelable { public int what; public Object obj; Handler target; //如下省略不少代碼哦 }
在App進程中,Application和四大組件的每一個生命週期函數,都要和AMS進程進行跨進程通訊。函數
1)App進程把數據傳給AMS進程,是經過ActivityManagerNative完成的。oop
2)AMS進程把數據傳給App進程,App進程這邊接收數據的是ApplicationThread。測試
ApplicationThread在接收到數據後,會調用sendMessage方法。這就把消息Message對象發送給了MainLooper這個死循環。
在MainLooper死循環中,處理這個Message對象。怎麼處理呢,取出它的target字段,這是一個Handler類型的對象,調用這個Handler對象的dispatchMessage方法。
是時候看一下Handler類的結構了,我簡化了它的代碼,爲的是易於理解:
public class Handler { final Callback mCallback; public interface Callback { public boolean handleMessage(Message msg); } public void handleMessage(Message msg) { } public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } }
Handler類中有一個mCallback變量,這個變量是插件化技術的核心。書接上文,MainLooper調用了Handler的dispatchMessage方法,這個方法的邏輯是,要麼執行mCallback的handleMessage方法,要麼執行Handler類本身的handleMessage方法。
Handler類本身的handleMessage方法,是一個空方法,因此咱們通常寫一個Handler的子類,而後實現這個handleMessage方法。
在Android系統底層,這個Handler類的子類,就是H類,咱們在ActivityThread.java中能夠找到這個類。H類的handleMessage方法中,定義了全部消息的分發,以下所示:
public final class ActivityThread { private class H extends Handler { public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; public static final int STOP_ACTIVITY_SHOW = 103; public static final int STOP_ACTIVITY_HIDE = 104; public static final int SHOW_WINDOW = 105; public static final int HIDE_WINDOW = 106; public static final int RESUME_ACTIVITY = 107; public static final int SEND_RESULT = 108; public static final int DESTROY_ACTIVITY = 109; public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int NEW_INTENT = 112; public static final int RECEIVER = 113; //如下省略不少代碼 public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: { final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); } break; //如下省略不少代碼 } } } }
在H類的handleMessage方法中,會根據msg參數的what值,來判斷究竟是哪一種消息,以及相應的執行什麼邏輯,好比說,啓動Activity。
在H類中,定義了幾十種消息,好比說LAUNCH_ACTIVITY的值是100,PAUSE_ACTIVITY的值是101。從100到109,都是給Activity的生命週期函數準備的。從110開始,纔是給Application、Service、ContentProvider、BroadcastReceiver使用的。
至此,咱們簡單回顧了Android系統內部Message的發送和接收流程。其中比較重要的是:
1)Handler類中有一個mCallback變量。
2)H類中定義了各類消息。
3.1.2 Android P以前的插件化解決方案
在Android P(API level 28)以前,咱們作插件化,都是Hook掉H類的mCallback對象,攔截這個對象的handleMessage方法。在此以前,咱們把插件中的Activity替換爲StubActtivty,那麼如今,咱們攔截到handleMessage方法,再把StubActivity換回爲插件中的Activity,代碼以下所示:
class MockClass2 implements Handler.Callback { Handler mBase; public MockClass2(Handler base) { mBase = base; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { // ActivityThread裏面 "LAUNCH_ACTIVITY" 這個字段的值是100 // 原本使用反射的方式獲取最好, 這裏爲了簡便直接使用硬編碼 case 100: handleLaunchActivity(msg); break; } mBase.handleMessage(msg); return true; } private void handleLaunchActivity(Message msg) { // 這裏簡單起見,直接取出TargetActivity; Object obj = msg.obj; // 把替身恢復成真身 Intent raw = (Intent) RefInvoke.getFieldObject(obj, "intent"); Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT); raw.setComponent(target.getComponent()); } }
3.1.3 Android P對Activity消息機制的改造
Android系統升級到P,它重構了H類,把100到109這10個用於Activity的消息,都合併爲159這個消息,消息名爲EXECUTE_TRANSACTION。
爲何要這麼修改呢?相信你們面試Android工程師崗位的時候,都會被問及Activity的生命週期圖。這實際上是一個由Create、Pause、Resume、Stop、Destory、Restart組成的狀態機。按照設計模式中狀態模式的定義,能夠把每一個狀態都定義成一個類,因而便有了以下的類圖:
就拿LaunchActivity來講吧,在Android P以前,是在H類的handleMessage方法的switch分支語句中,編寫啓動一個Activity的邏輯:
public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: { final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); } break; //如下省略不少代碼 } }
在Android P中,啓動Activity的這部分邏輯,被轉移到了LaunchActivityItem類的execute方法中。
public class LaunchActivityItem extends ClientTransactionItem { @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mIsForward, mProfilerInfo, client); client.handleLaunchActivity(r, pendingActions, null /* customIntent */); } }
從架構的角度來講,此次重構的效果很好。使用狀態模式,使得Android這部分代碼,就是OOP的了。咱們把寫在H類的handleMessage方法中的switch分支語句,拆分到不少類中,這就符合了五大設計原則中的開閉原則,寧可有100個類,每一個類有100行代碼,也不要有一個一萬行代碼的類。好處是,當我想改動Resume這個狀態的業務邏輯時,我只要在ResumeActivityItem類中修改並進行測試就能夠了,影響的範圍很小。
可是此次重構也有缺點,OOP的缺點就是代碼會讓人看不懂,由於只有在運行時才知道到底實例化的是哪一個類,這讓本來清晰的Android Activity消息邏輯變得支離破碎。
按照這個趨勢,四大組件之一的Service,它在H類中也有不少消息,也是有不少生命週期函數,Android的下個版本,極有可能把Service也重構爲狀態模式。
3.1.4 Android P針對於H的Hook
Android P把H類中的100-109這10個消息都刪除了,取而代之的是159這個消息,名爲EXECUTE_TRANSACTION。
這就致使咱們以前的插件化解決方案,在Android P上是不生效的,會由於找不到100這個消息,而不能把StubActiivty換回爲插件中的Activity。爲此,咱們須要攔截159這個消息。攔截後,咱們又要面對如何判斷當前這個消息究竟是Launch,仍是Pause或者Resume。
關鍵在於H類的handleMessage方法的Message參數。這個Message的obj字段,在Message是159的時候,返回的是ClientTransacion類型對象,它內部有一個mActivityCallbacks集合:
public class ClientTransaction implements Parcelable, ObjectPoolItem { private List<ClientTransactionItem> mActivityCallbacks; }
這個mActivityCallbacks集合中,存放的是ClientTransactionItem的各類子類對象,好比LaunchActivityItem、DestoryActivityListItem。咱們能夠判斷這個集合中的值,發現有某個元素是LaunchActivityItem類型的,那麼就至關於捕獲到了啓動Activity的那個消息。
定位到LaunchActivityItem類的對象,它內部有一個mIntent字段,裏面存放的就是要啓動的Activity名稱,目前值是StubActivity。在這裏把它替換爲真正要啓動的插件Activity,代碼以下所示:
class MockClass2 implements Handler.Callback { Handler mBase; public MockClass2(Handler base) { mBase = base; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { // ActivityThread裏面 "LAUNCH_ACTIVITY" 這個字段的值是100 // 原本使用反射的方式獲取最好, 這裏爲了簡便直接使用硬編碼 case 100: //for API 28如下 handleLaunchActivity(msg); break; case 159: //for API 28 handleActivity(msg); break; } mBase.handleMessage(msg); return true; } private void handleActivity(Message msg) { // 這裏簡單起見,直接取出TargetActivity; Object obj = msg.obj; List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks"); if(mActivityCallbacks.size() > 0) { String className = "android.app.servertransaction.LaunchActivityItem"; if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) { Object object = mActivityCallbacks.get(0); Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent"); Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT); intent.setComponent(target.getComponent()); } } } }
在Android P以前,Instrumentation的newActivity方法。邏輯以下:
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (Activity)cl.loadClass(className).newInstance(); }
到了Android P,則改寫了Instrumentation類的部分邏輯。它會在newActivity方法中,檢查Instrumentation的mThread變量,若是爲空,就會拋出一個異常:
public class Instrumentation { public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { String pkg = intent != null && intent.getComponent() != null ? intent.getComponent().getPackageName() : null; return getFactory(pkg).instantiateActivity(cl, className, intent); } private AppComponentFactory getFactory(String pkg) { if (pkg == null) { Log.e(TAG, "No pkg specified, disabling AppComponentFactory"); return AppComponentFactory.DEFAULT; } if (mThread == null) { Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation," + " disabling AppComponentFactory", new Throwable()); return AppComponentFactory.DEFAULT; } LoadedApk apk = mThread.peekPackageInfo(pkg, true); // This is in the case of starting up "android". if (apk == null) apk = mThread.getSystemContext().mPackageInfo; return apk.getAppFactory(); } }
咱們在本書第5章介紹給一種Hook方案,攔截Instrumentation類的execStartActivity方法,以下所示:
public class HookHelper { public static void attachContext() throws Exception{ // 先獲取到當前的ActivityThread對象 Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread"); // 拿到原始的 mInstrumentation字段 Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread, "mInstrumentation"); // 建立代理對象 Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation); // 偷樑換柱 RefInvoke.setFieldObject(currentActivityThread, "mInstrumentation", evilInstrumentation); } } public class EvilInstrumentation extends Instrumentation { private static final String TAG = "EvilInstrumentation"; // ActivityThread中原始的對象, 保存起來 Instrumentation mBase; public EvilInstrumentation(Instrumentation base) { mBase = base; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { Log.d(TAG, "XXX到此一遊!"); // 開始調用原始的方法, 調不調用隨你,可是不調用的話, 全部的startActivity都失效了. // 因爲這個方法是隱藏的,所以須要使用反射調用;首先找到這個方法 Class[] p1 = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class}; Object[] v1 = {who, contextThread, token, target, intent, requestCode, options}; return (ActivityResult) RefInvoke.invokeInstanceMethod( mBase, "execStartActivity", p1, v1); } }
這段代碼,咱們把系統原先的Instrumentation替換成EvilInstrumentation,在Android P如下的系統是能夠運行的,可是在Android P上就會拋出Uninitialized ActivityThread, likely app-created Instrumentation的異常,顯然這是由於EvilInstrumentation的mThread爲空致使的。
想要解決這個問題,就必須重寫EvilInstrumentation中的newActivity方法,以下所示:
public class EvilInstrumentation extends Instrumentation { //省略了部分代碼 public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return mBase.newActivity(cl, className, intent); } }
這樣編碼,即便是EvilInstrumentation,在執行newActivity方法的時候,也會執行原先Instrumentation的newActivity方法,Instrumentation的mThread字段不是null,因此就不會拋出上述的異常信息了。