Android個人項目插件化總結——方式二(Hook Instrumentation)

文章目錄

概述

第二種方式思路非常清晰,直接Hook Instrumentation。

由activity啓動流程知道,startActivity會交給Activity的mInstrumentation.execStartActivity()來處理。

最終會調用ActivityThread裏面的performLaunchActivity()方法。performLaunchActivity()方法進而調用 mInstrumentation.newActivity()會創建啓動Activity。

所以步驟很簡單。

因爲有Application.startActivity()和Activity.startActivity()。等會說Application.startActivity(),先說Activity.startActivity()。

  1. hook Activity的mInstrumentation變量
  2. hook ActivityThread的mInstrumentation變量
  3. 在我們的Instrumentation類裏面攔截execStartActivity方法,在裏面將Intent替換成我們的佔坑Activity。
  4. 在我們的Instrumentation類裏面攔截newActivity方法,將Intent替換回來,再完成Activity的創建。

代碼

首先是我們的HookedInstrumentation 類,我們通過反射得到原有的Instrumentation 放入我們的類中,在execStartActivity方法中替換Intent,完成AMS的驗證。newActivity方法中再將Intent替換回來。這裏需要注意一下,因爲在newActivity中傳入了我們自己的ClassLoader,關於ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);這個代碼先不要管,下篇會說怎樣加載插件APK的資源。

public class HookedInstrumentation extends Instrumentation   {
    public static final String TAG = "Lpp";
    protected Instrumentation mBase;
    private PluginManager mPluginManager;

    public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) {
        mBase = base;
        mPluginManager = pluginManager;
    }

    /**
     * 覆蓋掉原始Instrumentation類的對應方法,用於插件內部跳轉Activity時適配
     *
     * @Override
     */
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        mPluginManager.hookToStubActivity(intent);

        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity", Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("do not support!!!" + e.getMessage());
        }
    }

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (mPluginManager.hookToPluginActivity(intent)) {
            String targetClassName = intent.getComponent().getClassName();
            PluginApp pluginApp = mPluginManager.getLoadedPluginApk();
            Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);
            Log.d(TAG, "newActivity: " + activity);
            Log.d(TAG, "newActivity getApplicationContext: " + activity.getApplication());
            ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);
            return activity;
        }
        return super.newActivity(cl, className, intent);
    }

}

完成我們的Instrumentation類後,接下來就是反射設置給ActivityThread和Activity了。

Activity

 public void hookCurrentActivityInstrumentation(Activity activity) {
     ReflectUtil.setActivityInstrumentation(activity, sInstance);
 }
 public static void setActivityInstrumentation(Activity activity, PluginManager manager) {
     try {
         sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity);
         HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager);
         sActivityInstrumentationField.set(activity, instrumentation);
     } catch (IllegalAccessException e) {
         e.printStackTrace();
     }
 }

ActivityThread

 public void hookInstrumentation() {
     try {
         Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation();
         final HookedInstrumentation instrumentation =
                 new HookedInstrumentation(baseInstrumentation, this);

         Object activityThread = ReflectUtil.getActivityThread();
         ReflectUtil.setInstrumentation(activityThread, instrumentation);
     } catch (Exception e) {
         e.printStackTrace();
     }
 }

下面是初始化的代碼,得到ActivityThread等等

 public static final String METHOD_currentActivityThread = "currentActivityThread";
 public static final String CLASS_ActivityThread = "android.app.ActivityThread";
 public static final String FIELD_mInstrumentation = "mInstrumentation";
 public static final String TAG = "Lpp";

 private static Instrumentation sInstrumentation;
 private static Instrumentation sActivityInstrumentation;
 private static Field sActivityThreadInstrumentationField;
 private static Field sActivityInstrumentationField;
 private static Object sActivityThread;

 public static boolean init() {
     //獲取當前的ActivityThread對象
     Class<?> activityThreadClass = null;
     try {
         activityThreadClass = Class.forName(CLASS_ActivityThread);
         Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread);
         currentActivityThreadMethod.setAccessible(true);
         Object currentActivityThread = currentActivityThreadMethod.invoke(null);

         //拿到在ActivityThread類裏面的原始mInstrumentation對象
         Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation);
         instrumentationField.setAccessible(true);
         sActivityThreadInstrumentationField = instrumentationField;

         sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);
         sActivityThread = currentActivityThread;


         sActivityInstrumentationField =  Activity.class.getDeclaredField(FIELD_mInstrumentation);
         sActivityInstrumentationField.setAccessible(true);
         return true;
     } catch (ClassNotFoundException
             | NoSuchMethodException
             | IllegalAccessException
             | InvocationTargetException
             | NoSuchFieldException e) {
         e.printStackTrace();
     }

     return false;
 }

補充

上面說的是通過Activity.startActivity()完成跳轉時的情況。

當我們使用Application.startActivity()完成跳轉時,只需要hook ActivityThread裏面的Instrumentation就可以了。

讓我們來看爲什麼。

    getApplication().startActivity();
    
    public final Application getApplication() {
        return mApplication;
    }

Application繼承的是ContextWrapper,ContextWrapper又繼承自Context,所以我們在ContextWrapper裏面找startActivity方法。

	public class ContextWrapper extends Context {
	    Context mBase;
	    ······
	    @Override
	    public void startActivity(Intent intent) {
	        mBase.startActivity(intent);
	    }
	}		

調用了mBase的startActivity,但是mBase是Context類型的,Context是一個抽象類,所以我們尋找他的實現類。

他的實現類是ContextImpl 。尋找他的startActivity方法。

class ContextImpl extends Context {
	······
    final ActivityThread mMainThread;
   
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
}

可以很清楚的看到,他從mMainThread中得到了Instrumentation對象,並調用了他的execStartActivity方法。而這個mMainThread就是ActivityThread 。

所以說通過Application啓動活動時,只需要hook ActivityThread裏面的Instrumentation就可以了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章