Android系統的每次版本升級,都會對原有程式碼進行重構,這就為外掛化帶來了麻煩。
Android P對外掛化的影響,主要體現在兩方面,一是它重構了H類中Activity相關的邏輯,另一個是它重構了Instrumentation。
3.1 H類的變身
3.1.1 從Message和Handler說起
對於App開發人員而言,Message和Handler是耳熟能詳的兩個概念。我們簡單回顧一下,一條訊息是怎麼傳送和接收的。
首先,在App啟動的時候,會建立ActivityThread,這就是主執行緒,也叫UI執行緒。App的入口——main函式,就藏在ActivityThread中,
在main函式中,會建立MainLooper。MainLooper是一個死迴圈,專門負責接收訊息,也就是Message類。
Message類的定義如下,除了耳熟能詳的what和obj屬性外,還有一個不對App開放的變數target,它是一個Handler:
public final class Message implements Parcelable { public int what; public Object obj; Handler target; //以下省略很多程式碼哦 }
在App程式中,Application和四大元件的每個生命週期函式,都要和AMS程式進行跨程式通訊。
1)App程式把資料傳給AMS程式,是通過ActivityManagerNative完成的。
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()); } } } }
3.2 Instrumentation的變身
在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,所以就不會丟擲上述的異常資訊了。