如何打开未注册的Activity

1、背景

复习的时候看到这样一个问题,这是插件化的一个知识点,实现一下加深印象

2、Activity启动流程简介

Activity Instrumentation ActivityThreaed AMS startActivity() startActivityForResult() execStartActivity() startActivity() 一系列调用 启动app(忽略) scheduleLaunchActivity() sendMessage(LAUNCH_ACTIVITY) handleLaunchActivity() performLaunchActivity() 创建Activity newActivity() 执行Activity生命周期onCreate()等 Activity Instrumentation ActivityThreaed AMS

当调用startActivity()时会通过Instrumentation调用AMS去启动Activity,AMS经过一系列的处理后通知ApplicationThread创建Activity,ApplicationThread又用Handler(即mH)把消息转到主线程,即sendMessage(LAUNCH_ACTIVITY);之后Instrumentation通过反射创建Activity,调用该Activity的生命周期(上述流程可能在各个android版本有差异)。

2、Hook入口

通过上述流程可知,最后启动Activity时会发送Handler的LAUNCH_ACTIVITY消息(api 27)

public final class ActivityThread {
    ...
    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;
                ...  
    
}

然后执行handleLaunchActivity()方法,把ActivityClientRecord作为参数传入

//ActivityThread.class
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    ...
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    ComponentName component = r.intent.getComponent();
    if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
    }
    ...
    Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //Instrumentation创建activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
    ...
}
//Instantiation.class
public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

经过一些列调用,最后执行到mInstrumentation.newActivity(),Instantiation反射创建Activity,className即

ActivityClientRecord.intent。因此在反射创建之前把这个intent替换成真正要启动的Activity,就能实现启动未注册的Activity了。通过上面的分析可知LAUNCH_ACTIVITY消息会携带的msg.obj就是ActivityClientRecord。而Hanlder在调用handleMessage()之前会先执行CallBack里的方法

/**
  * Handle system messages here.
  */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

因此插件化的做法是hook Handler,通过反射的方式设置mH里的mCallBack值达到启动未注册的Activity的目的。

3、实现

Handler的mCallBack是不能通过外部方法设置的,只能通过反射,因此要先拿到ActivityThread里的mH对象;ActivityThread无法直接获取,同样需要用反射。注意到ActivityThread中有个静态方法可以获取到实例。

//ActivityThread.class
public static ActivityThread currentActivityThread() {
    return sCurrentActivityThread;
}

因此实现如下:

//关键方法
override fun hook() {
        //拿到ActivityThread
        val activityThreadClass = Class.forName("android.app.ActivityThread")
        val currentActivityThread: Method = activityThreadClass.getDeclaredMethod("currentActivityThread")
        val activityThreadObj: Any? = currentActivityThread.invoke(null)

        //拿到mH
        val mHField: Field = activityThreadClass.getDeclaredField("mH")
        mHField.isAccessible = true
        val mH: Handler = mHField[activityThreadObj] as Handler

        //拿到Handle里的mCallBack
        val callBackField: Field = Handler::class.java.getDeclaredField("mCallback")
        callBackField.isAccessible = true
        //赋新值
        callBackField.set(mH, object : Handler.Callback {
            override
            fun handleMessage(msg: Message): Boolean {
                //LAUNCH_ACTIVITY = 100
                if (msg.what == 100) {
                    Log.e(TAG, "handleMessage: LAUNCH_ACTIVITY")
                    val record: Any = msg.obj
                    try {
                        Log.d(TAG, "hookActivityClientRecord: " + hookActivityClientRecord(record))
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
                //继续执行handleMessage
                return false
            }
        })
    }

    /**
     * 替换掉ActivityClientRecord中的Intent
     *
     * @param record
     * @return
     * @throws Exception
     */
    @SuppressLint("PrivateApi")
    private fun hookActivityClientRecord(record: Any): Boolean {
        //内部类反射需要用xxx.xxx$xxx
        val recordClass = Class.forName("android.app.ActivityThread\$ActivityClientRecord")
        val intentField = recordClass.getDeclaredField("intent")
        intentField.isAccessible = true
        //拿到intent
        val intent = intentField[record] as Intent
        //目标Activity,没有就返回
        val targetIntent = intent.getParcelableExtra<Intent>(Hooker.extra) ?: return false
        //修改intent
        intentField[record] = targetIntent
        return true
    }

上面的代码实现了Intent的替换,但是由于AMS还有一系列校验,因此仍然需要使用一个已注册的Activity作为代理,把真正要打开的Activity Intent当做参数传递过来用做替换。

4、测试效果

/**
 * Description: hook Activity
 * Author : pxq
 * Date : 2020/5/14 9:17 PM
 */
class App : Application() {

    override fun onCreate() {
        super.onCreate()
        Hooker.hook(Build.VERSION.SDK_INT)
    }

}

/**
 * Description: 代理Activity(已注册)
 * Author : pxq
 * Date : 2020/5/14 9:44 PM
 */
class ActivityStub : AppCompatActivity() {

    val TAG = ActivityStub::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "ActivityStub")
    }

}

/**
 * Description: 未注册的Activity
 * Author : pxq
 * Date : 2020/5/14 9:45 PM
 */
class TargetActivity : AppCompatActivity() {
	val TAG = TargetActivity::class.java.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(TAG, "TargetActivity 启动...")
    }

}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun open(view: View) {
        val intent = Intent(this, ActivityStub::class.java).apply {
            //要启动的Activity
            val bundle = Bundle()
            bundle.putParcelable(Hooker.extra, Intent(this@MainActivity, TargetActivity::class.java))
            putExtras(bundle)
        }

        startActivity(intent)
    }
}

Android 6.0上测试效果:
在这里插入图片描述
GitHub传送门:如何打开未注册的Activity

你可能感兴趣的:(插件化)