0.目录
RePlugin是360公司推出的开源的Android插件化和热更新框架, 广泛运用于360旗下的Android客户端项目.
其项目源码和中文说明可参看其GitHub页面: https://github.com/Qihoo360/RePlugin/blob/dev/README_CN.md
本系列文章将主要针对其当前版本2.1.0源码进行学习和分析, 目的是学习如何去实现一个插件化和热更新框架.
RePlugin源码中一共分为4个子项目:
-
replugin-host-gradle host项目的gradle插件
-
replugin-host-library host项目需要依赖的android library
-
replugin-plugin-gradle plugin项目的gradle插件
-
replugin-plugin-library plugin项目需要依赖的android library
接下来会分别对这4个子项目的代码进行阅读和学习.
1.Host Gradle
1. replugin-host-gradle项目
1.1 简要说明
本文主要对RePlugin的插件项目源码进行学习和分析. 目的是了解RePlugin在Android构建时做了什么事情来支持其插件化系统.
本文需要对以下知识有一个初步了解:
- gralde task相关知识: 参考gradle官方文档: https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
- gradle插件开发: 参考gradle官方文档: https://docs.gradle.org/current/userguide/userguide_single.html#custom_plugins
- android gradle插件: 参考android 官方文档: 配置构建 https://developer.android.com/studio/build/
- grooxy语言: 参考 https://www.ibm.com/developerworks/cn/education/java/j-groovy/j-groovy.html
1.2 插件简介
RePlugin-host-gradle项目为一个Gralde插件的项目, 主要是在Android构建的国中做如下事情:
- 收集内置插件列表信息, 生成 /assert/plugins-builtin.json 文件
- 读取RePlugin Gradle配置, 生成 RePluginHostConfig.java 文件
- 修改AndroidManifest文件, 主要是用来占坑
注意: 这个项目主要使用 groovy 语言来编写gradle插件的.
1.3 插件实现类
开发Gradle插件, 需要在 src/main/resources/META-INF/gradle-plugins/
目录下提供一个 properties 文件, 用于指定插件的实现类.
在本项目中的插件配置文件为: replugin-host-gradle.properties, 在其中定义了插件的实现类:
implementation-class=com.qihoo360.replugin.gradle.host.Replugin
每个Gradle插件的实现类, 必须实现 org.gradle.api.Plugin
接口即可, 该接口只有一个apply方法,
例如在本项目中的 Replugin 实现类如下:
import org.gradle.api.Plugin
public class Replugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// ...
}
}
1.4 Gradle自定义配置项
在接入RePlugin的过程里, 需要在项目的build.gradle文件中去配置RePlugin插件的一些配置项, 例如:
/** * 配置项均为可选配置,默认无需添加 * 更多可选配置项参见replugin-host-gradle的RepluginConfig类 * 可更改配置项参见 自动生成RePluginHostConfig.java */
repluginHostConfig {
/** * 是否使用 AppCompat 库 * 不需要个性化配置时,无需添加 */
useAppCompat = true
/** * 背景不透明的坑的数量 * 不需要个性化配置时,无需添加 */
countNotTranslucentStandard = 6
countNotTranslucentSingleTop = 2
countNotTranslucentSingleTask = 3
countNotTranslucentSingleInstance = 2
}
具体的配置项可以参考: https://github.com/Qihoo360/RePlugin/wiki/%E4%B8%BB%E7%A8%8B%E5%BA%8F%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
因此在Gralde插件的项目里, 首先需要添加这些配置项.
使用的方法是在 project.extensions 中创建一个 Configs 即可, 例如本项目中:
project.extensions.create("repluginHostConfig", RepluginConfig)
这里的RepluginConfig就是一个普通的类, 定义了所有可在gralde文件里配置的项, 例如:
class RepluginConfig {
/** 自定义进程的数量(除 UI 和 Persistent 进程) */
def countProcess = 3
/** 是否使用常驻进程? */
def persistentEnable = true
/** 常驻进程名称(也就是上面说的 Persistent 进程,开发者可自定义)*/
def persistentName = ':GuardService'
/** 背景不透明的坑的数量 */
def countNotTranslucentStandard = 6
def countNotTranslucentSingleTop = 2
def countNotTranslucentSingleTask = 3
def countNotTranslucentSingleInstance = 2
// ...
}
1.5 Android Gradle Plugin
这里需要额外的了解一下Android Gradle Plugin, 这个插件是所有Android应用主要使用的, 例如所有Android项目里的 build.gradle 里都会使用该plugin:
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
apply plugin: 'com.android.application'
而这里的 'com.android.application' 插件也通过实现 org.gradle.api.Plugin
来实现的. 它的具体的代码如下:
package com.android.build.gradle
/**
* Gradle plugin class for 'application' projects.
*/
class AppPlugin extends BasePlugin implements Plugin<Project> {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry)
}
@Override
protected Class<? extends BaseExtension> getExtensionClass() {
return AppExtension.class
}
@Override
void apply(Project project) {
super.apply(project)
}
// ...
}
在AppPlugin新建了一个project extension, 名字叫做 "android", 实现类是 AppExtension
, 它就是我们一般在Android项目的 build.gradle 里定义的:
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.qihoo360.replugin.sample.host"
minSdkVersion 9
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
因为Android Gradle的内容很多, 这里并不展开, 只需要看一个概念, 就是 buildTypes 的属性, 每个Android项目的gradle里都可以配置多种构建变种, 用于不同的场景, 打出不同的包, 例如默认会有两个构建变种:
- release
- debug
当我们默认使用Android Studio运行APP的时候, 默认使用的就是使用debug前面的debug包, 而发布的时候会使用开发者前面来打release包.
具体的相关信息可以参考Android官方文档: build-variants https://developer.android.com/studio/build/build-variants
我们这里需要先了解buildTypes的目的是为了知道, 在添加Gradle Task的时候, 也需要根据不同的变种, 新建不同的Task. 接下来就来看RePlugin具体新建了哪些Task.
1.6 Gradle Task
RePlugin插件一共添加了如下的几个自定义的Task:
- ShowPlugins
- rpShowPluginsDebug
- rpShowPluginsRelease
- GenerateBuiltinJson
- rpGenerateDebugBuiltinJson
- rpGenerateReleaseBuiltinJson
- GenerateHostConfig
- rpGenerateDebugHostConfig
- rpGenerateReleaseHostConfig
ShowPlugins Task主要是用来显示内置插件列表信息, 在执行的时候它会在Gradle Console里打印出当前host下所有内置插件的信息.
GenerateBuiltinJson Task 主要是在构建的时候, 生成内置插件列表的Json文件.
GenerateHostConfig Task 主要是读取RePlugin Gradle的配置项, 然后生成Config的Java文件.
而正如上一节提到的, RePlugin新建的三种Task, 也根据buildType不同, 而新建了不同的Task.
这里我们来看具体如何根据不同的buildType来新建任务:
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
this.project = project
if (project.plugins.hasPlugin(AppPlugin)) { // apply plugin: 'com.android.application'
def android = project.extensions.getByType(AppExtension) // android extensions
android.applicationVariants.all { variant ->
addShowPluginTask(variant) // 添加showPluginTask , 该Task用于整理并收集内置插件列表, 并将内置插件信息其保存到json文件中
}
}
}
这里首先检查了项目是否有使用 'com.android.application' 插件, 然后获取到 android
的 extension 实例, 遍历它的所有构建变种 android.applicationVariants
.
然后在每个构建变种里面都新建一个 ShowPlugin 的Task. 接下来我们来看具体是如何实现这个Task的.
1.7 ShowPlugins Task
创建一个Task的方法, 例如创建ShowPlugins Task的方法如下:
// 添加 【查看所有插件信息】 任务
def addShowPluginTask(def variant) {
def variantData = variant.variantData // android当前的构建变种信息
def scope = variantData.scope // 变种scope对象
def showPluginsTaskName = scope.getTaskName(AppConstant.TASK_SHOW_PLUGIN, "") // 创建变种的task名称
def showPluginsTask = project.task(showPluginsTaskName) // create new task with name
showPluginsTask.doLast {
// 在所有asset资源被merge以后, 遍历该目录, 找到所有插件的文件, 并读取其中的信息, 整理成插件列表
// 以JSON的形式写入到 /assert/plugins-builtin.json 文件中
IFileCreator creator = new PluginBuiltinJsonCreator(project, variant, config)
def dir = creator.getFileDir() // mergeAssetsTask.outputDir
if (!dir.exists()) {
println "${AppConstant.TAG} The ${dir.absolutePath} does not exist "
println "${AppConstant.TAG} pluginsInfo=null"
return
}
String fileContent = creator.getFileContent()
if (null == fileContent) {
return
}
// 将插件列表信息以Json格式保存到: /assert/plugins-builtin.json
new File(dir, creator.getFileName()).write(fileContent, 'UTF-8')
}
showPluginsTask.group = AppConstant.TASKS_GROUP
//get mergeAssetsTask name
String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
//get real gradle task
def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
//depend on mergeAssetsTask so that assets have been merged
if (mergeAssetsTask) {
showPluginsTask.dependsOn mergeAssetsTask
}
}
这里所有使用Android的构建变种scope来生成task name.
/**
* A scope containing data for a specific variant.
*/
public class VariantScopeImpl implements VariantScope {
@Override
@NonNull
public String getTaskName(@NonNull String prefix, @NonNull String suffix) {
return prefix + StringHelper.capitalize(getVariantConfiguration().getFullName()) + suffix;
}
}
也就是这里传入的task名为"rpShowPlugins", 根据构建变种最终的task name则为"rpShowPluginsDebug" , 和 "rpShowPluginsRelease" .
然后新建Task: project.task(taskName), 并设置task的几个属性: doLast, group, dependsOn. 这三个都是gradle task自带的属性, 其中doLast表示在task的action list都执行完了以后最终指定的动作, 在该函数中主要做的事情就是遍历 /assert/plugins 目录下的所有 jar 包文件(因为RePlugin要求所有内置的Plugin, 都必须将apk文件重命令为 .jar 包文件, 并放置到 /assert/plugins 目录下),
然后依次的去读取这个jar包文件(其实是apk文件)里的AndroidManifest.xml文件, 获取插件的基本信息, 然后将所有插件的信息, 写到一个 /assert/plugins-builtin.json 文件里(gen目录下, 而非src目录).
该文件里列出了所有插件的信息:
[
{
"high": null, /** 插件最高兼容版本 */
"frm": null, /** 框架版本号 */
"ver": 104, /** 插件版本号 */
"low": null, /** 插件最低兼容版本 */
"pkg": "com.qihoo360.replugin.sample.demo1", /** 插件包名 */
"path": "plugins/demo1.jar", /** 插件文件路径 */
"name": "demo1" /** 插件名 */
}
]
这些信息都是在插件项目的AndroidManifest里进行配置的, 配置的方式如下:
<meta-data
android:name="com.qihoo360.plugin.name"
android:value="[你的插件别名]" />
<meta-data
android:name="com.qihoo360.plugin.version.low"
android:value="[你的插件协议版本号]" />
<meta-data
android:name="com.qihoo360.plugin.version.high"
android:value="[你的插件协议版本号]" />
具体的配置项可参看RePlugin官方文档: https://github.com/Qihoo360/RePlugin/wiki/%E6%8F%92%E4%BB%B6%E7%9A%84%E4%BF%A1%E6%81%AF
这里使用了第三方库来解析apk文件里的manifest文件, 第三方库为: https://github.com/hsiafan/apk-parser
ApkFile apkFile = new ApkFile(pluginFile) // 开源的APK解析库: <https://github.com/hsiafan/apk-parser>
// 解析Manifest中的应用包名, 版本号, 和RePlugin的配置项
String manifestXmlStr = apkFile.getManifestXml()
ByteArrayInputStream inputStream = new ByteArrayInputStream(manifestXmlStr.getBytes("UTF-8"))
SAXParserFactory factory = SAXParserFactory.newInstance()
SAXParser parser = factory.newSAXParser()
parser.parse(inputStream, this)
把manifest当做XML做解析, 读取其中的meta-data即可.
ShowPlugins Task设置为依赖Android的mergeAssert Task, 因为需要在子项目的assert资源都合并了以后再开始遍历. 这里由兴趣的可以扩展的了解一下Android Gradle提供的一个Task各自是做什么, 并且以怎样的顺序执行, 来对Android项目的构建有一个全面的了解. 具体看参考 android gradle项目里的 AndroidTask 类. 项目源码在:
https://android.googlesource.com/platform/tools/build
1.8 GenerateBuiltinJson Task
这个Task和ShowPlugins Task的任务基本是一样的, 也是去获取插件列表数据, 然后将其写到json文件.
唯一的区别是, ShowPlugins Task的主要是为了去将结果打印到Gradle Console, 便于开发者去查看当前的插件信息.
而GenerateBuiltinJson是在项目构建的过程去, 去生成这个json文件, 并将其打包到apk里.
因此这个Task依赖的依然是Android的MergeAssert Task, 但是它将自己加到的MergeAssert Task任务之后(finalizedBy)去执行, 从而将其加入了整个构建的流程中:
//depends on mergeAssets Task
String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
1.9 GenerateHostConfig Task
这个任务的主要作用是去读取项目的build.gradle中关于RePlugin的配置项, 然后将其写到一个java文件里, 并将其编译到项目中去, 使得可以从java里(RePlugin library项目)去访问到这个配置项.
然而这个Task依赖的是 Android 的 GenerateBuildConfigTask , 并也是将其添加到这个任务之后进行执行, 使其进入构建流程.
Android的 GenerateBuildConfigTask 主要是为了生成 BuildConfig.java 文件, 里面包括了该构建变种的一些配置文件, 其中用得多的就是 BuildConfig.DEBUG 来判断当前是不是debug版本.
在这里的 GenerateHostConfigTask 任务里, 主要是使用 RePluginHostConfigCreator
类来负责生成 RePluginHostConfig.java
文件.
这里Java文件包括了所有RePlugin在Gradle里的配置项. 生成后的文件如下:
package com.qihoo360.replugin.gen;
/**
* 注意:此文件由插件化框架自动生成,请不要手动修改。
*/
public class RePluginHostConfig {
// 常驻进程名字
public static String PERSISTENT_NAME = "${config.persistentName}";
// 是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程。若为False,则会使用默认进程
public static boolean PERSISTENT_ENABLE = ${config.persistentEnable};
// 背景透明的坑的数量(每种 launchMode 不同)
public static int ACTIVITY_PIT_COUNT_TS_STANDARD = ${config.countTranslucentStandard};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TOP = ${config.countTranslucentSingleTop};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_TASK = ${config.countTranslucentSingleTask};
public static int ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE = ${
config.countTranslucentSingleInstance
};
// ...
}
这里所有的配置项都对应了最开始我们提到的 repluginHostConfig 配置项.
刚才提到这个任务会在 Android 的 GenerateBuildConfigTask 之后执行, 并且它会在在生成的 BuildConfig.java 文件的同目录去写一个它自己的 RePluginHostConfig.java 文件. 这个目录一般情况下都是应用的packagename的根目录, 及和R文件同样的目录. 这个目录通过如下方法访问:
File buildConfigGeneratedDir = this.variant.getVariantData().getScope().getBuildConfigSourceOutputDir()
1.10 修改Manifest文件
RePlugin Host插件的最重要的任务, 就是去修改Manifest文件. 把RePlugin框架需要的一些组件添加到Manifest里.
这里主要是通过 variant.outputs.proccessManifest 任务去修改Manifest文件的, 其方式就是在AndroidManifest.xml被processManifest任务处理完毕以后, 再打开AndroidManifest.xml文件, 并将新的内容直接写到 </application>
之前以实现注册自己的组件.
/* ------------------------------------------------------------- */
// 在 生成AndroidManifest.xml的task结束后, 将需要添加到manifest中的文件内容添加到 </application> 前面
// output
variant.outputs.each { output ->
output.processManifest.doLast {
output.processManifest.outputs.files.each { File file ->
def manifestFile = null;
//在gradle plugin 3.0.0之前,file是文件,且文件名为AndroidManifest.xml
//在gradle plugin 3.0.0之后,file是目录,且不包含AndroidManifest.xml,需要自己拼接
//除了目录和AndroidManifest.xml之外,还可能会包含manifest-merger-debug-report.txt等不相干的文件,过滤它
if ((file.name.equalsIgnoreCase("AndroidManifest.xml") && !file.isDirectory()) || file.isDirectory()) {
if (file.isDirectory()) {
//3.0.0之后,自己拼接AndroidManifest.xml
manifestFile = new File(file, "AndroidManifest.xml")
} else {
//3.0.0之前,直接使用
manifestFile = file
}
//检测文件是否存在
if (manifestFile != null && manifestFile.exists()) {
println "${AppConstant.TAG} handle manifest: ${manifestFile}"
def updatedContent = manifestFile.getText("UTF-8").replaceAll("</application>", newManifest + "</application>")
manifestFile.write(updatedContent, 'UTF-8')
}
}
}
}
}
这里的 variant.output 的实现类是 ApkVariantOutputImpl
, 其属性 processManifest 的实现类是 ManifestProcessorTask
, 该任务主要是用来处理 Manifest 文件. 其包括在该文件中添加 miniSdkVersion, targetSdkVersion等, 还包括merge多个AndroidManifest.xml为一个文件.
这里在Manifest里新增的内容, 主要是通过 ComponentsGenerator
来类实现的, 其中主要是在创建一个 <application> 里的xml内容.
/**
* 动态生成插件化框架中需要的组件
*
* @param applicationID 宿主的 applicationID
* @param config 用户配置
* @return String 插件化框架中需要的组件
*/
def static generateComponent(def applicationID, def config) {
// 是否使用 AppCompat 库(涉及到默认主题)
if (config.useAppCompat) {
themeNTS = THEME_NTS_USE_APP_COMPAT
} else {
themeNTS = THEME_NTS_NOT_USE_APP_COMPAT
}
def writer = new StringWriter()
def xml = new MarkupBuilder(writer) // gradle里专门用来创建xml的类
/* UI 进程 */
xml.application {
/* 需要编译期动态修改进程名的组件*/
String pluginMgrProcessName = config.persistentEnable ? config.persistentName : applicationID
// 常驻进程Provider
provider(
"${name}":"[com.qihoo360.replugin.component.process.ProcessPitProviderPersist](http://com.qihoo360.replugin.component.process.processpitproviderpersist/)",
"${authorities}":"${applicationID}.loader.p.main",
"${exp}":"false",
"${process}":"${pluginMgrProcessName}"
//...
这里体现了 groovy 语言的优势, 其生成xml可以直接在代码里写, 而不需要拼接字符串, 上面的代码转换成xml就基本相当于:
<application>
<provider
android:name="[com.qihoo360.replugin.component.process.ProcessPitProviderPersist](http://com.qihoo360.replugin.component.process.processpitproviderpersist/)"
android:authorities="your.packagename.loader.p.main"
android:exported="false"
android:process=":GuardService" />
</application>
在这里添加了三个provider:
- com.qihoo360.replugin.component.process.ProcessPitProviderPersist
- com.qihoo360.replugin.component.provider.PluginPitProviderPersist
- com.qihoo360.mobilesafe.svcmanager.ServiceProvider
还有一个Service:
和很多个不同属性的activity:
- 透明的N个Activity: 不同launchMode的坑: singleTop, singleTask, singleInstance
- 不透明的N个Activity: 不同launchMode的坑: singleTop, singleTask, singleInstance
- 不同TaskAffinity的activity的坑
- 不同process的activity的坑
以及多个自定义进程的各种provider, service, 和activity的坑.
这里的各种activity的占坑, 就是RePlugin支持activity热更新的关键. 在启动某个没有在Manifest注册的activity的时候, RePlugin会使用事先占好的坑来启动.
1.11 小结
到此为止, RePlugin Host Plugin项目的代码基本就读完了, 从中主要可以了解如何开发一个Gradle插件, 已经Android Gradle插件的一些基本概念.
对于RePlugin的插件系统有一点点了解, 并对RePlugin的Activity占坑有一个简单的了解.
后面需要去阅读 RePlugin Host Library 项目来了解具体的RePlugin实现方案.
NOTE ATTRIBUTES
Created Date: 2018-04-18 08:49:35
Last Evernote Update Date: 2018-05-17 09:12:06
网友评论