騰訊插件框架Shadow解析之動態化和插件加載

幾個月前,騰訊開源了一款新的插件化框架Shadow。它的出現對於Android插件化的進程是十分重要的,由於隨着google對系統API限制愈來愈嚴格,市面上大多數插件框架終將被被淘汰。而Shadow重新的角度解決了這一難題。java

與市面上其餘插件框架相比,Shadow最明顯的兩個優勢:android

  • 全動態插件框架:一次性實現完美的插件框架很難,但Shadow將這些實現所有動態化起來,使插件框架的代碼成爲了插件的一部分。插件的迭代再也不受宿主打包了舊版本插件框架所限制。
  • 零反射無Hack實現插件技術:從理論上就已經肯定無需對任何系統作兼容開發,更無任何隱藏API調用,和Google限制非公開SDK接口訪問的策略徹底不衝突。

框架全動態分析

在分析以前,咱們先看一下官方對全動態插件框架的解釋。數據庫

這是一個在咱們長期接觸插件框架技術後就意識到的問題。也是咱們發現全部已知的插件框架沒有解決的問題。咱們將它稱爲全動態插件框架。全動態指的就是除了插件代碼以外,插件框架自己的全部邏輯代碼也都是動態的。實際上插件框架的代碼咱們是和插件打包在一塊兒發佈的。json

這個特性有多重要呢?實際上它比無Hack、零反射實現還要重要!由於有了這個特性以後,就算是咱們用了Hack的方案,須要兼容各類手機廠商的系統。咱們也不須要等宿主App更新才能解決問題。緩存

這個特性對於新研發的Shadow來講也尤其重要,由於新研發的東西確定有不少不完善的地方。若是是要打包在宿主裏發佈的話,那必然要測試的很是當心,還要寫大量過設計以知足將來的插件需求。可是,如今Shadow是不須要這樣的,Shadow只實現業務眼前所需的功能就能夠發佈了。app

上面這段話總結起來就是:全動態指的就是除了插件代碼以外,插件框架自己的全部邏輯代碼也都是動態的。Shadow將框架分爲四部分:Host、Manager、Loader和Runtime。其中除Host外,Manager、Loader、Runtime都是動態的。框架

Host:Host打包在宿主中,它負責兩件事情:1. 爲Manger、Loader、Runtime運行提供接口。2.加載Manager、Loader、Runtime等插件。它有兩個重要的類DynamicPluginManager和PluginProcessService。DynamicPluginManager的enter()方法是程序的入口,該方法實現了加載Manager的邏輯。PluginProcessService加載Loader和Runtime。ide

Manager:管理插件,包括插件的下載邏輯、入口邏輯、預加載邏輯等。反正就是一切尚未進入到Loader以前的全部事情。開源的部分主要包括插件的安裝(apk存放指定路徑、odex優化、解壓so庫等)。post

Loader:Loader是框架的核心部分。主要負責加載插、管理四大組件的生命週期、Application的生命週期等功能。不少插件框架只有Loader這部分功能。通常來講Loader是宿主和插件之間的橋樑。好比在以Hook系統API方式實現的插件框架中,只有在宿主中執行Loader中代碼才能Hook一些系統類,從而能夠成功加載插件。或者在以代理方式實現的插件框架中,也必須經過Loader加載插件才能完成四大組件的轉調。測試

Runtime:Runtime這一部分主要是註冊在AndroidManifest.xml中的一些殼子類。Shadow做者對這部分的描述是被迫動態化。緣由是宿主對合入代碼的增量要求極其嚴格,而殼子類會引入大量的方法增量,所以被迫把這部分作成動態化。這也迫使Shadow引入了整套方案中惟一一處Hook系統的API。這咱們下面再細說。

關於Manager、Loader、Runtime更多的介紹能夠看原做者的博客Shadow的全動態設計原理解析

接下來咱們具體分析一下Manager、Loader、Runtime的動態化是如何實現的。直接上圖。

這張圖描述了Host、Manger、Loader、Runtime和Plugin的主要交互邏輯。整個過程我作了簡化。

  • DynamicPluginManager類位於Host模塊中,它是啓動插件的入口。它負責加載Manager插件,並實例化PluginManagerImpl,以後將處理邏輯交給PluginManagerImpl。
  • Manager負責Loader、Runtime、Plugin的安裝(apk存放指定路徑、odex優化、解壓so庫等)。
  • PluginProcessService類也位於Host模塊中。Shadow採用了跨進程的設計,將宿主和插件的進程區分開。插件進程的建立就是經過啓動PluginProceeServicew完成的。PluginManagerImpl與PluginProceeServicew交互是一個典型的Binder雙向通訊,Binder代理分別是宿主進程中的PpsController和插件進程中的UuidManager。宿主經過PpsController控制PluginProceeService加載Runtime、Loader,而插件經過UuidManager獲取Runtime、Loader、插件的安裝信息。更多關於Shadow多進程設計的介紹,能夠看做者的Shadow的跨進程設計與插件Service原理
  • PluginProcessService加載Loader,並實例化PluginLoaderBinder。PluginLoaderBinder是一個Binder對象,所以Manager能夠經過PpsController拿到PluginLoaderBinder的代理PluginLoader。以後Manager經過PluginLoader和PluginLoaderBinder通訊,控制插件的加載、四大組件的生命週期、Application的生命週期等。
  • PluginLoaderBinder將具體的處理邏輯交給PluginLoaderImpl。

Manager的動態化實現

在介紹Host時,我提到過整個框架的入口是DynamicPluginManager的enter()方法。咱們就從enter()開始分析Manager的動態化。enter()主要邏輯以下:

  1. 加載Manager插件。
  2. 經過反射實例化Manager內的PluginManagerImpl。
  3. 將處理邏輯委託給PluginMangerImpl的enter()。

咱們先看一下前兩步的處理邏輯

final class ManagerImplLoader extends ImplLoader {
    private static final String MANAGER_FACTORY_CLASS_NAME = "com.tencent.shadow.dynamic.impl.ManagerFactoryImpl";
    PluginManagerImpl load() {
        //1. 加載Manager插件。這個ClassLoader很神奇,沒徹底遵循雙親委派。只能經過parent加載白名單中的類。
        ApkClassLoader apkClassLoader = new ApkClassLoader(
                installedApk, //Manager插件APK。
                getClass().getClassLoader(),
                loadWhiteList(installedApk),//ClassLoader白名單,後面再西說
                1
        );
        //支持Resource相關和ClassLoader,容許Manager插件使用資源。
        Context pluginManagerContext = new ChangeApkContextWrapper(
                applicationContext,  //Applicaton
                installedApk.apkFilePath,
                apkClassLoader
        );
        try {
          	//2.反射獲得Manager中的類com.tencent.shadow.dynamic.impl.ManagerFactoryImpl的實例,調用buildManager生成PluginManagerImpl。
            ManagerFactory managerFactory = apkClassLoader.getInterface(
                    ManagerFactory.class,
                    MANAGER_FACTORY_CLASS_NAME
            );
            return managerFactory.buildManager(pluginManagerContext);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
複製代碼

隨後調用PluginManagerImpl的enter()方法。這樣看來DynamicPluginManager只是一個代理類,真正的處理類是Manager插件中的PluginManagerImpl。

Runtime的動態化實現

Runtime的加載過程是在PluginProcessService的loadRuntime中完成的。咱們看一下僞代碼。

public class PluginProcessService extends Service {
  //與宿主通訊的代理對象
  private UuidManager mUuidManager;
  void loadRuntime(String uuid) throws FailedException {
    	//...省略代碼
        //獲取Runtime的安裝信息
        InstalledApk installedRuntimeApk = mUuidManager.getRuntime(uuid);
        //加載Runtime.
        boolean loaded = DynamicRuntime.loadRuntime(installedRuntimeApk);
    	//...省略代碼
    }
}
複製代碼

loadRuntime主要完成了兩件事情:

  1. 經過mUuidManager獲取Runtime的安裝信息。以前說過Runtime的安裝是在Manager中完成的,而Manager運行在宿主進程中,所以須要Binder通訊。
  2. 調用DynamicRuntime.loadRuntime()加載Runtime。咱們接下來繼續分析。
public class DynamicRuntime{

    public static boolean loadRuntime(InstalledApk installedRuntimeApk) {
        ClassLoader contextClassLoader = DynamicRuntime.class.getClassLoader();
        RuntimeClassLoader runtimeClassLoader = getRuntimeClassLoader();
        if (runtimeClassLoader != null) {
            String apkPath = runtimeClassLoader.apkPath;
            if (TextUtils.equals(apkPath, installedRuntimeApk.apkFilePath)) {
                //已經加載相同版本的runtime了,不須要加載
                return false;
            } else {
                //版本不同,說明要更新runtime,先恢復正常的classLoader結構
                recoveryClassLoader();
            }
        }
        try {
             //正常處理,將runtime 掛到pathclassLoader之上
            hackParentToRuntime(installedRuntimeApk, contextClassLoader);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    private static void hackParentToRuntime(InstalledApk installedRuntimeApk, ClassLoader contextClassLoader) throws Exception {
      	//RuntimeClassLoader加載Runtime插件。
        RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(installedRuntimeApk.apkFilePath, installedRuntimeApk.oDexPath,
                installedRuntimeApk.libraryPath, contextClassLoader.getParent());
      	//這是全框架中惟一一處反射系統API的地方。但這是Java層提供的API,相對來講風險較小。
        Field field = getParentField();
        if (field == null) {
            throw new RuntimeException("在ClassLoader.class中沒找到類型爲ClassLoader的parent域");
        }
        field.setAccessible(true);
        field.set(contextClassLoader, runtimeClassLoader);
    }
}
複製代碼

loadRuntime用到了全框架中惟一一處反射調用系統的私有API,它的做用是將加載Runtime的RuntimeClassLoader掛載到系統的PathClassLoader之上。也就是將RuntimeClassLoader做爲PathClassLoader的父加載器。爲何這樣處理呢?由於Runtime主要是一些殼子類,例如殼子Activity。在系統啓動插件中的Activity時,實際上是啓動這些殼子Activity。這就要保證系統的PathClassLoader必須能找到殼子Activity。簡單的方式就是利用雙親委派模型,把PathClassLoader的父加載器設置成RuntimeClassLoader。

Loader的動態化實現

Loader的加載過程與Runtime同樣,也是在PluginProcessService中完成的。主要流程以下:

  1. 獲取Loader的安裝信息。
  2. 在LoaderImplLoader中加載Loader插件。
public class PluginProcessService extends Service {
    
    private PluginLoaderImpl mPluginLoader;
    private UuidManager mUuidManager;

    void loadPluginLoader(String uuid) throws FailedException {
        //獲取Loader的安裝信息
        InstalledApk installedApk = mUuidManager.getPluginLoader(uuid);
        //交給LoaderImplLoader.load()加載loader。
        PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());
        pluginLoader.setUuidManager(mUuidManager);
        mPluginLoader = pluginLoader;
    }

    IBinder getPluginLoader() {
        return mPluginLoader;
    }
}
複製代碼
public class PpsController {
    public IBinder getPluginLoader() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        IBinder _result;
        try {
            _data.writeInterfaceToken(PpsBinder.DESCRIPTOR);
            mRemote.transact(PpsBinder.TRANSACTION_getPluginLoader, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readStrongBinder();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}
複製代碼

LoaderImplLoader.load()中的邏輯與前面的ManagerImplLoader邏輯類似,我就不分析了。不過這裏再強調一下,LoaderImplLoader.load()返回的PluginLoaderImpl是一個Binder對象。咱們的Manager能夠經過PpsController.getPluginLoader()獲得其代理。

安裝插件

安裝包結構

因爲Loader和Runtime的動態化,在發佈插件時,咱們還要發佈Loader和Runtime。Shadow的作法是將插件和Loader、Runtime打包到一個zip文件中。目錄結構是這樣子的。

除了以上文件外,咱們發現還有一個config.json文件,這個文件是在編譯期shadow自動生成的。它是對整個安裝包的描述。

{
  "compact_version": [
    1,
    2,
    3
  ],
  "pluginLoader": { //Loader的描述
    "apkName": "sample-loader-debug.apk", //指定哪一個APK是Loader。
    "hash": "FF05A9CC0A80D9D0950B0EA9CB431B35" //文件的hash值,shadow根據這個值和已經安裝/加載的Loader插件對比,是否須要從新安裝/加載。
  },
  "plugins": [ //插件描述
    {
      "partKey": "sample-plugin-app",//
      "apkName": "sample-plugin-app-debug.apk", 
      "businessName": "sample-plugin-app",
      "hash": "FD6FF462B95540C1184A64370FCA090E"
    }
  ],
  "runtime": { //Runtime的描述
    "apkName": "sample-runtime-debug.apk",
    "hash": "AC2E39021DDFCBED5471B8739DDB3643"
  },
  "UUID": "E822E493-2E29-41BB-B1E6-28D58BEBB8AB",
  "version": 4,
  "UUID_NickName": "1.1.5"
}
複製代碼

安裝過程

我把整個安裝過程分爲已下幾步:

  1. 解壓安裝包到指定目錄下,配合config.json獲取安裝包信息,並將信息插入到數據庫,以備之後使用。
  2. Loader、Runtiem安裝,主要是oDex優化,更新數據庫的oDex文件路徑。
  3. 插件安裝。提取so庫和oDex優化,更新數據庫的so文件路徑和oDex文件路徑。

整個過程比較簡單,就不分析源碼了。oDex優化是把apk交給DexClassLoader處理就行了。so庫提取就是利用zip文件流,找到裏面的so文件,複製到相應的目錄。

加載插件

宿主在與插件交互時,例如啓動插件Activity,要先加載插件。插件的加載是Manager中的PluginLoader經過Binder通訊向Loader中的PluginLoaderBinder發送消息。PluginLoadersBinder收到消息後,委託給PluginLoaderImpl處理。

插件加載其實就是爲插件四大組件準備運行環境。整個過程分爲:

  1. 利用ClassLoader加載插件APK。
  2. 利用PackageManager獲取插件信息。
  3. 建立存儲插件信息的PluginPackageManager,提供獲取插件ActivityInfo、PackageInfo等信息的功能。
  4. 建立查找插件資源的Resources。
  5. 爲當前插件建立ShadowApplication,爲插件Application的生命週期作準備。
  6. 緩存以上內容。

咱們直接看Loader中的實現,具體的實現邏輯在LoadPluginBloc中。

object LoadPluginBloc {
    fun loadPlugin( executorService: ExecutorService, abi: String, commonPluginPackageManager: CommonPluginPackageManager, componentManager: ComponentManager, lock: ReentrantLock, pluginPartsMap: MutableMap<String, PluginParts>, hostAppContext: Context, installedApk: InstalledApk, loadParameters: LoadParameters, remoteViewCreatorProvider: ShadowRemoteViewCreatorProvider? ): Future<*> {
        if (installedApk.apkFilePath == null) {
            throw LoadPluginException("apkFilePath==null")
        } else {
             //利用ClassLoader加載APk。
            val buildClassLoader = executorService.submit(Callable {
                lock.withLock {                   
                    LoadApkBloc.loadPlugin(installedApk, loadParameters, pluginPartsMap)
                }
            })

            //利用packageManager獲取插件信息。
            val getPackageInfo = executorService.submit(Callable {
                val archiveFilePath = installedApk.apkFilePath
                val packageManager = hostAppContext.packageManager               
                val packageArchiveInfo = packageManager.getPackageArchiveInfo(
                        archiveFilePath,
                        PackageManager.GET_ACTIVITIES
                                or PackageManager.GET_META_DATA
                                or PackageManager.GET_SERVICES
                                or PackageManager.GET_PROVIDERS
                                or PackageManager.GET_SIGNATURES
                )
                        ?: throw NullPointerException("getPackageArchiveInfo return null.archiveFilePath==$archiveFilePath")
                packageArchiveInfo
            })

            //建立存儲插件信息的PluginPackageManager。
            val buildPackageManager = executorService.submit(Callable {
                val packageInfo = getPackageInfo.get()
                val pluginInfo = ParsePluginApkBloc.parse(packageInfo, loadParameters, hostAppContext)
                PluginPackageManager(commonPluginPackageManager, pluginInfo)
            })
              
            //建立查找插件資源的Resources。
            val buildResources = executorService.submit(Callable {
                val packageInfo = getPackageInfo.get()
                CreateResourceBloc.create(packageInfo, installedApk.apkFilePath, hostAppContext)
            })
              
            //爲當前插件建立Application。
            val buildApplication = executorService.submit(Callable {
                val pluginClassLoader = buildClassLoader.get()
                val pluginPackageManager = buildPackageManager.get()
                val resources = buildResources.get()
                val pluginInfo = pluginPackageManager.pluginInfo

                CreateApplicationBloc.createShadowApplication(
                        pluginClassLoader,
                        pluginInfo.applicationClassName,
                        pluginPackageManager,
                        resources,
                        hostAppContext,
                        componentManager,
                        remoteViewCreatorProvider
                )
            })
            //將以上內容保存在緩存中。
            val buildRunningPlugin = executorService.submit {
                if (File(installedApk.apkFilePath).exists().not()) {
                    throw LoadPluginException("插件文件不存在.pluginFile==" + installedApk.apkFilePath)
                }
                val pluginPackageManager = buildPackageManager.get()
                val pluginClassLoader = buildClassLoader.get()
                val resources = buildResources.get()
                val pluginInfo = pluginPackageManager.pluginInfo
                val shadowApplication = buildApplication.get()
                lock.withLock {
                    componentManager.addPluginApkInfo(pluginInfo)
                    pluginPartsMap[pluginInfo.partKey] = PluginParts(
                            shadowApplication,
                            pluginClassLoader,
                            resources,
                            pluginInfo.businessName
                    )
                    PluginPartInfoManager.addPluginInfo(pluginClassLoader, PluginPartInfo(shadowApplication, resources,
                            pluginClassLoader, pluginPackageManager))
                }
            }
            return buildRunningPlugin
        }
    }
}
複製代碼

插件ClassLoader的實現

插件中的ClassLoader必須能夠加載宿主和其它插件中的類,這樣插件才能與宿主或其餘插件交互。

加載宿主中的類

在Shadow中,插件只能加載白名單中配置了的宿主類。咱們看一下具體的實現方式。

class PluginClassLoader(
        private val dexPath: String,
        optimizedDirectory: File?,
        private val librarySearchPath: String?,
        parent: ClassLoader,
        private val specialClassLoader: ClassLoader?,
  			hostWhiteList: Array<String>?
) : BaseDexClassLoader(dexPath, optimizedDirectory, librarySearchPath, parent) {

}

複製代碼

咱們須要關注三個參數:parent、specialClassLoader、hostWhiteList。在只考慮宿主和插件交互的狀況下,specialClassLoader是宿主PathClassLoader的父加載器。parent是宿主PathClassLoader。hostWhiteList表示插件能夠加載宿主類的白名單。具體的加載邏輯以下:

  1. 在白名單中,直接走雙親委派邏輯。直接交給宿主的PathClassLoader加載。
  2. 不在白名單中,先嚐試本身加載。本身加載失敗,交給宿主PathClassLoader的父加載器加載。這個過程跳過了宿主PathClassLoader。
@Throws(ClassNotFoundException::class)
    override fun loadClass(className: String, resolve: Boolean): Class<*> {
        //在白名單中類直接走雙親委派
        if (specialClassLoader == null || className.startWith(allHostWhiteList))  {
            return super.loadClass(className, resolve)
        } else {
            var clazz: Class<*>? = findLoadedClass(className)
            if (clazz == null) {
                clazz = findClass(className)!!
                if (clazz == null) {
                    clazz = specialClassLoader.loadClass(className)!!
                }
            }
            return clazz
        }
    }
複製代碼

加載其它插件中的類。

在介紹安裝過程時,我說過shadow的安裝包中有一個config.json文件。該文件對插件的描述其實還有一個dependsOn字段,用來表示依賴的其餘插件的。shadow在加載插件時,會先判斷它所依賴的插件是否已經加載。若是依賴的插件已經所有加載,則把加載這些插件的PathClassLoader組裝到一個CombineClassLoader中。這個CombineClassLoader就是當前插件PathClassLoader的父加載器。若是有依賴的插件沒有加載,則拋出異常。所以這就要求咱們熟悉插件間的調用關係,在加載插件時,先加載其依賴插件。

@Throws(LoadApkException::class)
    fun loadPlugin(installedApk: InstalledApk, loadParameters: LoadParameters, pluginPartsMap: MutableMap<String, PluginParts>): PluginClassLoader {
        val apk = File(installedApk.apkFilePath)
        val odexDir = if (installedApk.oDexPath == null) null else File(installedApk.oDexPath)
        val dependsOn = loadParameters.dependsOn
        //Logger類必定打包在宿主中,所在的classLoader即爲加載宿主的classLoader
        val hostClassLoader: ClassLoader = Logger::class.java.classLoader!!
        val hostParentClassLoader = hostClassLoader.parent
        if (dependsOn == null || dependsOn.isEmpty()) {
            return PluginClassLoader(
                    apk.absolutePath,
                    odexDir,
                    installedApk.libraryPath,
                    hostClassLoader,
                    hostParentClassLoader,
                    loadParameters.hostWhiteList
            )
        } else if (dependsOn.size == 1) {
            val partKey = dependsOn[0]
            val pluginParts = pluginPartsMap[partKey]
            if (pluginParts == null) {
                throw LoadApkException("加載" + loadParameters.partKey + "時它的依賴" + partKey + "尚未加載")
            } else {
                return PluginClassLoader(
                        apk.absolutePath,
                        odexDir,
                        installedApk.libraryPath,
                        pluginParts.classLoader,
                        null,
                        loadParameters.hostWhiteList
                )
            }
        } else {
            val dependsOnClassLoaders = dependsOn.map {
                val pluginParts = pluginPartsMap[it]
                if (pluginParts == null) {
                    throw LoadApkException("加載" + loadParameters.partKey + "時它的依賴" + it + "尚未加載")
                } else {
                    pluginParts.classLoader
                }
            }.toTypedArray()
            val combineClassLoader = CombineClassLoader(dependsOnClassLoaders, hostParentClassLoader)
            return PluginClassLoader(
                    apk.absolutePath,
                    odexDir,
                    installedApk.libraryPath,
                    combineClassLoader,
                    null,
                    loadParameters.hostWhiteList
            )
        }
    }
複製代碼

PackageManager獲取插件信息

val getPackageInfo = executorService.submit(Callable {
                val archiveFilePath = installedApk.apkFilePath
                val packageManager = hostAppContext.packageManager               
                val packageArchiveInfo = packageManager.getPackageArchiveInfo(
                        archiveFilePath,
                        PackageManager.GET_ACTIVITIES
                                or PackageManager.GET_META_DATA
                                or PackageManager.GET_SERVICES
                                or PackageManager.GET_PROVIDERS
                                or PackageManager.GET_SIGNATURES
                )
                        ?: throw NullPointerException("getPackageArchiveInfo return null.archiveFilePath==$archiveFilePath")
                packageArchiveInfo
            })
複製代碼

這裏就是調用了PackageManager.getPackageArchiveInfo(),將插件APK保存的路徑傳遞過去。

生成PluginPackageManager

val buildPackageManager = executorService.submit(Callable {
     //獲取插件信息
     val packageInfo = getPackageInfo.get()
     //解析成咱們本身的PluginInfo。
     val pluginInfo = ParsePluginApkBloc.parse(packageInfo, loadParameters, hostAppContext)
     //構建PluginPackManager
     PluginPackageManager(commonPluginPackageManager, pluginInfo)
 })
複製代碼

整個過程很簡單,直接看PluginPackageManager。

class PluginPackageManager(val commonPluginPackageManager: CommonPluginPackageManager,
                           val pluginInfo: PluginInfo) : PackageManager() {
    override fun getApplicationInfo(packageName: String?, flags: Int): ApplicationInfo {
        val applicationInfo = ApplicationInfo()
        applicationInfo.metaData = pluginInfo.metaData
        applicationInfo.className = pluginInfo.applicationClassName
        return applicationInfo
    }

    override fun getPackageInfo(packageName: String?, flags: Int): PackageInfo? {
        if (pluginInfo.packageName == packageName) {
            val info = PackageInfo()
            info.versionCode = pluginInfo.versionCode
            info.versionName = pluginInfo.versionName
            info.signatures = pluginInfo.signatures
            return info;
        }
        return null;
    }

    override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo {
        val find = pluginInfo.mActivities.find {
            it.className == component.className
        }
        if (find == null) {
            return commonPluginPackageManager.getActivityInfo(component, flags)
        } else {
            return find.activityInfo
        }
    }
  
  	override fun getProviderInfo(component: ComponentName?, flags: Int): ProviderInfo {
        ImplementLater()
    }

    override fun getReceiverInfo(component: ComponentName?, flags: Int): ActivityInfo {
        ImplementLater()
    }
}
複製代碼

PluginPackageManager繼承自PackageManager,返回當前插件的相關信息。關於這部分請看做者的博客Shadow對PackageManager的處理方法

建立查找插件資源的Resources

object CreateResourceBloc {
    fun create(packageArchiveInfo: PackageInfo, archiveFilePath: String, hostAppContext: Context): Resources {
        val packageManager = hostAppContext.packageManager
        packageArchiveInfo.applicationInfo.publicSourceDir = archiveFilePath
        packageArchiveInfo.applicationInfo.sourceDir = archiveFilePath
        try {
            return packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo)
        } catch (e: PackageManager.NameNotFoundException) {
            throw RuntimeException(e)
        }
    }
}
複製代碼

將插件apk的路徑賦值給publicSourceDir和sourceDir,利用PackageManager.getResourcesForApplication()建立一個新的Resources。

ShadowApplication

字節碼替換

Shadow在處理插件Application時,採用了字節碼技術,在編譯期將繼承的系統Application替換成了ShadowApplication。ShadowApplication就是一個繼承了Context的普通類。

替換邏輯在ApplicationTransform中,經過下面代碼咱們能夠看到Shadow插件將android.app.Application替換成了com.tencent.shadow.core.runtime.ShadowApplication。

class ApplicationTransform : SimpleRenameTransform(
        mapOf(
                "android.app.Application"
                        to "com.tencent.shadow.core.runtime.ShadowApplication"
                ,
                "android.app.Application\$ActivityLifecycleCallbacks"
                        to "com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks"
        )
)
複製代碼

建立ShadowApplication

object CreateApplicationBloc {
    @Throws(CreateApplicationException::class)
    fun createShadowApplication( pluginClassLoader: PluginClassLoader, appClassName: String?, pluginPackageManager: PluginPackageManager, resources: Resources, hostAppContext: Context, componentManager: ComponentManager, remoteViewCreatorProvider: ShadowRemoteViewCreatorProvider? ): ShadowApplication {
        try {
            val shadowApplication : ShadowApplication;
            shadowApplication = if (appClassName != null) {//若是appClassName存在,表示插件聲明瞭Application,反射生成ShadowApplication。
                val appClass = pluginClassLoader.loadClass(appClassName)
                ShadowApplication::class.java.cast(appClass.newInstance())
            } else {
                object : ShadowApplication(){}//插件沒有聲明Application,直接new一個ShadowApplication。
            }
            val partKey = pluginPackageManager.pluginInfo.partKey
            shadowApplication.setPluginResources(resources) //添加Resources
            shadowApplication.setPluginClassLoader(pluginClassLoader) //添加ClassLoader
            shadowApplication.setPluginComponentLauncher(componentManager) //添加ComponentManager,管理四大組件用的。
            shadowApplication.setHostApplicationContextAsBase(hostAppContext) //添加宿主的Application。
            shadowApplication.setBroadcasts(componentManager.getBroadcastsByPartKey(partKey)) //添加BroadcastReceiver。
            shadowApplication.setLibrarySearchPath(pluginClassLoader.getLibrarySearchPath())
            shadowApplication.setDexPath(pluginClassLoader.getDexPath())//插件APK路徑。
            shadowApplication.setBusinessName(pluginPackageManager.pluginInfo.businessName)
            shadowApplication.setPluginPartKey(partKey)
            shadowApplication.remoteViewCreatorProvider = remoteViewCreatorProvider
            return shadowApplication
        } catch (e: Exception) {
            throw CreateApplicationException(e)
        }
    }
}
複製代碼

這裏就直接看註釋吧。

未完待續

  • ShadowApplicaton與ShadowContext詳解。
  • Activity具體實現。
  • Service具體實現。
  • ContentProvider具體實現。
  • BroadcastReceiver具體實現。
相關文章
相關標籤/搜索