1. 程式人生 > >Android外掛化的相容性(中):Android P的適配

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

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

相關推薦

Android外掛相容性Android P

     Android系統的每次版本升級,都會對原有程式碼進行重構,這就為外掛化帶來了麻煩。      Android P對外掛化的影響,主要體現在兩方面,一是它重構了H類中Activity相關的邏輯,另一個是它重構了Instrumentation。      3.1 H類的變身      3.1

Android外掛相容性Android O的

      首先宣告,《Android外掛化開發指南》這本書所介紹的Android底層是基於Android6.0(API level 23)的,而本書介紹的各種外掛化解決方案,以及配套的70多個例子,在Android7.0(API level 24)手機上測試都是能正常工作的。      如果讀者您的手機是

Android插件的兼容性Android P

有一個 tca pro 內部 bject load anon stat activity Android系統的每次版本升級,都會對原有代碼進行重構,這就為插件化帶來了麻煩。 Android P對插件化的影響,主要體現在兩方面,一是它重構了H類中Acti

Android 外掛分析3- Activity啟動流程

在真正分析外掛化技術前,我們必須瞭解一些必要的關於Android四大元件的相關知識。 以Activity為例,我們需要了解Activity啟動過程,才能有效的進行Hook實現外掛化。 以Android 8.1為例 我們啟動一個Activity通常會使用startActi

Android外掛探索資源載入

前情提要 PathClassLoader和DexClassLoader的區別 DexClassLoader的原始碼如下: public class DexClassLoader extends BaseDexClassLoader {

Android外掛原理Activity外掛

相關文章 前言 四大元件的外掛化是外掛化技術的核心知識點,而Activity外掛化更是重中之重,Activity外掛化主要有三種實現方式,分別是反射實現、介面實現和Hook技術實現。反射實現會對效能有所影響,主流的外掛化框架沒有采用此方式,關於介面實

Android外掛探索免安裝執行Activity

在上一篇中,我們介紹了兩種免安裝啟動Activity的方法。但是那兩種方法都有缺陷,必須在AndroidManifest.xml中註冊。那麼今天,我們來探索其它幾種不需要在清單檔案中註冊的啟動方式。 靜態代理啟動activity 通過前幾篇的探索我們

包建強的培訓課程10Android外掛從入門到精通

Android外掛化和熱修復 一.簡介 本課程結合講師多年來對Android外掛化技術的潛心研究,以及在千萬級使用者的App上長期實踐經驗,整理而成。本課程從四大元件的外掛化技術講起,中途會詳細剖析Android系統中與外掛化技術有關的底層概念,最後詳細介紹業界流行很廣的外

Android插件的兼容性Android O的

cto load 註意 android系統 自己 攔截 str oca 接口 首先聲明,《Android插件化開發指南》這本書所介紹的Android底層是基於Android6.0(API level 23)的,而本書介紹的各種插件化解決方案,以及配套的70多個例

Kubernetes 新概念 “Initializers”解析能讓你為叢集編寫外掛的新模型_Kubernetes中文社群

Kubernetes v1.7 新增了 Initializers,它可以用來方便地擴充套件准入控制,今天的文章來自 Google Kubernetes 現役工程師 Ahmet Alp Balkan,讓他帶領我們詳解 Initializer。通過上期的文章,我們瞭解了 Initializers

Android Plugin插樁式實現外掛開發-實現原理及Activity外掛實現

1. 前言在現在一些大型的Android應用中都採用了外掛化的開發方式,比如美團,支付寶及我們常用的微信等採用了插修的化的開發方式來進行開發的,既然國內一流的網際網路公司都採用這樣的方式來開發那它一定能帶給開發部署大型應用帶來很大的便捷,那麼外掛化的優勢在哪裡呢?1.1 外掛

Android開發知識Android事件處理機制事件分發、傳遞、攔截、處理機制的原理分析

  在本章節中,我們重點談論一下onTouch、onClick、onLongClick三個方法被回撥的過程。   在上一篇文章中,我們談到關於為View新增一個點選事件SetOnClickListener後,就可以通過回撥onClick方法來實現事件的響應

Pro Android學習筆記 ActionBar1Home圖標區

ces tom 新的 方便 find rac vertica lba manifest ?? Pro Android學習筆記(四八):ActionBar(1):Home圖標區 2013年03月10日 ? 綜合 ? 共 3256字 ? 字號 小 中 大 ? 評論關閉

Android項目實戰實現第一次進入軟件的引導頁

spl cli rate gets -i let ride open rtm 原文:Android項目實戰(三):實現第一次進入軟件的引導頁最近做的APP接近尾聲了,就是些優化工作了, 我們都知道現在的APP都會有引導頁,就是安裝之後第一次打開才顯示的引導頁面(介紹這個軟

Android項目實戰JazzyGridView和JazzyListView的使用

@+ java類 gif HR 使用 out tar 項目 適配器 原文:Android項目實戰(六):JazzyGridView和JazzyListView的使用GridView和ListView控件劃動的動畫效果 ---------------------------

Android項目實戰ViewPager切換動畫3.0版本以上有效果

技術 code info utf-8 play draw pos support addview 原文:Android項目實戰(四):ViewPager切換動畫(3.0版本以上有效果)學習內容來自“慕課網” 一般APP進去之後都會有幾張圖片來導航,

Android項目實戰Dialog主題Activity實現自定義對話框效果

utf 定義 nim 亮點 close .com 去除 span 代碼 原文:Android項目實戰(七):Dialog主題Activity實現自定義對話框效果想必大家都用過Dialog主題的Activity吧,用它來顯示自定義對話框效果絕對是一個非常不錯的選擇。 即把a

Android項目實戰CustomShapeImageView 自定義形狀的ImageView

重點 clas home 項目開發 logs clip com html days 原文:Android項目實戰(九):CustomShapeImageView 自定義形狀的ImageView一個兩年前出來的第三方類庫,具有不限於圓形ImageView的多種形狀ImageV

Android項目實戰十三淺談EventBus

app mage tar 一句話 creat 簡單 銷毀 second gradle 原文:Android項目實戰(十三):淺談EventBus概述: EventBus是一款針對Android優化的發布/訂閱事件總線。 主要功能是替代Intent,Handler,Bro

Android項目實戰自定義倒計時的TextView

初始 als time class nts 時間 自定義 計時 err 原文:Android項目實戰(十):自定義倒計時的TextView項目總結 --------------------------------------------------------------