腾讯插件框架Shadow解析之动态化和插件加载

腾讯插件框架Shadow解析之动态化和插件加载几个月前,腾讯开源了一款新的插件化框架Shadow。它的出现对于Android插件化的进程是十分重要的,因为随着google对系统API限制越来越严格,市面上大多数插件框架终将被被淘汰。而Shadow从新的角度解决了这一难题。 全动态插件框架:一次性实现完美的插件框架很难,但S…

几个月前,腾讯开源了一款新的插件化框架Shadow。它的出现对于Android插件化的进程是十分重要的,因为随着google对系统API限制越来越严格,市面上大多数插件框架终将被被淘汰。而Shadow从新的角度解决了这一难题。

与市面上其他插件框架相比,Shadow最明显的两个优点:

  • 全动态插件框架:一次性实现完美的插件框架很难,但Shadow将这些实现全部动态化起来,使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制。
  • 零反射无Hack实现插件技术:从理论上就已经确定无需对任何系统做兼容开发,更无任何隐藏API调用,和Google限制非公开SDK接口访问的策略完全不冲突。

框架全动态分析

在分析之前,我们先看一下官方对全动态插件框架的解释。

这是一个在我们长期接触插件框架技术后就意识到的问题。也是我们发现所有已知的插件框架没有解决的问题。我们将它称为全动态插件框架。全动态指的就是除了插件代码之外,插件框架本身的所有逻辑代码也都是动态的。实际上插件框架的代码我们是和插件打包在一起发布的。

这个特性有多重要呢?实际上它比无Hack、零反射实现还要重要!因为有了这个特性之后,就算是我们用了Hack的方案,需要兼容各种手机厂商的系统。我们也不需要等宿主App更新才能解决问题。

这个特性对于新研发的Shadow来说也尤为重要,因为新研发的东西肯定有很多不完善的地方。如果是要打包在宿主里发布的话,那必然要测试的非常小心,还要写大量过设计以满足未来的插件需求。但是,现在Shadow是不需要这样的,Shadow只实现业务眼前所需的功能就可以发布了。

上面这段话总结起来就是:全动态指的就是除了插件代码之外,插件框架本身的所有逻辑代码也都是动态的。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。

Manager:管理插件,包括插件的下载逻辑、入口逻辑、预加载逻辑等。反正就是一切还没有进入到Loader之前的所有事情。开源的部分主要包括插件的安装(apk存放指定路径、odex优化、解压so库等)。

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的动态化是如何实现的。直接上图。

腾讯插件框架Shadow解析之动态化和插件加载

这张图描述了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文件中。目录结构是这样子的。

腾讯插件框架Shadow解析之动态化和插件加载

除了以上文件外,我们发现还有一个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处理。

腾讯插件框架Shadow解析之动态化和插件加载

插件加载其实就是为插件四大组件准备运行环境。整个过程分为:

  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的普通类。

腾讯插件框架Shadow解析之动态化和插件加载

替换逻辑在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)
        }
    }
}

这里就直接看注释吧。

今天的文章腾讯插件框架Shadow解析之动态化和插件加载分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22041.html

(1)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注