Android外掛化原理(一)Activity外掛化

劉望舒發表於2018-05-28

相關文章

Android深入四大元件系列
Android解析AMS系列
Android解析ClassLoader系列

前言

四大元件的外掛化是外掛化技術的核心知識點,而Activity外掛化更是重中之重,Activity外掛化主要有三種實現方式,分別是反射實現、介面實現和Hook技術實現。反射實現會對效能有所影響,主流的外掛化框架沒有采用此方式,關於介面實現可以閱讀dynamic-load-apk的原始碼,這裡不做介紹,目前Hook技術實現是主流,因此本篇文章主要介紹Hook技術實現。 Hook技術實現主要有兩種解決方案 ,一種是通過Hook IActivityManager來實現,另一種是Hook Instrumentation實現。在講到這兩個解決方案前,我們需要從整體上了解Activity的啟動流程。

1.Activity的啟動過程回顧

Activity的啟動過程主要分為兩種,一種是根Activity的啟動過程,一種是普通Activity的啟動過程。關於根Activity的啟動過程在在介紹過,這裡來簡單回顧下,如下圖所示。

Android外掛化原理(一)Activity外掛化
圖 1

首先Launcher程式向AMS請求建立根Activity,AMS會判斷根Activity所需的應用程式程式是否存在並啟動,如果不存在就會請求Zygote程式建立應用程式程式。應用程式程式啟動後,AMS會請求應用程式程式建立並啟動根Activity。 普通Activity和根Activity的啟動過程大同小異,但是沒有這麼複雜,因為不涉及應用程式程式的建立,跟Laucher也沒關係,如下圖所示。

Android外掛化原理(一)Activity外掛化
圖2

上圖抽象的給出了普通Acticity的啟動過程。在應用程式程式中的Activity向AMS請求建立普通Activity(步驟1),AMS會對 這個Activty的生命週期管和棧進行管理,校驗Activity等等。如果Activity滿足AMS的校驗,AMS就會請求應用程式程式中的ActivityThread去建立並啟動普通Activity(步驟2)。

2.Hook IActivityManager方案實現

AMS是在SystemServer程式中,我們無法直接進行修改,只能在應用程式程式中做文章。可以採用預先佔坑的方式來解決沒有在AndroidManifest.xml中顯示宣告的問題,具體做法就是在上圖步驟1之前使用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來通過AMS的校驗。 接著在步驟2之後用外掛Activity替換佔坑的Activity,接下來根據這個解決方案我們來實踐一下。

2.1 註冊Activity進行佔坑

為了更好的講解啟動外掛Activity的原理,這裡省略了外掛Activity的載入邏輯,直接建立一個TargetActivity來代表已經載入進來的外掛Activity,接著我們再建立一個SubActivity用來佔坑。在AndroidManifest.xml中註冊SubActivity,如下所示。 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.liuwangshu.pluginactivity">S
    <application
       ...
        <activity android:name=".StubActivity"/>
    </application>
</manifest>
複製程式碼

TargetActivity用來代表已經載入進來的外掛Activity,因此不需要在AndroidManifest.xml進行註冊。如果我們直接在MainActivity中啟動TargetActivity肯定會報錯(android.content.ActivityNotFoundException異常)。

2.2 使用佔坑Activity通過AMS驗證

為了防止報錯,需要將啟動的TargetActivity替換為SubActivity,用SubActivity來通過AMS的驗證。在第六章時講過Android 8.0與7.0的AMS家族有一些差別,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接採用AIDL來進行程式間通訊。 Android7.0的Activity的啟動會呼叫ActivityManagerNative的getDefault方法,如下所示。 frameworks/base/core/java/android/app/ActivityManagerNative.java

 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類來實現單例,而且gDefault又是靜態的,因此IActivityManager是一個比較好的Hook點。 Android8.0的Activity的啟動會呼叫ActivityManager的getService方法,如下所示。 frameworks/base/core/java/android/app/ActivityManager.java

  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型別的物件,並且IActivityManager藉助了Singleton類來實現單例,確定了無論是Android7.0還是Android8.0,IActivityManager都是比較好的Hook點。Singleton類如下所示,後面會用到。 frameworks/base/core/java/android/util/Singleton.java

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;
        }
    }
}
複製程式碼

由於Hook需要多次對欄位進行反射操作,先寫一個欄位工具類FieldUtil:

FieldUtil.java

public class FieldUtil {
    public static Object getField(Class clazz, Object target, String name) throws Exception {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return field.get(target);
    }
    public static Field getField(Class clazz, String name) throws Exception{
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return field;
    }
    public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(target, value);
    }
複製程式碼

其中setField方法不會馬上用到,接著定義替換IActivityManager的代理類IActivityManagerProxy,如下所示。

public class IActivityManagerProxy implements InvocationHandler {
    private Object mActivityManager;
    private static final String TAG = "IActivityManagerProxy";
    public IActivityManagerProxy(Object activityManager) {
        this.mActivityManager = activityManager;
    }
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {//1
            Intent intent = null;
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            intent = (Intent) args[index];
            Intent subIntent = new Intent();//2
            String packageName = "com.example.liuwangshu.pluginactivity";
            subIntent.setClassName(packageName,packageName+".StubActivity");//3
            subIntent.putExtra(HookHelper.TARGET_INTENT, intent);//4
            args[index] = subIntent;//5
        }
        return method.invoke(mActivityManager, args);
    }
}
複製程式碼

Hook點IActivityManager是一個介面,建議採用動態代理。在註釋1處攔截startActivity方法,接著獲取引數args中第一個Intent物件,它是原本要啟動外掛TargetActivity的Intent。註釋2、3處新建一個subIntent用來啟動的StubActivity,註釋4處將這個TargetActivity的Intent儲存到subIntent中,便於以後還原TargetActivity。註釋5處用subIntent賦值給引數args,這樣啟動的目標就變為了StubActivity,用來通過AMS的校驗。 最後用代理類IActivityManagerProxy來替換IActivityManager,如下所示。

HookHelper.java

public class HookHelper {
    public static final String TARGET_INTENT = "target_intent";
    public static void hookAMS() throws Exception {
        Object defaultSingleton=null;
        if (Build.VERSION.SDK_INT >= 26) {//1
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
            //獲取activityManager中的IActivityManagerSingleton欄位
            defaultSingleton=  FieldUtil.getField(activityManageClazz ,null,"IActivityManagerSingleton");
        } else {
            Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
            //獲取ActivityManagerNative中的gDefault欄位
            defaultSingleton=  FieldUtil.getField(activityManagerNativeClazz,null,"gDefault");
        }
        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField= FieldUtil.getField(singletonClazz ,"mInstance");//2
        //獲取iActivityManager
        Object iActivityManager = mInstanceField.get(defaultSingleton);//3
        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
        mInstanceField.set(defaultSingleton, proxy);
    }
}
複製程式碼

首先在註釋1處對系統版本進行區分,最終獲取的是 Singleton<IActivityManager>型別的IActivityManagerSingleton或者gDefault欄位。註釋2處獲取Singleton類中的mInstance欄位,從前面Singleton類的程式碼可以得知mInstance欄位的型別為T,在註釋3處得到IActivityManagerSingleton或者gDefault欄位中的T的型別,T的型別為IActivityManager。最後動態建立代理類IActivityManagerProxy,用IActivityManagerProxy來替換IActivityManager。 自定義一個Application,在其中呼叫HookHelper 的hookAMS方法,如下所示。

MyApplication.java

public class MyApplication extends Application {
    @Override
    public void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            HookHelper.hookAMS();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

在MainActivity中啟動TargetActivity,如下所示。

MainActivity.java

public class MainActivity extends Activity {
    private Button bt_hook;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bt_hook = (Button) this.findViewById(R.id.bt_hook);
        bt_hook.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, TargetActivity.class);
                startActivity(intent);
            }
        });
    }
}
複製程式碼

點選Button時,啟動的並不是TargetActivity而是SubActivity,同時Log中列印了"hook成功",說明我們已經成功用SubActivity通過了AMS的校驗。

2.3 還原外掛Activity

前面用佔坑Activity通過了AMS的校驗,但是我們要啟動的是外掛TargetActivity,還需要用外掛TargetActivity來替換佔坑的SubActivity,這一替換的時機就在圖2的步驟2之後。 ActivityThread啟動Activity的過程,如圖3所示。

Android外掛化原理(一)Activity外掛化

圖3

ActivityThread會通過H將程式碼的邏輯切換到主執行緒中,H類是ActivityThread的內部類並繼承自Handler,如下所示。 frameworks/base/core/java/android/app/ActivityThread.java

private class H extends Handler {
public static final int LAUNCH_ACTIVITY         = 100;
public static final int PAUSE_ACTIVITY          = 101;
...
   public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
              }
...
}
複製程式碼

H中重寫的handleMessage方法會對LAUNCH_ACTIVITY型別的訊息進行處理,最終會呼叫Activity的onCreate方法。那麼在哪進行替換呢?接著來看Handler的dispatchMessage方法: frameworks/base/core/java/android/os/Handler.java

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製程式碼

Handler的dispatchMessage用於處理訊息,看到如果Handler的Callback型別的mCallback不為null,就會執行mCallback的handleMessage方法。因此,mCallback可以作為Hook點,我們可以用自定義的Callback來替換mCallback,自定義的Callback如下所示。 HCallback.java

public class HCallback implements Handler.Callback{
    public static final int LAUNCH_ACTIVITY = 100;
    Handler mHandler;
    public HCallback(Handler handler) {
        mHandler = handler;
    }
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
            Object r = msg.obj;
            try {
                //得到訊息中的Intent(啟動SubActivity的Intent)
                Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, "intent");
                //得到此前儲存起來的Intent(啟動TargetActivity的Intent)
                Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
                //將啟動SubActivity的Intent替換為啟動TargetActivity的Intent
                intent.setComponent(target.getComponent());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mHandler.handleMessage(msg);
        return true;
    }
}
複製程式碼

HCallback實現了Handler.Callback,並重寫了handleMessage方法,當收到訊息的型別為LAUNCH_ACTIVITY時,將啟動SubActivity的Intent替換為啟動TargetActivity的Intent。接著我們在HookHelper中定義一個hookHandler方法如下所示。

HookHelper.java

 public static void hookHandler() throws Exception {
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Object currentActivityThread= FieldUtil.getField(activityThreadClass ,null,"sCurrentActivityThread");//1
        Field mHField = FieldUtil.getField(activityThread,"mH");//2
        Handler mH = (Handler) mHField.get(currentActivityThread);//3
        FieldUtil.setField(Handler.class,mH,"mCallback",new HCallback(mH));
    }
複製程式碼

ActivityThread類中有一個靜態變數sCurrentActivityThread,用於表示當前的ActivityThread物件,因此在註釋1處獲取ActivityThread中定義的sCurrentActivityThread物件。註釋2處獲取ActivityThread類的mH欄位,接著在註釋3處獲取當前ActivityThread物件中的mH物件,最後用HCallback來替換mH中的mCallback。 在MyApplication的attachBaseContext方法中呼叫HookHelper的hookHandler方法,執行程式,當我們點選啟動外掛按鈕,發現啟動的是外掛TargetActivity。

2.4 外掛Activity的生命週期

外掛TargetActivity確實啟動了,但是它有生命週期嗎?這裡從原始碼角度來進行分析,Activity的finish方法可以觸發Activity的生命週期變化,和Activity的啟動過程類似,finish方法如下所示。 frameworks/base/core/java/android/app/Activity.java

 public void finish() {
        finish(DONT_FINISH_TASK_WITH_ACTIVITY);
    }
 private void finish(int finishTask) {
        if (mParent == null) {
            int resultCode;
            Intent resultData;
            synchronized (this) {
                resultCode = mResultCode;
                resultData = mResultData;
            }
            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
            try {
                if (resultData != null) {
                    resultData.prepareToLeaveProcess(this);
                }
                if (ActivityManager.getService()
                        .finishActivity(mToken, resultCode, resultData, finishTask)) {//1
                    mFinished = true;
                }
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishFromChild(this);
        }
    }
複製程式碼

finish方法的呼叫鏈和Activity的啟動過程是類似的,註釋1處會呼叫的AMS的finishActivity方法,接著是AMS通過ApplicationThread呼叫ActivityThread,ActivityThread向H傳送DESTROY_ACTIVITY型別的訊息,H接收到這個訊息會執行handleDestroyActivity方法,handleDestroyActivity方法又呼叫了performDestroyActivity方法,如下所示。

frameworks/base/core/java/android/app/ActivityThread.java

  private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ActivityClientRecord r = mActivities.get(token);//1
        ...
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnDestroy(r.activity);//2
               ...
            } catch (SuperNotCalledException e) {
             ...
            }
        }
        mActivities.remove(token);
        StrictMode.decrementExpectedActivityCount(activityClass);
        return r;
   
複製程式碼

註釋1處通過IBinder型別的token來獲取ActivityClientRecord,ActivityClientRecord用於描述應用程式中的Activity。在註釋2處呼叫Instrumentation的callActivityOnDestroy方法來呼叫Activity的OnDestroy方法,並傳入了r.activity。前面的例子我們用SubActivity替換了TargetActivity通過了AMS的校驗,這樣AMS只知道SubActivity的存在,那麼AMS是如何能控制TargetActivity生命週期的回撥的?我們接著往下看,啟動Activity時會呼叫ActivityThread的performLaunchActivity方法,如下所示。

frameworks/base/core/java/android/app/ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          ...
      
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);//1
           ...
             activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
           ...
            mActivities.put(r.token, r);//2
           ...
        return activity;
    }
複製程式碼

註釋1處根據Activity的類名用ClassLoader載入Acitivty,接著呼叫Activity的attach方法,將r.token賦值給Activity的成員變數mToken。在註釋2處將ActivityClientRecord根據r.token存在mActivities中(mActivities型別為ArrayMap<IBinder, ActivityClientRecord> ),再結合Activity的finish方法的註釋1處,可以得出結論:AMS和ActivityThread之間的通訊採用了token來對Activity進行標識,並且此後的Activity的生命週期處理也是根據token來對Activity進行標識的。 回到我們這個例子來,我們在Activity啟動時用外掛TargetActivity替換佔坑SubActivity,這一過程在performLaunchActivity方法呼叫之前,因此註釋2處的r.token指向的是TargetActivity,在performDestroyActivity的註釋1處獲取的就是代表TargetActivity的ActivityClientRecord,可見TargetActivity是具有生命週期的。

3.Hook Instrumentation方案實現

Hook Instrumentation實現要比Hook IActivityManager實現要簡潔一些,示例程式碼會和Hook IActivityManager實現有重複,重複的部分這裡不再贅述。 Hook Instrumentation實現同樣也需要用到佔坑Activity,與Hook IActivityManager實現不同的是,用佔坑Activity替換外掛Activity以及還原外掛Activity的地方不同。Acitivty的startActivity方法呼叫時序圖如圖4所示。

Android外掛化原理(一)Activity外掛化
圖4

從圖4可以發現,在Activity通過AMS校驗前,會呼叫Activity的startActivityForResult方法: frameworks/base/core/java/android/app/Activity.java

 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);
          ...
        } else {
         ...
        }
    }
複製程式碼

startActivityForResult方法中呼叫了Instrumentation的execStartActivity方法來啟用Activity的生命週期。 如圖3所示,ActivityThread啟動Activity的過程中會呼叫ActivityThread的performLaunchActivity方法,如下所示。 frameworks/base/core/java/android/app/ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {       
        ...
        //建立要啟動Activity的上下文環境
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //用類載入器來建立Activity的例項
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);//1
          ...
        } catch (Exception e) {
          ...
        }
      ...
        return activity;
    }
複製程式碼

註釋1處呼叫了mInstrumentation的newActivity方法,其內部會用類載入器來建立Activity的例項。看到這裡我們可以得到方案,就是在Instrumentation的execStartActivity方法中用佔坑SubActivity來通過AMS的驗證,在Instrumentation的newActivity方法中還原TargetActivity,這兩部操作都和Instrumentation有關,因此我們可以用自定義的Instrumentation來替換掉mInstrumentation。首先我們自定義一個Instrumentation,在execStartActivity方法中將啟動的TargetActivity替換為SubActivity,如下所示。

InstrumentationProxy.java

public class InstrumentationProxy extends Instrumentation {
    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;
    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        mInstrumentation = instrumentation;
        mPackageManager = packageManager;
    }
    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.putExtra(HookHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());//1
            intent.setClassName(who, "com.example.liuwangshu.pluginactivity.StubActivity");//2
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
            return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
                    target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
複製程式碼

首先查詢要啟動的Activity是否已經在AndroidManifest.xml中註冊了,如果沒有就在註釋1處將要啟動的Activity(TargetActivity)的ClassName儲存起來用於後面還原TargetActivity,接著在註釋2處替換要啟動的Activity為StubActivity,最後通過反射呼叫execStartActivity方法,這樣就可以用StubActivity通過AMS的驗證。在InstrumentationProxy 的newActivity方法還原TargetActivity,如下所示。

InstrumentationProxy.java

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
        IllegalAccessException, ClassNotFoundException {
    String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);
    if (!TextUtils.isEmpty(intentName)) {
        return super.newActivity(cl, intentName, intent);
    }
    return super.newActivity(cl, className, intent);
}
複製程式碼

newActivity方法中建立了此前儲存的TargetActivity,完成了還原TargetActivity。 編寫hookInstrumentation方法,用InstrumentationProxy替換mInstrumentation:

HookHelper.java

  public static void hookInstrumentation(Context context) throws Exception {
        Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
        Field mMainThreadField  =FieldUtil.getField(contextImplClass,"mMainThread");//1
        Object activityThread = mMainThreadField.get(context);//2
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field mInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");//3
        FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread),
                context.getPackageManager()));
    }
複製程式碼

註釋1處獲取ContextImpl類的ActivityThread型別的mMainThread欄位,註釋2出獲取當前上下文環境的ActivityThread物件。 註釋3出獲取ActivityThread類中的mInstrumentation欄位,最後用InstrumentationProxy來替換mInstrumentation。 在MyApplication的attachBaseContext方法中呼叫HookHelper的hookInstrumentation方法,執行程式,當我們點選啟動外掛按鈕,發現啟動的是外掛TargetActivity。

4. 總結

這一節我們學到了啟動外掛Activity的原理,主要的方案就是先用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來通過AMS的校驗,接著在合適的時機用外掛Activity替換佔坑的Activity。為了更好的講解啟動外掛Activity的原理,本小節省略了外掛Activity的載入邏輯,直接建立一個TargetActivity來代表已經載入進來的外掛Activity。同時這一節使我們更好的理解了Activity的啟動過程。更多的Android外掛化原理請檢視即將要出版的《Android進階之光》續作。

公眾號末尾1.5_副本.jpg

相關文章