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

1.插件化

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

2.Activity的啟動(dòng)

APK在安裝的時(shí)候展融,PMS(PackageManagerService)解析Apk中的AndroidManifest.xml文件,根據(jù)Apk包路徑創(chuàng)建一個(gè)對(duì)應(yīng)的資源管理器對(duì)象AssetManager,通過(guò)該對(duì)象來(lái)訪(fǎng)問(wèn)Apk包中的資源信息豫柬,包括AndroidManifest.xml文件告希。
Activity的啟動(dòng)由AMS管理扑浸,Launcher通知AMS啟動(dòng)Activity,AMS通過(guò)PMS去查是否存在該Activity燕偶,若不存在喝噪,則停止了。若存在指么,再判斷是否已經(jīng)創(chuàng)建了進(jìn)程酝惧。若未創(chuàng)建,則請(qǐng)求Zygote創(chuàng)建應(yīng)用進(jìn)程伯诬,然后啟動(dòng)新的進(jìn)程晚唇,在進(jìn)程中創(chuàng)建ActivityThread對(duì)象,執(zhí)行其中的main函數(shù)方法盗似,這里會(huì)創(chuàng)建啟動(dòng)主線(xiàn)程缺亮,再通知AMS,傳入applicationThread以便通訊桥言,AMS再通知應(yīng)用啟動(dòng)Application萌踱,創(chuàng)建啟動(dòng)Activity。

3.插樁Activity

要啟動(dòng)一個(gè)Activity必須在AndroidManifest.xml中注冊(cè)号阿,如果我們要啟動(dòng)一個(gè)新的Activity并鸵,則必須先在發(fā)布的APK內(nèi)占坑注冊(cè)。這樣扔涧,在啟動(dòng)Activity的時(shí)候园担,可以通過(guò)AMS的校驗(yàn)。然后將插件里的新Activity來(lái)代替之前占坑的Activity枯夜,去執(zhí)行代碼弯汰。
那么問(wèn)題來(lái)了,hook技術(shù)應(yīng)該hook什么湖雹,才能替換Activity咏闪。這考驗(yàn)對(duì)Android系統(tǒng)工作原理的熟悉程度。
此外摔吏,為了保證hook的穩(wěn)定性鸽嫂,hook點(diǎn)一般找不容易變化的對(duì)象,比如單例征讲、靜態(tài)變量据某。

3.1Activity的啟動(dòng)分析

啟動(dòng)Activity調(diào)用startActivity,最終會(huì)調(diào)用startActivityForResult诗箍。
在內(nèi)部調(diào)用Instrumentation的execStartActivity方法.
Instrumentation:用戶(hù)監(jiān)控應(yīng)用程序與系統(tǒng)的交互癣籽。


Activity的startActivityForResult.png

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

Android7.0

啟動(dòng)Activity需先通過(guò)AMS的校驗(yàn)筷狼,則需要獲取AMS對(duì)象橱夭。Android7.0通過(guò)
ActivityManagerNative的getDefault來(lái)獲取AMS的代理對(duì)象。


image.png

getDefault通過(guò)ServiceManager得到“activity”的Service引用桑逝,也就是IBinder類(lèi)型的AMS的引用棘劣。


image.png

將其封裝成ActivityManagerProxy類(lèi)型對(duì)象。它用來(lái)與AMS進(jìn)行進(jìn)程間通信楞遏。
image.png

getDefault借助Singleton類(lèi)來(lái)實(shí)現(xiàn)單例茬暇,且它又是靜態(tài)的,因此hook IActivityManager寡喝。
Android8.0

ActivityManager的getService方法糙俗,該方法得到名為“activity的”Servcie的引用,也是IBinder類(lèi)型预鬓,將其轉(zhuǎn)化成IActivityManager類(lèi)型對(duì)象巧骚,這里使用了AIDL的方式,IActivityManager類(lèi)是由AIDL工具在編譯時(shí)自動(dòng)生成格二,與AMS通信劈彪,只要集成IActivityManager.Stub并實(shí)現(xiàn)對(duì)象的方法就可以。因此IActivityManager就是AMS在本地的代理顶猜。因此startActivityAsUser實(shí)際上調(diào)用的是AMS的方法沧奴。


Instrumentation的startActivityForResult.png

IActivityManager借助Singleton類(lèi)實(shí)現(xiàn)單例,因此也選擇IActivityManager為hook點(diǎn)长窄。


android8.0.png

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

先寫(xiě)一個(gè)插樁Activity(OldActivity)滔吠,在AndroidManifest內(nèi)注冊(cè)。


image.png

再寫(xiě)一個(gè)新的Activity(PluginActivity)挠日,不注冊(cè)疮绷。
最后寫(xiě)一個(gè)測(cè)試類(lèi)AcTestActivity,點(diǎn)擊啟動(dòng)PluginActivity嚣潜。


image.png

下面開(kāi)始寫(xiě)核心代碼
IActivityManager的代理類(lèi):
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類(lèi)要啟動(dòng)的Activity為在AndroidManifest內(nèi)已經(jīng)注冊(cè)的Activity
            val newIntent=Intent()
            val packageName=BuildConfig.APPLICATION_ID
            //指定插樁Activity
            val  componentName=ComponentName(packageName,OldActivity::javaClass.name)
            newIntent.component = componentName
            //獲取到的intent先保存到新的intent內(nèi)冬骚,后面會(huì)用到。
            newIntent.putExtra(OLD_INTENT,intent)
            //將新的intent賦值
            args[index]=newIntent
        }
        return method?.invoke(mActivityManager,*(args))
    }
}

利用反射郑原,將系統(tǒng)原定的IActivityManager對(duì)象替換成我們自定義的代理類(lèi)唉韭。

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
            //通過(guò)反射,得到即將被代理的對(duì)象(即mInstance的實(shí)例)--iActivityManager
            val  iActivityManager=singletonClassFiled.get(defaultSingleton)
            //創(chuàng)建代理類(lèi)對(duì)象女器,該對(duì)象持有被代理的對(duì)象 :IActivityManagerProxy(iActivityManager)
            // 我們可以在IActivityManagerProxy做一些自定義的操作酸役,其內(nèi)部持有原來(lá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)建的代理類(lèi)proxy對(duì)象來(lái)替換Singleton中的mInstance字段原來(lái)的對(duì)象
            singletonClassFiled.set(defaultSingleton,proxy)

        }
    }

}

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

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

    }
}

最后點(diǎn)擊AcTestActivity內(nèi)的按鈕,啟動(dòng)一個(gè)未注冊(cè)的PluginActivity最終會(huì)啟動(dòng)插樁OldActivity涣澡。那么就成功“騙過(guò)”AMS啟動(dòng)了一個(gè)已注冊(cè)的Activity贱呐。

啟動(dòng)新的Activity

上面講到啟動(dòng)了已注冊(cè)的Activity,但整個(gè)是空的入桂,我們真正想啟動(dòng)的是新Activity而不是插樁Activity奄薇。那么這就要找到真正啟動(dòng)Activity的位置,對(duì)其進(jìn)行替換改造抗愁。我們知道通過(guò)AMS校驗(yàn)后馁蒂,AMS會(huì)通知應(yīng)用可以啟動(dòng)Activity。AMS支持ApplicationThread的IBinder類(lèi)IApplicationThread對(duì)象蜘腌,用來(lái)進(jìn)程間通信沫屡。
ApplicationThread的scheduleLaunchActivity方法,會(huì)調(diào)用ActivityThread的sendMessage(H.LAUNCH_ACTIVITY, r)方法撮珠。H是Activity的內(nèi)部類(lèi)沮脖,繼承自Handler,是應(yīng)用程序進(jìn)程中主線(xiàn)程的消息管理類(lèi)芯急。Activity的生命周期都是在主線(xiàn)程中執(zhí)行勺届,所以這里會(huì)通過(guò)H來(lái)切換。


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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末澜掩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子杖挣,更是在濱河造成了極大的恐慌肩榕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惩妇,死亡現(xiàn)場(chǎng)離奇詭異株汉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歌殃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)乔妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人氓皱,你說(shuō)我怎么就攤上這事路召〔伲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵股淡,是天一觀(guān)的道長(zhǎng)身隐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)唯灵,這世上最難降的妖魔是什么贾铝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮埠帕,結(jié)果婚禮上垢揩,老公的妹妹穿的比我還像新娘。我一直安慰自己搞监,他們只是感情好水孩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著琐驴,像睡著了一般俘种。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绝淡,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天宙刘,我揣著相機(jī)與錄音,去河邊找鬼牢酵。 笑死悬包,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馍乙。 我是一名探鬼主播布近,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼丝格!你這毒婦竟也來(lái)了撑瞧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤显蝌,失蹤者是張志新(化名)和其女友劉穎预伺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體曼尊,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酬诀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骆撇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞒御。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖艾船,靈堂內(nèi)的尸體忽然破棺而出葵腹,到底是詐尸還是另有隱情高每,我是刑警寧澤屿岂,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布践宴,位于F島的核電站,受9級(jí)特大地震影響爷怀,放射性物質(zhì)發(fā)生泄漏阻肩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一运授、第九天 我趴在偏房一處隱蔽的房頂上張望烤惊。 院中可真熱鬧,春花似錦吁朦、人聲如沸柒室。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雄右。三九已至,卻和暖如春纺讲,著一層夾襖步出監(jiān)牢的瞬間擂仍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工熬甚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逢渔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓乡括,卻偏偏與公主長(zhǎng)得像肃廓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诲泌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容