Android外掛化的相容性(中):Android P的適配

包建強發表於2018-08-23

     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,所以就不會丟擲上述的異常資訊了。

相關文章