淺讀騰訊插件化框架Shadow

背景

Android發(fā)展至今,出現(xiàn)過較多的插件化框架狞换。目前大部分都是使用hook系統(tǒng)的方式來做的。類似通過Hook Instrumentation 類將插件activity替換成占坑activity舟肉,在系統(tǒng)回調(diào)時修噪,通過hook ActivityThread 的 H再把占坑activity替換成插件activity的VirtualAPK和Atlas÷访模或者通過首先記錄占坑 Activity 和 插件Activity 的映射關(guān)系黄琼,然后在ClassLoader的 load 占坑 Activity 類的的時候,根據(jù)之前記錄的映射關(guān)系, 加載插件Activity的Replugin整慎。
騰訊在2019年開源了零反射無Hack的全動態(tài)插件化框架Shadow脏款。

項目的目錄結(jié)構(gòu)


├── projects
│  ├── sample // 示例代碼
│  │  ├── sample-constant // sample中使用的一些常量
│  │  ├── sample-host // 宿主
│  │  ├── sample-manager // PluginManager的實現(xiàn)
│  │  └── sample-plugin // 插件的實現(xiàn)(sample的loader、runtime裤园、app)
│  ├── sdk // 框架實現(xiàn)代碼
│  │  ├── core
│  │  │  ├── common
│  │  │  ├── gradle-plugin // gradle 插件
│  │  │  ├── load-parameters
│  │  │  ├── loader // 負(fù)責(zé)加載插件
│  │  │  ├── manager // 裝載插件撤师,管理插件
│  │  │  ├── runtime // 插件運行時需要,包括占位 Activity拧揽,占位 Provider 等等
│  │  │  ├── transform // Transform 實現(xiàn)剃盾,用于替換插件 Activity 父類等等
│  │  │  └── transform-kit
│  │  └── dynamic // 插件自身動態(tài)化實現(xiàn),包括一些接口的抽象

sample示例代碼

sample-host

1.完成PluginManager的apk和PluginZip文件的初始化(PluginHelper)淤袜。
2.實現(xiàn)DynamicPluginManager的初始化(Shadow)万俗。
3.實現(xiàn)PluginProcessService(PluginProcessPPS,通過Binder提供給其他插件調(diào)用)饮怯。
4.在manifest中注冊Service、容器Activity(sample-runtime)嚎研、容器ContentProvider等所需資源蓖墅。

sample-manager

1.實現(xiàn)PluginManager
2.WhiteList库倘,classLoader的白名單
3.實現(xiàn)ManagerFactoryImpl,在buildManager中论矾,完成PluginManager的初始化教翩,供宿主中DynamicPluginManager使用。
WhiteList贪壳、ManagerFactoryImpl類名與包名固定饱亿,在ManagerImplLoader和其父類ImplLoader中硬編碼匹配。

sample-plugin

sample-loader

1.實現(xiàn)PluginLoader
2.WhiteList闰靴,classLoader的白名單
3.實現(xiàn)CoreLoaderFactoryImpl彪笼,在build中,完成PluginLoader的初始化蚂且,供DynamicPluginLoader使用配猫,再通過Binder,供宿主中PluginProcessService和pluginManager使用杏死,此時也會將UuidManager提供給DynamicPluginLoader使用泵肄。
WhiteList、ManagerFactoryImpl類名與包名固定淑翼,在ManagerImplLoader和其父類ImplLoader中硬編碼匹配腐巢。

sample-runtime

在sample中實現(xiàn)了default、singleTask和singleInstance三類PluginContainerActivity玄括。

sample-plugin-app

包含了lib的module冯丙,加載lib的插件app,以及打包等功能惠豺。

實現(xiàn)

plugin打包

關(guān)鍵字:Plugin, Transform

image

初始化

關(guān)鍵字:ClassLoader, Binder

init.png

ApkClassLoader银还,如果目標(biāo)類符合構(gòu)造時傳入的包名,則從parent ClassLoader中查找,否則先從自己的dexPath中查找,如果找不到,則再從parent的parent ClassLoader中查找。(PluginManager, DynamicPluginLoader)

@Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        ……
        //如果在白名單內(nèi)
        if (isInterface) {
            return super.loadClass(className, resolve);
        } else {
            Class<?> clazz = findLoadedClass(className);

            if (clazz == null) {
                ClassNotFoundException suppressed = null;
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    suppressed = e;
                }

                if (clazz == null) {
                    try {
                        clazz = mGrandParent.loadClass(className);
                    } catch (ClassNotFoundException e) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            e.addSuppressed(suppressed);
                        }
                        throw e;
                    }
                }
            }

            return clazz;
        }
    }

RuntimeClassLoader(繼承自BaseDexClassLoader)洁墙,使其在原ClassLoader和Parent之間蛹疯。

private static void hackParentToRuntime(InstalledApk installedRuntimeApk, ClassLoader contextClassLoader) throws Exception {
        RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(installedRuntimeApk.apkFilePath, installedRuntimeApk.oDexPath,
                installedRuntimeApk.libraryPath, contextClassLoader.getParent());
        hackParentClassLoader(contextClassLoader, runtimeClassLoader);
    }


    /**
     * 修改ClassLoader的parent
     *
     * @param classLoader          需要修改的ClassLoader
     * @param newParentClassLoader classLoader的新的parent
     * @throws Exception 失敗時拋出
     */
    static void hackParentClassLoader(ClassLoader classLoader,
                                              ClassLoader newParentClassLoader) throws Exception {
        Field field = getParentField();
        if (field == null) {
            throw new RuntimeException("在ClassLoader.class中沒找到類型為ClassLoader的parent域");
        }
        field.setAccessible(true);
        field.set(classLoader, newParentClassLoader);
    }

PluginClassLoader

override fun loadClass(className: String, resolve: Boolean): Class<*> {
        if (specialClassLoader == null) {//specialClassLoader 為null 表示該classLoader依賴了其他的插件classLoader,需要遵循雙親委派
            return super.loadClass(className, resolve)
        } else if (className.subStringBeforeDot() == "com.tencent.shadow.core.runtime") {
            return loaderClassLoader.loadClass(className)
        } else if (className.inPackage(allHostWhiteList)
                || (Build.VERSION.SDK_INT < 28 && className.startsWith("org.apache.http"))) {//Android 9.0以下的系統(tǒng)里面帶有http包热监,走系統(tǒng)的不走本地的) {
            return super.loadClass(className, resolve)
        } else {
            var clazz: Class<*>? = findLoadedClass(className)

            if (clazz == null) {
                var suppressed: ClassNotFoundException? = null
                try {
                    clazz = findClass(className)!!
                } catch (e: ClassNotFoundException) {
                    suppressed = e
                }
                if (clazz == null) {
                    try {
                        clazz = specialClassLoader.loadClass(className)!!
                    } catch (e: ClassNotFoundException) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            e.addSuppressed(suppressed)
                        }
                        throw e
                    }

                }
            }

            return clazz
        }
    }

delegate

關(guān)鍵字:Delegate, Javapoet
以Activity為例捺弦,ActivityCodeGenerator通過square公司提供的Javapoet,生成Delegate與Delegator相關(guān)的java代碼孝扛。有兩個地方比較有趣列吼,分別是getCustomMethods和getActivityCallbackMethods。例如:

val startWithOnMethods = getActivityMethods(clazz)
                    .filter {
                        java.lang.reflect.Modifier.isPublic(it.modifiers) or
                                java.lang.reflect.Modifier.isProtected(it.modifiers)
                    }.filter {
                        it.name.startsWith("on")
                    }
            callbacks.addAll(startWithOnMethods)
image

TODO

由于依賴騰訊內(nèi)部后臺框架服務(wù)苦始,Shadow開源的代碼寞钥,目前是沒有包括版本檢查和插件下載。manager只實現(xiàn)了下載插件之后的安裝和升級邏輯陌选。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末理郑,一起剝皮案震驚了整個濱河市蹄溉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌您炉,老刑警劉巖柒爵,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赚爵,居然都是意外死亡棉胀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門冀膝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唁奢,“玉大人,你說我怎么就攤上這事畸写⊥郧疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵枯芬,是天一觀的道長论笔。 經(jīng)常有香客問我,道長千所,這世上最難降的妖魔是什么狂魔? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮淫痰,結(jié)果婚禮上最楷,老公的妹妹穿的比我還像新娘。我一直安慰自己待错,他們只是感情好籽孙,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著火俄,像睡著了一般犯建。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓜客,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天适瓦,我揣著相機與錄音,去河邊找鬼谱仪。 笑死玻熙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疯攒。 我是一名探鬼主播嗦随,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼敬尺!你這毒婦竟也來了枚尼?” 一聲冷哼從身側(cè)響起肌毅,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姑原,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呜舒,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡锭汛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袭蝗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唤殴。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖到腥,靈堂內(nèi)的尸體忽然破棺而出朵逝,到底是詐尸還是另有隱情,我是刑警寧澤乡范,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布配名,位于F島的核電站,受9級特大地震影響晋辆,放射性物質(zhì)發(fā)生泄漏渠脉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一瓶佳、第九天 我趴在偏房一處隱蔽的房頂上張望芋膘。 院中可真熱鬧,春花似錦霸饲、人聲如沸为朋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽习寸。三九已至,卻和暖如春器仗,著一層夾襖步出監(jiān)牢的瞬間融涣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工精钮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留威鹿,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓轨香,卻偏偏與公主長得像忽你,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子臂容,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

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