天天看點

Activity 插件化-Hook Activity

文章目錄

    • 1 Hook IActivityManager 方案實作
    • 2 Hook Instrumentation 方案實作

四大元件的插件化是插件化技術的核心知識點,而 Activity 插件化更是重中之重,Activity 插件化主要有 3 種實作方式,分别是反射實作、接口實作和 Hook 技術實作。反射實作會對性能有所影響,主流的插件化架構沒有采用此方式,關于接口實作可以閱讀dynamic-load-apk 的源碼,目前 Hook 技術實作是主流,是以本章主要介紹Hook 技術實作。

Hook 技術實作主要有兩種解決方案, 一種是通過 Hook IActivityManager 來實作,另一種是 Hook Instrumentation 實作。

1 Hook IActivityManager 方案實作

<application
        android:name=".MyApplication"
        
        ......
        
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity android:name=".StubActivity"/>

    </application>
           
import java.lang.reflect.Field;

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);
    }

}

           
import android.content.Intent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 替換IActivityManager的代理類
 */
public class IActivityManagerProxy implements InvocationHandler {

    private static final String TAG = "IActivityManagerProxy";

    private Object mActivityManager;


    public IActivityManagerProxy(Object activityManager) {
        this.mActivityManager = activityManager;
    }

    @Override
    public Object invoke(Object proxy, 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.hongx.unuse";
            subIntent.setClassName(packageName, packageName + ".StubActivity");//3
            subIntent.putExtra(HookHelper.TARGET_INTENT, intent);//4
            args[index] = subIntent;//5

        }

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

           
import android.os.Build;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;

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");
            defaultSingleton = FieldUtil.getField(activityManageClazz,null,"IActivityManagerSingleton");
        }else{
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManagerNative");
            defaultSingleton = FieldUtil.getField(activityManageClazz,null,"gDefault");
        }

        Class<?> singletonClazz = Class.forName("android.util.Singleton");

        Field mInstanceField = FieldUtil.getField(singletonClazz,"mInstance");//2

        Object iActivityManager = mInstanceField.get(defaultSingleton);//3

        Class<?> iAcitivityManagerClazz = Class.forName("android.app.IActivityManager");

        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                ,new Class<?>[]{iAcitivityManagerClazz}
                ,new IActivityManagerProxy(iActivityManager));

        mInstanceField.set(defaultSingleton,proxy);

    }

}

           
import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            HookHelper.hookAMS();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
           
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv_hook = findViewById(R.id.tv_hook);
        tv_hook.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,TargetActivity.class));
            }
        });


    }

           
import android.content.Intent;
import android.os.Handler;
import android.os.Message;

public class HCallback implements Handler.Callback {

    public  static final  int LAUNCH_ACIVITY = 100;

    Handler mHandler;

    public HCallback(Handler handler){
        mHandler = handler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if(msg.what == LAUNCH_ACIVITY){

            Object r = msg.obj;

            try {
                Intent intent = (Intent) FieldUtil.getField(r.getClass(),r,"intent");

                Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);

                intent.setComponent(target.getComponent());

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        mHandler.handleMessage(msg);

        return true;
    }
}

           

在HookHelper.java中添加hookHandler方法

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 (activityThreadClass,"mH" ) ; //12

        Handler mH = (Handler) mHField.get(currentActivityThread);//3

        FieldUtil.setField(Handler.class , mH,"mCallback",new HCallback(mH));


    }
           

2 Hook Instrumentation 方案實作

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;

import java.lang.reflect.Method;
import java.util.List;

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_INTENT, intent.getComponent().getClassName()); //1
            intent.setClassName(who, "com.hongx.plugin.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;
        }

    }

    public Activity newActivity(ClassLoader cl,String className,Intent intent) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT);
        if(!TextUtils.isEmpty(intentName)){
            return super.newActivity(cl,intentName,intent);
        }

        return super.newActivity(cl,className,intent);

    }


}

           
public static void hookInstrumentation(Context context) throws Exception{

        Class<?> contextImplClass = Class.forName("android.app.ContextImpl");

        Field mMainThreadField = FieldUtil.getField(contextImplClass,"mMainThread");

        Object activityThread = mMainThreadField.get(context);

        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

        Field mInstrumentation = FieldUtil.getField(activityThreadClass,"mInstrumentation");

        FieldUtil.setField(activityThreadClass
                ,activityThread
                ,"mInstrumentation"
                ,new InstrumentationProxy((Instrumentation)mInstrumentation.get(activityThread),context.getPackageManager()));


    }
           
public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context context) {
        super.attachBaseContext(context);
        try {
//            HookHelper.hookAMS();
//            HookHelper.hookHandler();
            HookHelper.hookInstrumentation(context);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }