插件化-Activity實現(xiàn)

1.插件化

關于插件化的原理和插件化框架之前的優(yōu)缺點對比,已經(jīng)有很多的文章,這里不再贅述。

2.Activity的啟動

APK在安裝的時候,PMS(PackageManagerService)解析Apk中的AndroidManifest.xml文件,根據(jù)Apk包路徑創(chuàng)建一個對應的資源管理器對象AssetManager,通過該對象來訪問Apk包中的資源信息,包括AndroidManifest.xml文件。
Activity的啟動由AMS管理,Launcher通知AMS啟動Activity,AMS通過PMS去查是否存在該Activity,若不存在,則停止了。若存在,再判斷是否已經(jīng)創(chuàng)建了進程。若未創(chuàng)建,則請求Zygote創(chuàng)建應用進程,然后啟動新的進程,在進程中創(chuàng)建ActivityThread對象,執(zhí)行其中的main函數(shù)方法,這里會創(chuàng)建啟動主線程,再通知AMS,傳入applicationThread以便通訊,AMS再通知應用啟動Application,創(chuàng)建啟動Activity。

3.插樁Activity

要啟動一個Activity必須在AndroidManifest.xml中注冊,如果我們要啟動一個新的Activity,則必須先在發(fā)布的APK內(nèi)占坑注冊。這樣,在啟動Activity的時候,可以通過AMS的校驗。然后將插件里的新Activity來代替之前占坑的Activity,去執(zhí)行代碼。
那么問題來了,hook技術應該hook什么,才能替換Activity。這考驗對Android系統(tǒng)工作原理的熟悉程度。
此外,為了保證hook的穩(wěn)定性,hook點一般找不容易變化的對象,比如單例、靜態(tài)變量。

3.1Activity的啟動分析

啟動Activity調(diào)用startActivity,最終會調(diào)用startActivityForResult。
在內(nèi)部調(diào)用Instrumentation的execStartActivity方法.
Instrumentation:用戶監(jiān)控應用程序與系統(tǒng)的交互。


Activity的startActivityForResult.png

Instrumentation的startActivityForResult在Android的7.0和8.0中代碼邏輯是不同的。先看Android7.0。

Android7.0

啟動Activity需先通過AMS的校驗,則需要獲取AMS對象。Android7.0通過
ActivityManagerNative的getDefault來獲取AMS的代理對象。


image.png

getDefault通過ServiceManager得到“activity”的Service引用,也就是IBinder類型的AMS的引用。


image.png

將其封裝成ActivityManagerProxy類型對象。它用來與AMS進行進程間通信。
image.png

getDefault借助Singleton類來實現(xiàn)單例,且它又是靜態(tài)的,因此hook IActivityManager。
Android8.0

ActivityManager的getService方法,該方法得到名為“activity的”Servcie的引用,也是IBinder類型,將其轉(zhuǎn)化成IActivityManager類型對象,這里使用了AIDL的方式,IActivityManager類是由AIDL工具在編譯時自動生成,與AMS通信,只要集成IActivityManager.Stub并實現(xiàn)對象的方法就可以。因此IActivityManager就是AMS在本地的代理。因此startActivityAsUser實際上調(diào)用的是AMS的方法。


Instrumentation的startActivityForResult.png

IActivityManager借助Singleton類實現(xiàn)單例,因此也選擇IActivityManager為hook點。


android8.0.png

4.Hook Activity的具體實現(xiàn)(Kotlin)

先寫一個插樁Activity(OldActivity),在AndroidManifest內(nèi)注冊。


image.png

再寫一個新的Activity(PluginActivity),不注冊。
最后寫一個測試類AcTestActivity,點擊啟動PluginActivity。


image.png

下面開始寫核心代碼
IActivityManager的代理類:
IActivityManagerProxy繼承自InvocationHandler

class IActivityManagerProxy :InvocationHandler {
    var mActivityManager:Any

    companion object {
        private val mMethod:String="startActivity"
        private val OLD_INTENT="OLD_ACTIVITY_INTENT"
    }

    constructor(activityManager: Any){
        this.mActivityManager=activityManager
    }
    @Throws(Throwable::class)
    override fun invoke(proxy: Any?, method: Method?, args: Array<Any>): Any ?{
        //判斷是否為"startActivity"方法
        if (mMethod == method?.name){
            val intent:Intent
            var index=0
            //獲取intent
            for (i in args.indices){
                if (args[i] is Intent){
                    index=i
                    break
                }
            }
            intent=args[index] as Intent
            //替換intent類要啟動的Activity為在AndroidManifest內(nèi)已經(jīng)注冊的Activity
            val newIntent=Intent()
            val packageName=BuildConfig.APPLICATION_ID
            //指定插樁Activity
            val  componentName=ComponentName(packageName,OldActivity::javaClass.name)
            newIntent.component = componentName
            //獲取到的intent先保存到新的intent內(nèi),后面會用到。
            newIntent.putExtra(OLD_INTENT,intent)
            //將新的intent賦值
            args[index]=newIntent
        }
        return method?.invoke(mActivityManager,*(args))
    }
}

利用反射,將系統(tǒng)原定的IActivityManager對象替換成我們自定義的代理類。

class HookUtil {

    companion object {
        @Throws(Throwable::class)
        fun hookAMS(){
            var  defaultSingleton:Any
            if (Build.VERSION.SDK_INT>=26){
                //android8.0以上,獲取hook的IActivityManagerSingleton字段
                val  activityManageClass=Class.forName("android.app.ActivityManager")
                val filed=activityManageClass.getDeclaredField("IActivityManagerSingleton")
                filed.isAccessible=true
                defaultSingleton= filed.get(null)
            }else{
                //android 7.0以下,獲取hook的gDefault字段
                val  activityManagerClass=Class.forName("android.app.ActivityManagerNative")
                val  filed=activityManagerClass.getDeclaredField("gDefault")
                filed.isAccessible=true
                defaultSingleton=filed.get(null)
            }
            //獲取Singleton中的即將被替換的字段的值--mInstance
            val  singletonClass=Class.forName("android.util.Singleton")
            val  singletonClassFiled=singletonClass.getDeclaredField("mInstance")
            singletonClassFiled.isAccessible=true
            //通過反射,得到即將被代理的對象(即mInstance的實例)--iActivityManager
            val  iActivityManager=singletonClassFiled.get(defaultSingleton)
            //創(chuàng)建代理類對象,該對象持有被代理的對象 :IActivityManagerProxy(iActivityManager)
            // 我們可以在IActivityManagerProxy做一些自定義的操作,其內(nèi)部持有原來的iActivityManager,
            val  iActivityManagerClass=Class.forName("android.app.IActivityManager")
            val interfaces= arrayOf(iActivityManagerClass)

            val  proxy=Proxy.newProxyInstance(Thread.currentThread().contextClassLoader, interfaces,
                    IActivityManagerProxy(iActivityManager)) as Any
            //用創(chuàng)建的代理類proxy對象來替換Singleton中的mInstance字段原來的對象
            singletonClassFiled.set(defaultSingleton,proxy)

        }
    }

}

自定義Application類(記得替換AndroidManifest內(nèi)的Application)

class ActivityApplication: Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        try {
            HookUtil.hookAMS()
        } catch (e:Exception){
            e.printStackTrace()
        }

    }
}

最后點擊AcTestActivity內(nèi)的按鈕,啟動一個未注冊的PluginActivity最終會啟動插樁OldActivity。那么就成功“騙過”AMS啟動了一個已注冊的Activity。

啟動新的Activity

上面講到啟動了已注冊的Activity,但整個是空的,我們真正想啟動的是新Activity而不是插樁Activity。那么這就要找到真正啟動Activity的位置,對其進行替換改造。我們知道通過AMS校驗后,AMS會通知應用可以啟動Activity。AMS支持ApplicationThread的IBinder類IApplicationThread對象,用來進程間通信。
ApplicationThread的scheduleLaunchActivity方法,會調(diào)用ActivityThread的sendMessage(H.LAUNCH_ACTIVITY, r)方法。H是Activity的內(nèi)部類,繼承自Handler,是應用程序進程中主線程的消息管理類。Activity的生命周期都是在主線程中執(zhí)行,所以這里會通過H來切換。


H handler處理Activity的生命周期.png

其中handleLaunchActivity方法最終會調(diào)到Activity的onCreate方法。
我們將H的handleMessage的msg進行替換,讓其攜帶的消息為新的Activity。(即從代理類IActivityManagerProxy保存的intent取出來。還記得之前我們將intent內(nèi)的Activity做了一次替換嗎?現(xiàn)在再換回來?。?/p>

====未完,待續(xù)~~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容