Replugin 浅谈
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Replugin 浅谈相关的知识,希望对你有一定的参考价值。
参考技术A 一、replugin 是360 第2代插件库(上一代是droidplugin) ,兼容性、稳定性及侵入性方面都做了很大改良和优化,当然DroidPlugin 也是一个优秀的插件库(作者张勇也是技术大牛,在android插件化领域,很影响力)先说一下RePlugin的主要优势哈
1.十分灵活:主程序无须在Android市场升级,支持四大组件
2.及其稳定:hook点仅有一处(classLoader),崩溃率低于万分之一
3.兼容性比第1代库大幅度提示,几乎覆盖所有4.4及其以上Android系统
二、套话说到这,聊点核心技术哈
1.关于classLoader打破双亲委派 (classLoader加载class 先查看父classloader是否加载过,递归,如果直到根classLoader也没加载过,就自己加载咯),宿主用的classloader 实际是pathclassloader,插件用的是dexclassLoader 加载外部apk或dex。看了部分源码我的理解是宿主加载依然用系统的pathclassload 把宿主占坑的四大组件 加载进来,从这个PluginContainer对应集合里找出要调用的插件中的四大组件,因为插件的东西相对于宿主apk 而言是外部的,只能扩展dexclassLoader去加载插件里对应的四大组件。宿主和插件,是平行的关系。宿主给自己找了一些兄弟,这些兄弟帮自己加载不属于宿主中的四大组件。Hook点仅有一处(ClassLoader)。
2.、拿startActivity For example
启动插件Activity可以从RePlugin.startActivity开始,startActivity经历了Factory、PmLocalImpl,其实大部分启动的逻辑其实主要在PmInternalImpl中。关键在于loadPluginActivity。这里获取了插件对应的坑位,然后保存了目标Activity的信息,通过系统启动坑位。因为已经Hook住了ClassLoader,在loadClass时再加载出目标Activity,这样坑位中承载的,便是绕过系统打开的目标Activity(插件里的activity)。如下或许帮助理解:
RepluginActivity-->Factory.startActivity-->PmlLocalImp.startActivity-->PmlInternalImpl.startActivity
-->PmlLocalImpl.loadPluginActivity-->宿主系统startactivity--》pluginActiivty等--》回到宿主PmBase的PmlInternalImpl
Replugin 源码分析------replugin-host-gradle插件源码分析
前言
Replugin
是360开源的一个插件化框架,源码地址:https://github.com/Qihoo360/RePlugin,属于一种占坑类的插件化方案,整个框架分为四部分:宿主编译插件、插件工程编译插件、宿主依赖库、插件依赖库。今天分享的是宿主编译插件源码,该插件的作用是在编译时将四大组件的坑位预置进宿主程序中,并生成内置插件的配置信息。
一、源码结构
很显然标准的
gradle
插件工程,编码语言为groovy
,编译工具为gradle
,本文不讨论gradle
插件工程相关知识,本文只分析该插件在宿主编译时做了哪些工作。
二、源码分析
从replugin-host-gradle.properties
文件可以看到整个plugin
的入口在类com.qihoo360.replugin.gradle.host.Replugin
中,首先就从这个文件开始入手吧。
源码路径:com.qihoo360.replugin.gradle.host.Replugin.groovy
/**整个插件的调用入口方法*/
@Override
public void apply(Project project)
this.project = project
/* Extensions */
//创建extensions,然后再bulid.gradle文件里就可以,修改RepluginConfig里的默认配置了。
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)
/*app工程才会执行,Library项目直接过滤了*/
if (project.plugins.hasPlugin(AppPlugin))
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all variant ->
addShowPluginTask(variant)
//获取用户bulid.gradle文件里的配置
if (config == null)
config = project.extensions.getByName(AppConstant.USER_CONFIG)
checkUserConfig(config)
def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
def appID = generateBuildConfigTask.appPackageName
def newManifest = ComponentsGenerator.generateComponent(appID, config)
println "$TAG countTask=$config.countTask"
def variantData = variant.variantData
def scope = variantData.scope
//host generate task
def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
def generateHostConfigTask = project.task(generateHostConfigTaskName)
//生成RePluginHostConfig.java
generateHostConfigTask.doLast
FileCreators.createHostConfig(project, variant, config)
generateHostConfigTask.group = AppConstant.TASKS_GROUP
//depends on build config task
if (generateBuildConfigTask)
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
//json generate task
def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
//plugins-builtin.json
generateBuiltinJsonTask.doLast
FileCreators.createBuiltinJson(project, variant, config)
generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP
//depends on mergeAssets Task
def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant)
if (mergeAssetsTask)
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
variant.outputs.each output ->
VariantCompat.getProcessManifestTask(output).doLast
println "$AppConstant.TAG processManifest: $it.outputs.files"
it.outputs.files.each File file ->
updateManifest(file, newManifest)
上面代码出现多次使用了一个工具类VariantCompat
,该类是做gradle
版本兼容的,主要目的是从Android Gradle Task
里获取对应的task
,例如方法VariantCompat.getGenerateBuildConfigTask(variant)
就是获取generateBuildConfigTask
的,可以在Android Studio中打开gradle任务视图,根据不同的variant
即可获取到不同的任务。
生成坑位代码
/**
* 动态生成插件化框架中需要的组件
*
* @param applicationID 宿主的 applicationID
* @param config 用户配置
* @return String 插件化框架中需要的组件
*/
def newManifest = ComponentsGenerator.generateComponent(appID, config)
//生成结果
// <provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderPersist' android:authorities='com.qihoo360.replugin.sample.host.loader.p.main' android:exported='false' android:process=':GuardService' />
// ...
// <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS0' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.Translucent.NoTitleBar'/>
//...
// <service android:name='com.qihoo360.replugin.component.service.server.PluginPitServiceP2' android:process=':p2' android:exported='false' />
//...
该方法通过gradle
文件中的配置坑位个数来生成四大组件的预置坑位,生成代码很简单,就是for循环生成xml文件。有兴趣可以点进去看,源码路径:com.qihoo360.replugin.gradle.host.handlemanifest.ComponentsGenerator.groovy
生成内置插件json配置
源码位置:com.qihoo360.replugin.gradle.host.creator.impl.json.PluginBuiltinJsonCreator#getFileContent;
//查找插件文件并抽取信息,如果没有就直接返回null
File pluginDirFile = new File(fileDir?.getAbsolutePath() + File.separator + config.pluginDir)
//遍历assets/plugins下的所有插件。
new File(fileDir.getAbsolutePath() + File.separator + config.pluginDir)
.traverse(type: FileType.FILES, nameFilter: ~/.*\\$config.pluginFilePostfix/)
PluginInfoParser parser = null
try
//利用开源库解析插件APK的版本号等Meta信息。
parser = new PluginInfoParser(it.absoluteFile, config)
catch (Exception e)
if (config.enablePluginFileIllegalStopBuild)
throw new Exception(e)
if (null != parser)
pluginInfos << parser.pluginInfo
//插件为0个
if (pluginInfos.isEmpty())
return null
//构建插件们的json信息
def jsonOutput = new JsonOutput()
String pluginInfosJson = jsonOutput.toJson(pluginInfos)
//格式化打印插件们的json信息
return pluginInfosJson
从代码中看到,此处直接从assets里扫描并生成内置插件的json
信息。这里解析插件apk
文件用到了一个开源库net.dongliu:apk-parser:2.2.0
,https://github.com/hsiafan/apk-parser,有兴趣可以关注一下。
更新到Manifest文件中
直接上代码吧,没有任何难度,就是简单的替换,找到所有的AndroidManifest.xml
把生产的坑位append
上去。
def updateManifest(def file, def newManifest)
// 除了目录和AndroidManifest.xml之外,还可能会包含manifest-merger-debug-report.txt等不相干的文件,过滤它
if (file == null || !file.exists() || newManifest == null) return
if (file.isDirectory())
println "$AppConstant.TAG updateManifest: $file"
file.listFiles().each
updateManifest(it, newManifest)
else if (file.name.equalsIgnoreCase("AndroidManifest.xml"))
appendManifest(file, newManifest)
def appendManifest(def file, def content)
if (file == null || !file.exists()) return
println "$AppConstant.TAG appendManifest: $file"
def updatedContent = file.getText("UTF-8").replaceAll("</application>", content + "</application>")
file.write(updatedContent, 'UTF-8')
总结
看完源码后,Replugin
宿主的gradle
插件没有干什么高大上的活,都是一些配置生成的体力活。理论上来讲对编译性能影响也不大,耗时多得地方可能就在扫描内置插件的任务上了。但是Replugin
生成了非常多得坑位,接入之后,最终的AndroidManifest.xml
会多出200多个坑位,但这两百多个坑位是没有对应的类文件的,后续阅读宿主程序源码后再行分析。整体来看Replugin
在编译器对宿主的入侵是很小的。
以上是关于Replugin 浅谈的主要内容,如果未能解决你的问题,请参考以下文章