如何打開未註冊的Activity

1、背景

複習的時候看到這樣一個問題,這是插件化的一個知識點,實現一下加深印象

2、Activity啓動流程簡介

ActivityInstrumentationActivityThreaedAMSstartActivity()startActivityForResult()execStartActivity()startActivity()一系列調用啓動app(忽略)scheduleLaunchActivity()sendMessage(LAUNCH_ACTIVITY)handleLaunchActivity()performLaunchActivity()創建ActivitynewActivity()執行Activity生命週期onCreate()等ActivityInstrumentationActivityThreaedAMS

當調用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上測試效果:
在這裏插入圖片描述

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