几个月前,腾讯开源了一款新的插件化框架Shadow。它的出现对于Android插件化的进程是十分重要的,由于随着google对系统API限制愈来愈严格,市面上大多数插件框架终将被被淘汰。而Shadow重新的角度解决了这一难题。java
与市面上其余插件框架相比,Shadow最明显的两个优势:android
在分析以前,咱们先看一下官方对全动态插件框架的解释。数据库
这是一个在咱们长期接触插件框架技术后就意识到的问题。也是咱们发现全部已知的插件框架没有解决的问题。咱们将它称为全动态插件框架。全动态指的就是除了插件代码以外,插件框架自己的全部逻辑代码也都是动态的。实际上插件框架的代码咱们是和插件打包在一块儿发布的。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的主要交互逻辑。整个过程我作了简化。
在介绍Host时,我提到过整个框架的入口是DynamicPluginManager的enter()方法。咱们就从enter()开始分析Manager的动态化。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的加载过程是在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主要完成了两件事情:
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的加载过程与Runtime同样,也是在PluginProcessService中完成的。主要流程以下:
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"
}
复制代码
我把整个安装过程分为已下几步:
整个过程比较简单,就不分析源码了。oDex优化是把apk交给DexClassLoader处理就行了。so库提取就是利用zip文件流,找到里面的so文件,复制到相应的目录。
宿主在与插件交互时,例如启动插件Activity,要先加载插件。插件的加载是Manager中的PluginLoader经过Binder通讯向Loader中的PluginLoaderBinder发送消息。PluginLoadersBinder收到消息后,委托给PluginLoaderImpl处理。
插件加载其实就是为插件四大组件准备运行环境。整个过程分为:
咱们直接看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必须能够加载宿主和其它插件中的类,这样插件才能与宿主或其余插件交互。
在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表示插件能够加载宿主类的白名单。具体的加载逻辑以下:
@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
)
}
}
复制代码
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保存的路径传递过去。
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的处理方法。
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。
Shadow在处理插件Application时,采用了字节码技术,在编译期将继承的系统Application替换成了ShadowApplication。ShadowApplication就是一个继承了Context的普通类。
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"
)
)
复制代码
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)
}
}
}
复制代码
这里就直接看注释吧。