几个月前,腾讯开源了一款新的插件化框架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的动态化是如何实现的。直接上图。
这张图描述了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()主要逻辑如下:
- 加载Manager插件。
- 通过反射实例化Manager内的PluginManagerImpl。
- 将处理逻辑委托给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主要完成了两件事情:
- 通过mUuidManager获取Runtime的安装信息。之前说过Runtime的安装是在Manager中完成的,而Manager运行在宿主进程中,因此需要Binder通信。
- 调用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中完成的。主要流程如下:
- 获取Loader的安装信息。
- 在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"
}
安装过程
我把整个安装过程分为已下几步:
- 解压安装包到指定目录下,配合config.json获取安装包信息,并将信息插入到数据库,以备以后使用。
- Loader、Runtiem安装,主要是oDex优化,更新数据库的oDex文件路径。
- 插件安装。提取so库和oDex优化,更新数据库的so文件路径和oDex文件路径。
整个过程比较简单,就不分析源码了。oDex优化是把apk交给DexClassLoader处理就好了。so库提取就是利用zip文件流,找到里面的so文件,复制到相应的目录。
加载插件
宿主在与插件交互时,例如启动插件Activity,要先加载插件。插件的加载是Manager中的PluginLoader通过Binder通信向Loader中的PluginLoaderBinder发送消息。PluginLoadersBinder收到消息后,委托给PluginLoaderImpl处理。
插件加载其实就是为插件四大组件准备运行环境。整个过程分为:
- 利用ClassLoader加载插件APK。
- 利用PackageManager获取插件信息。
- 创建存储插件信息的PluginPackageManager,提供获取插件ActivityInfo、PackageInfo等信息的功能。
- 创建查找插件资源的Resources。
- 为当前插件创建ShadowApplication,为插件Application的生命周期做准备。
- 缓存以上内容。
我们直接看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表示插件可以加载宿主类的白名单。具体的加载逻辑如下:
- 在白名单中,直接走双亲委派逻辑。直接交给宿主的PathClassLoader加载。
- 不在白名单中,先尝试自己加载。自己加载失败,交给宿主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)
}
}
}
这里就直接看注释吧。
今天的文章腾讯插件框架Shadow解析之动态化和插件加载分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22041.html