Android插件化(1)啟動(dòng)未注冊的Activity

插件化技術(shù)和熱修復(fù)技術(shù)都屬于動(dòng)態(tài)加載技術(shù),從普及率的角度來看,插件化沒有熱修復(fù)的普及率高,主要原因是占大多數(shù)的中小型應(yīng)用很少也沒有必要去采用插件化技術(shù)。雖然插件化的普及率現(xiàn)在還不算高,但是理解插件化的原理對于應(yīng)用開發(fā)的技術(shù)提升有很大幫助,可以使你更好的地理解系統(tǒng)源碼,并將系統(tǒng)源碼和應(yīng)用開發(fā)相結(jié)合。

動(dòng)態(tài)加載技術(shù)

在講到插件化原理之前,需要先了解它的前身:動(dòng)態(tài)加載技術(shù)。在傳統(tǒng)Android開發(fā)中,一旦應(yīng)用程序的代碼被打包成APK并上傳到各個(gè)應(yīng)用市場,我們就不能修改App的源碼了,只能通過服務(wù)器來控制程序中預(yù)留的分支代碼邏輯。但是大多數(shù)情況下我們都無法提前預(yù)知新的需求和突發(fā)情況,也就無法提前在代碼中預(yù)留分支,這個(gè)時(shí)候就需要采用動(dòng)態(tài)加載技術(shù):在App運(yùn)行時(shí),動(dòng)態(tài)加載一些程序中原本不存在的可執(zhí)行文件并執(zhí)行這些文件中 的代碼邏輯。在Android中,可執(zhí)行文件分為兩種,一種是動(dòng)態(tài)鏈接庫so,另一種是dex相關(guān)文件(dex以及包含dex的jar/apk文件)。在之前的文章Android熱修復(fù)及原理總結(jié)中也提到了上述可執(zhí)行文件的加載,這是因?yàn)闊嵝迯?fù)技術(shù)本身也是由動(dòng)態(tài)加載技術(shù)派生出來的。

插件化的定義

插件化的App有宿主和插件兩部分組成,宿主是指先被安裝在手機(jī)上的APK,就是平常我們加載的普通APK。插件一般指的是經(jīng)過處理的APK、so和dex等文件,插件可以被宿主動(dòng)態(tài)加載并執(zhí)行,有的插件也可以作為App獨(dú)立運(yùn)行。所以,插件化的定義可以理解為:將一個(gè)應(yīng)用按照插件的方式進(jìn)行改造的過程。

Activity的插件化

四大組件的插件化是插件化技術(shù)的核心知識(shí)點(diǎn),而Activity的插件化又是四大組件中最重要的。Activity的插件化實(shí)質(zhì)上指的就是啟動(dòng)插件中的Activity,它和普通的Activity區(qū)別在于插件中的Activity,沒有在宿主App的AndroidManifest.xml文件中注冊。
Activity的插件化主要有三種實(shí)現(xiàn)方式,分別是反射實(shí)現(xiàn),接口實(shí)現(xiàn)和Hook技術(shù)實(shí)現(xiàn)。反射實(shí)現(xiàn)會(huì)對性能有所影響,主流的插件化框架沒有采用這種方式,關(guān)于接口實(shí)現(xiàn),可以閱讀Dynamic-load-apk的源碼,這里不多做介紹,目前Hook技術(shù)實(shí)現(xiàn)是主流,本文將主要介紹Hook技術(shù)實(shí)現(xiàn)的Activity插件化。
Hook技術(shù)實(shí)現(xiàn)主要有兩種解決方案,一種是通過Hook IActivityManager來實(shí)現(xiàn),另一種是Hook Intrumentation實(shí)現(xiàn)。主要也就是Hook點(diǎn)的選擇不同,要了解Hook點(diǎn)的選擇,我們需要從整體上再來回顧下Activity的啟動(dòng)過程

普通的Actiivty(A->B)啟動(dòng)整體過程回顧

在應(yīng)用程序進(jìn)程中的ActivityA向AMS請求創(chuàng)建ActivityB,AMS會(huì)對ActivityB的生命周期和棧進(jìn)行管理,校驗(yàn)ActivityB等,如果ActivityB滿足AMS的校驗(yàn),AMS就會(huì)請求應(yīng)用程序進(jìn)程中的ActivityThread去創(chuàng)建并啟動(dòng)ActivityB。
image.png
Hook IActivityManager方案的實(shí)現(xiàn)

如上圖所示,Activity的啟動(dòng)必須經(jīng)過AMS的校驗(yàn),而這個(gè)校驗(yàn)過程的關(guān)鍵,就是要檢查Activity是否已經(jīng)在AndroidManifest.xml文件中注冊。AMS存在于SystemServer進(jìn)程中,我們無法直接修改,只能在應(yīng)用程序進(jìn)程中做文章。可以采用預(yù)先占坑的方式來解決沒有在AndroidManifest.xml文件中注冊的問題,具體做法就是再上圖的【步驟1】之前使用一個(gè)在AndroidManifest.xml中注冊的Activity來進(jìn)行占坑,用來通過AMS的校驗(yàn)。接著在【步驟2】之后用插件的Activity替換占坑的Activity。
由于篇幅有限,這里省略了插件Activity的加載邏輯,想了解的同學(xué)可以看看這篇關(guān)于ClassLoader的文章。首先直接創(chuàng)建一個(gè)TargetActivity代表已經(jīng)加載進(jìn)來的插件Activity。

public class TargetActivity extends Activity {
    private final static String TAG = "TargetActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_target);
        Log.e(TAG,"onCreate");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG,"onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG,"onResume");
    }
}

代碼很簡單,沒有多余的邏輯,我們這里主要是通過在生命周期函數(shù)中打印log,來驗(yàn)證TargetActivity是否啟動(dòng)。接著我們在創(chuàng)建一個(gè)用來占坑的SubActivity。代碼如下:

public class SubActivity extends AppCompatActivity {

    private final static String TAG = "SubActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sub);
        Log.e(TAG,"onCreate");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG,"onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG,"onResume");
    }
}

然后在AndroidManifest.xml中注冊SubActivity。

<application
        android:name=".TestApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SubActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

TargetActivity用來代表插件中的Activity,因此不需要在AndroidManifest.xml中進(jìn)行注冊。如果我們直接在MainActivity中啟動(dòng)TargetActivity,通常情況下肯定是要報(bào)錯(cuò)的。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    Button start_one_btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start_one_btn = findViewById(R.id.start_one_btn);
        start_one_btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.start_one_btn:
                Intent intentOne = new Intent(this,TargetActivity.class);
                startActivity(intentOne);
                break;
        }
    }
}

接下來就是本方案的重點(diǎn)了。

使用占坑的SubActivity通過AMS的校驗(yàn)

為了防止上面提到的報(bào)錯(cuò),需要將啟動(dòng)的TargetActivity替換成SubActivity。用SubActivity來通過AMS的校驗(yàn)。這里需要指出Android 8.0與Android 7.0的AMS的相關(guān)類的代碼和結(jié)構(gòu)有一些差別,主要是Android8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接采用AIDL進(jìn)行進(jìn)程間通信。Android7.0的Activity的啟動(dòng)會(huì)調(diào)用到ActivityManagerNative的getDefault方法,代碼如下:

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
     ...
     static public IActivityManager getDefault() {
       return gDefault.get();
     }
     ...
     private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
    ...
}

getDefault方法返回了IActivityManager類型的對象,IActivityManager借助Singleton類來實(shí)現(xiàn)單例,而且Singelton類型的對象gDefault又是靜態(tài)對象,因此IActivityManager是一個(gè)比較好的Hook點(diǎn)。Android8.0的Activity的啟動(dòng)會(huì)調(diào)用到ActivityManager的getService方法,代碼如下:

public class ActivityManager {
    ...
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }
    ...
    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
      ...
    }
    ...
}

同樣的getService方法也返回了IActivityManager類型的對象,這個(gè)IActivityManager對象也是借助Singleton類來實(shí)現(xiàn)單例,這樣就可以確定了無論是Android 8.0或Android 7.0,IActivityManager都是一個(gè)比較好的Hook點(diǎn)。同時(shí)我們還需要了解下Singleton類的源碼,便于后面的Hook實(shí)現(xiàn):

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

接下來開始定義替換IActivityManager的代理類IActivityManagerProxy,這里用到的是動(dòng)態(tài)代理機(jī)制,代碼如下:

public class IActivityManagerProxy implements InvocationHandler {

    private Object iActivityManager;

    public final static String TARGET_NAME = "target_activity";

    public IActivityManagerProxy(Object iActivityManager){
        this.iActivityManager = iActivityManager;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("startActivity")){
            Intent intent = null;
            int index = 0;
            for(int i = 0;i < args.length;i++){
                if(args[i] instanceof Intent){
                    index = i;
                    intent = (Intent)args[i];
                    break;
                }
            }
            if(intent.getComponent().getClassName().contains("TargetActivity")){
                Intent proxyIntent = new Intent();
                proxyIntent.putExtra(TARGET_NAME,intent);
                proxyIntent.setClassName("com.zacky.activityplugintest",
                        "com.zacky.activityplugintest.SubActivity");
                args[index] = proxyIntent;
            }
        }

        return method.invoke(iActivityManager, args);
    }
}

簡述其邏輯:攔截startActivity方法的調(diào)用,獲取參數(shù)args中的第一個(gè)Intent對象,判斷它要啟動(dòng)的組件是不是TargetActivity,如果是就創(chuàng)建一個(gè)新的Intent對象proxyIntent,用來啟動(dòng)SubActivity,并把這個(gè)啟動(dòng)TargetActivity的Intent保存到proxyIntent中,用于之后還原TargetActivity。然后將參數(shù)args中的Intent參數(shù)替換成proxyIntent。這樣啟動(dòng)的目標(biāo)就變成了SubActivity,用來通過AMS的校驗(yàn)。最后用IActivityManagerProxy來替換IActivityManager。代碼如下:

private void initHookActivityManagerAndH() throws Exception {
        Object defaultSingleton;
        if(Build.VERSION.SDK_INT >= 26) {
            Class activityManagerClass = Class.forName("android.app.ActivityManager");
            Field songletonField =
                    activityManagerClass.getDeclaredField("IActivityManagerSingleton");
            songletonField.setAccessible(true);
            defaultSingleton = songletonField.get(activityManagerClass);
        }else{
            Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
            Field songletonField = activityManagerClass.getDeclaredField("gDefault");
            songletonField.setAccessible(true);
            defaultSingleton = songletonField.get(activityManagerClass);
        }
        Class singletonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        Object iActivityManager = mInstanceField.get(defaultSingleton);
        Object iActivityManagerProxy = Proxy.newProxyInstance(getClassLoader(),
                new Class[]{Class.forName("android.app.IActivityManager")},
                new IActivityManagerProxy(iActivityManager));
        mInstanceField.set(defaultSingleton,iActivityManagerProxy);

        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField =
                activityThreadClass.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object sCurrentActivityThread = sCurrentActivityThreadField.get(activityThreadClass);
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mH = mHField.get(sCurrentActivityThread);
        Class handlerCLass = Class.forName("android.os.Handler");
        Field mCallbackField = handlerCLass.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mH,new PluginHandlerCallback((Handler) mH));
    }

主要也就是用到反射,這個(gè)Hook的方法其實(shí)可以在啟動(dòng)TargetActivity之前的任何時(shí)機(jī)調(diào)用都行。我這里是在自定義Application的onCreate方法中調(diào)用的,代碼如下:

public class TestApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        try {
            initHookActivityManagerAndH();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
還原插件Activity

細(xì)心的同學(xué)肯定發(fā)現(xiàn)了initHookActivityManagerAndH方法中,還通過反射getActivityThread類中的mH對象設(shè)置了mCallback。這個(gè)就和還原TargetActivity有關(guān)。后面會(huì)講到。
上面已經(jīng)演示了用占坑的SubActivity替換TargetActivity,通過了AMS的校驗(yàn),但我們的真實(shí)目的是啟動(dòng)代表插件的TargetActivity,所以還需要用TargetActivity來替換占坑的SubActivity。替換的時(shí)機(jī)就是上面的Activity啟動(dòng)過程演示圖中的【步驟2】之后。簡單來說就是,通過AMS的校驗(yàn)后,AMS就會(huì)請求應(yīng)用程序進(jìn)程中的ActivityThread去創(chuàng)建并啟動(dòng)目標(biāo)Activity。ActivityThread會(huì)通過H類將代碼邏輯的處理切換到主線程中,H類是ActivityThread的內(nèi)部類并繼承自Handler。了解Activity啟動(dòng)過程的朋友對他肯定不陌生。但是由于Android 9.0的源碼,將ActivityThread類中的相關(guān)代碼邏輯做了一些改動(dòng),這里有必要簡單的介紹下:

Android 9.0引入了ClientTransaction、ClientLifecycleManager和ClientLifecycleHandler來輔助管理Activity的生命周期。以Activity的啟動(dòng)過程為例,在ActivityStackSupervisor.realStartActivityLocked方法中為ClientTransaction對象添加LaunchActivityItem的callback,然后設(shè)置當(dāng)前的生命周期狀態(tài),再調(diào)用ClientLifecycleManager.scheduleTransaction方法執(zhí)行。這個(gè)方法的內(nèi)部會(huì)調(diào)用ClientTransaction的transaction方法,transaction方法又會(huì)去調(diào)用IApplicationThread的scheduleTransaction方法,轉(zhuǎn)而調(diào)用ActivityThread的scheduleTransaction方法,這個(gè)方法繼承自ClientLifecycleHandler。在ClientLifecycleHandler的scheduleTransaction方法中,會(huì)將transaction通過Message傳遞給mH來處理,最終由TransactionExecutor來執(zhí)行LaunchActivityItem的execute方法,execute方法里面的代碼,就是Android9.0之前在mH的handleMessage方法中處理LAUNCH_ACTIVITY的相關(guān)代碼,創(chuàng)建ActivityClientRecord對象,調(diào)用handleLaunchActivity方法,創(chuàng)建和啟動(dòng)Activity。對相關(guān)細(xì)節(jié)感興趣的同學(xué),可以參考(Android 9.0)Activity啟動(dòng)流程源碼分析

ActivityThread通過mH將代碼邏輯的處理切換到主線程中執(zhí)行,一個(gè)應(yīng)用程序進(jìn)程中,只有一個(gè)ActivityThread對象sCurrentActivityThread,代表主線程。每個(gè)ActivityThread對象包含一個(gè)final修飾的mH對象,也就是說每個(gè)應(yīng)用程序進(jìn)程中也就只有唯一的mH對象,而Activity的生命周期管理肯定會(huì)經(jīng)過mH的handleMessage方法來轉(zhuǎn)發(fā)處理。所以mH可以還原TargetActivity的Hook點(diǎn)。H繼承自Handler。了解Handler機(jī)制的同學(xué),肯定都知道,Handler的dispatchMessage方法分發(fā)Message時(shí),會(huì)先判斷mCallback是否為null,如果不為null,就會(huì)執(zhí)行mCallback的handleMessage方法,否則執(zhí)行handler自身的handleMessage方法。代碼如下:

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

那么Hook mH的思路就出來了,我們可以用自定義的Callback來替換mH的mCallback,Callback定義如下

public class PluginHandlerCallback implements Handler.Callback {

    public static int EXECUTE_TRANSACTION = 159;

    Handler handler;
    public PluginHandlerCallback(Handler handler){
        this.handler = handler;
    }

    @Override
    public boolean handleMessage(@NonNull Message msg) {
            try {
                if(EXECUTE_TRANSACTION == msg.what){
                    Intent intent;
                    Class activityClientRecordClass =
                            Class.forName("android.app.servertransaction.ClientTransaction");
                    Method getCallbacksMethod  = activityClientRecordClass.getDeclaredMethod("getCallbacks");
                    getCallbacksMethod.setAccessible(true);
                    List mActivityCallbacks = (List) getCallbacksMethod.invoke(msg.obj,null);
                    if(mActivityCallbacks != null){
                        for (Object item : mActivityCallbacks){
                            Class launchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem");
                            if(item.getClass() == launchActivityItemClass){
                                Field mIntentField = launchActivityItemClass.getDeclaredField("mIntent");
                                mIntentField.setAccessible(true);
                                intent = (Intent)mIntentField.get(item);
                                if(intent.getComponent().getClassName().contains("SubActivity")){
                                    if(intent!=null && intent.hasExtra(TARGET_NAME)
                                            && intent.getParcelableExtra(TARGET_NAME) instanceof Intent){
                                        Intent targetIntent = intent.getParcelableExtra(TARGET_NAME);
                                        mIntentField.set(item,targetIntent);
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        handler.handleMessage(msg);
        return true;
    }
}

在PluginHandlerCallback的構(gòu)造方法中傳入一個(gè)Handler對象,實(shí)際使用時(shí)就是mH。在定義handleMessage方法的邏輯之前我們需要了解下ClientTransaction類的結(jié)構(gòu),因?yàn)閺纳厦嫣岬降腁ndroid9.0的Activity啟動(dòng)過程中,有mH處理的Message對象的obj屬性是一個(gè)ClientTransaction對象。ClientTransaction代碼如下:

public class ClientTransaction implements Parcelable, ObjectPoolItem {

    /** A list of individual callbacks to a client. */
    private List<ClientTransactionItem> mActivityCallbacks;
    ...
    /** Get the list of callbacks. */
    @Nullable
    List<ClientTransactionItem> getCallbacks() {
        return mActivityCallbacks;
    }
}

而最終調(diào)用handleLaunchActivity方法的LaunchActivityItem對象,就存放在mActivityCallbacks列表中。

public class LaunchActivityItem extends ClientTransactionItem {
    ...
    private Intent mIntent;
    ...
    @Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        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 */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }

}

由此我們就可以理清handleMessage方法中的代碼邏輯了。我們首先要根據(jù)msg.what來找到和Activity的生命周期管理相關(guān)的Message,通過反射找到LaunchActivityItem對象和它的mIntent變量,這個(gè)mIntent對象就是最終創(chuàng)建和啟動(dòng)Activity的Intent,我們需要做的就是用最初啟動(dòng)TargetActivity的Intent來替換這個(gè)mIntent。在handleMessage方法的最后,調(diào)用handler.handleMessage(msg)來處理mH的原有邏輯。
至此,PluginHandlerCallback的定義就對應(yīng)了initHookActivityManagerAndH方法中,通過反射來Hook mH和mCallback的那一段代碼。
代碼講解完畢,運(yùn)行程序,如點(diǎn)擊按鈕啟動(dòng)TargetActivity,查看Logcat可以看到程序不但沒有報(bào)錯(cuò),TargetActivity也成功創(chuàng)建并啟動(dòng)。

Hook Intrumentation方案的實(shí)現(xiàn)

上文中提到了Activity的插件化,除了Hook IActivityManager方案之外,還可以通過Hook Intrumentation來實(shí)現(xiàn)。首先還是回顧下Activity啟動(dòng)過程,Intrumentation的作用:

  • 在Activity類的startActivityForResult方法中會(huì)調(diào)用Instrumentation的execStartActivity方法,在這個(gè)方法里面就會(huì)AMS的startActivity方法,可以說,mInstrumentation的execStartActivity方法是Activity啟動(dòng)流程從應(yīng)用程序進(jìn)程轉(zhuǎn)到AMS所在的SystemServer進(jìn)程的過渡。
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
        ...
}
  • 在ActivityThread的performLaunchActivity方法中會(huì)調(diào)用Instrumentation的newActivity方法來創(chuàng)建Activity的實(shí)例。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      ...
      activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
      ...
}

可以看到,剛好一個(gè)是在AMS的校驗(yàn)之前,一個(gè)是在通過AMS校驗(yàn)之后。可以作為合適的Hook點(diǎn)。Hook Intrumentation方案比Hook IActivityManager方案實(shí)現(xiàn)要簡單一些,由于Android不同版本的源碼對IActivityManager的實(shí)現(xiàn)會(huì)有變化,相比之下,Intrumentation在Activity的啟動(dòng)過程中的作用和方法的調(diào)用時(shí)機(jī)并沒有什么變化。并且現(xiàn)在人氣很高的插件化開源框架VirtualAPK,也是用Hook Intrumentation方案來實(shí)現(xiàn)Activity的插件化。
Hook Intrumentation方案的實(shí)現(xiàn)同樣需要用到占坑的Activity,所以這里和Hook IActivityManager方案的演示代碼有重復(fù)的地方,不多贅述。
首先,我們自定義一個(gè)Instrumentation,代碼如下:

public class ProxyInstrumentation extends Instrumentation {

    public final static String TARGET_NAME = "target_activity";

    private Instrumentation instrumentation;

    private PackageManager mPackageManager;

    public ProxyInstrumentation(Instrumentation instrumentation,PackageManager mPackageManager){
        this.instrumentation = instrumentation;
        this.mPackageManager = mPackageManager;
    }
    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
                                            Intent intent, int requestCode, Bundle options){
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent,PackageManager.MATCH_ALL);
        if(infos==null || infos.size()==0){
            Intent proxyIntent = new Intent();
            proxyIntent.putExtra(TARGET_NAME,intent);
            proxyIntent.setClassName("com.zacky.activityplugintest",
                    "com.zacky.activityplugintest.SubActivity");
            try {
                Method execStartActivityMethod =
                        Instrumentation.class.getDeclaredMethod("execStartActivity",
                        Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
                execStartActivityMethod.setAccessible(true);
                ActivityResult activityResult = (ActivityResult)execStartActivityMethod.invoke(instrumentation,
                        who,contextThread,token,target,proxyIntent,requestCode,options);
                return activityResult;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if(intent.hasExtra(TARGET_NAME) && intent.getParcelableExtra(TARGET_NAME) instanceof Intent){
            Intent targetIntent = intent.getParcelableExtra(TARGET_NAME);
            return super.newActivity(cl, targetIntent.getComponent().getClassName(), targetIntent);
        }else{
            return super.newActivity(cl, className, intent);
        }
    }
}

因?yàn)镮nstrumentation類不是接口,不能用動(dòng)態(tài)代理實(shí)現(xiàn),我們用自定義的ProxyInstrumentation繼承自Instrumentation。
重寫execStartActivity方法,判斷待啟動(dòng)的Activity是否已經(jīng)在AndroidManifest.xml中注冊。如果沒有,就創(chuàng)建一個(gè)啟動(dòng)SubActivity的Intent對象proxyIntent,用來替換原來的intent。同時(shí)將intent保存在proxyIntent中,用于之后還原TargetActivity。然后通過反射調(diào)用構(gòu)造方法中傳進(jìn)來的instrumentation對象的execStartActivity方法,這樣就實(shí)現(xiàn)了用SubActivity來通過AMS的驗(yàn)證。
重寫newActivity方法,實(shí)現(xiàn)TargetActivity的還原。判斷也很簡單。如果傳進(jìn)來的參數(shù)intent里包含了啟動(dòng)TargetActivity的Intent,調(diào)用父類Instrumentation的newActivity方法時(shí),就傳入TargetActivity的類名。這樣發(fā)就完成了TargetActivity的還原。
然后,編寫initHookIntrumentation方法,用自定義的ProxyInstrumentation,替換代表當(dāng)前應(yīng)用程序主線程的ActivtyThread類型的對象sCurrentActivityThread的mInstrumentation。代碼如下:

private void initHookIntrumentation() throws Exception{
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField =
                activityThreadClass.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object sCurrentActivityThread = sCurrentActivityThreadField.get(activityThreadClass);
        Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(sCurrentActivityThread);
        ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(mInstrumentation,getPackageManager());
        mInstrumentationField.set(sCurrentActivityThread,proxyInstrumentation);
    }

筆者這里同樣是在自定義Application的onCreate方法中調(diào)用initHookIntrumentation方法。然后運(yùn)行程序,如點(diǎn)擊按鈕啟動(dòng)TargetActivity,查看Logcat可以看到程序不但沒有報(bào)錯(cuò),TargetActivity也成功創(chuàng)建并啟動(dòng)。

總結(jié)

實(shí)現(xiàn)Activity的插件化,實(shí)際上就是啟動(dòng)插件中未在AndroidManifest文件中注冊的Activity。主要的方案就是先用一個(gè)已在AndroidManifest.xml中注冊的Activity來占坑,用來通過AMS的校驗(yàn),接著在合適的時(shí)機(jī)用插件Activity替換占坑的Activity。

本文參考:
《Android進(jìn)階解密》
(Android 9.0)Activity啟動(dòng)流程源碼分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評論 2 372