replugin源码解析之replugin-host-gradle(宿主的gradle插件)

前言

replugin-host-gradle 是 RePlugin 插件框架中的宿主gradle插件,主要用于在宿主应用的编译期常规构建任务流中,插入一些定制化的构建任务,以便实现自动化编译期修改宿主应用的目的。
RePlugin 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。

注:文中会提及两种插件,请阅读本文时注意提及插件的上下文情景,避免混淆概念:

  • replugin插件:即replugin插件化框架所指的插件,这个插件指android应用业务拆分出的独立模块,是android应用或模块。
  • gradle插件:即gradle构建所需的构建插件,是gradle应用或模块。

结构概览

结构概览 - 英文高清大图 —————— 结构概览 - 中文高清大图

replugin-host-gradle,针对宿主应用执行的构建任务:

  • 生成带 RePlugin 插件坑位的 AndroidManifest.xml(允许自定义数量)
  • 生成 RepluginHostConfig 类,方便插件框架读取并自定义其属性
  • 生成 plugins-builtin.json,json中含有插件应用的信息,包名,插件名,插件路径等。

replugin-host-gradle 插件的构建任务基于{productFlavors}{buildTypes}组合出多维构建任务,在android gradle 插件构建规则内执行构建任务,举个具体的例子:
在宿主中配置了 两个渠道{baidu} {xiaomi},两个编译类型{debug} {release}
共会生成四种编译组合:
{baidu}{debug} {xiaomi}{debug} {baidu}{release} {xiaomi}{release}
每种组合都会执行经由replugin-host-gradle 插件插入或修改到默认构建任务流中的gradle task为:
rpGenerate{productFlavors}{buildTypes}HostConfig - 生成RePluginHostConfig.java配置文件到buildConfig目录下
process{productFlavors}{buildTypes}Manifest - 拼装生成 AndroidManifest.xml(坑位组件+原xml中的组件)
rpGenerate{productFlavors}{buildTypes}BuiltinJson - 生成插件信息文件plugins-builtin.json到assets目录下

目录概览

 
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
    
\qihoo\RePlugin\replugin-host-gradle\src
└─main
├─groovy
└─com
└─qihoo360
└─replugin
└─gradle
└─host
AppConstant.groovy # 程序常量定义区
RePlugin.groovy # 针对宿主的特定构建任务创建及调度
├─creator
FileCreators.groovy # 组装生成器
IFileCreator.groovy # 文件生成器接口
└─impl
├─java
RePluginHostConfigCreator.groovy # RePluginHostConfig.java 生成器
└─json
PluginBuiltinJsonCreator.groovy # plugins-builtin.json 生成器
PluginInfo.groovy # 插件信息模型
PluginInfoParser.groovy # 从 manifest 的 xml 中抽取 PluginInfo信息
└─handlemanifest
ComponentsGenerator.groovy # 动态生成插件化框架中需要的组件
└─resources
└─META-INF
└─gradle-plugins
replugin-host-gradle.properties # 指定 gradle 插件实现类

replugin-host-gradle的基本用法

  • 添加 RePlugin Host Gradle 依赖
    在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依赖:
     
          
    1
    2
    3
    4
    5
    6
     
          
    buildscript {
    dependencies {
    classpath 'com.qihoo360.replugin:replugin-host-gradle:2.1.5'
    ...
    }
    }

在项目的app模块中的build.gradle应用插件:

 
    
1
 
    
apply plugin: 'replugin-host-gradle'

replugin-host-gradle的源码解析

我们在开始阅读源码前,要思考下,replugin-host-gradle是什么?
A:replugin-host-gradle是一个自定义的gradle插件。
这个清楚了,那就上车吧。

讲解replugin-host-gradle源码的同时,还会讲解一些开发自定义gradle插件的知识,希望能和您一起:知其然,亦知其所以然。

replugin-host-gradle.properties文件

 
    
1
 
    
implementation- class=com.qihoo360.replugin.gradle.host.Replugin

在开发自定义gradle插件时,都会先定义这么个文件。这里有 2 个知识点:

  • 文件中的implementation-class用来指定插件实现类。
  • 文件名用来指定插件名,即在宿主中使用插件时的apply plugin: 'replugin-host-gradle'中的replugin-host-gradle.

我们到插件实现类看看这个插件是如何工作的。

此 gradle 插件基于 groovy 开发,groovy 也是 JVM 系的编程语言,对于 java 系程序员来说,几乎可以闭着眼就开撸代码,不过 gradle 基于 Groovy,build 脚本使用 Groovy 编写,想写出 gradle style 的代码,还是可以去学学这门语言。

RePlugin.groovy文件

 
    
1
2
3
4
5
6
7
 
    
public class Replugin implements Plugin<Project> {
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
...
}
}

定义了一个类RePlugin,继承自gradle-api 库中的接口类 Plugin ,实现了apply接口方法,apply方法会在 build.gradle 中执行 apply plugin: ‘replugin-host-gradle’ 时被调用。

那我们分小节,循序渐进的看看 apply 方法的具体实现。

预生成AndroidManifest.xml中的组件坑位

 
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
    
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
this.project = project
/* Extensions */
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
addShowPluginTask(variant)
if (config == null) {
config = project.extensions.getByName(AppConstant.USER_CONFIG)
checkUserConfig(config)
}
def appID = variant.generateBuildConfig.appPackageName
println "${TAG} appID: ${appID}"
def newManifest = ComponentsGenerator.generateComponent(appID, config)
...
}
}
}
  • 首先向Plugin传递参数,通过project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig),将RepluginConfig类的常量配置信息赋值给AppConstant.USER_CONFIG,在接下来checkUserConfig(config)检查配置信息时有用到,主要检查配置信息数据类型是否正确。
  • 判断project中是否含有AppPlugin类型插件,即是否有’application’ projects类型的Gradle plugin。我们在宿主项目中是应用了该类型插件的:apply plugin: 'com.android.application'.
    如果希望判断是否有libraryPlugin,可以这样写:if (project.plugins.hasPlugin(LibraryPlugin)),it’s for ‘library’ projects.
  • 获取project中的AppExtension类型extension,即com.android.application projects的android extension.也就是在你的app模块的build.gradle中定义的闭包:
     
          
    1
    2
    3
     
          
    android {
    ...
    }

遍历android extension的Application variants 列表。这里说下,这可以说是 Hook Android gradle 插件的一种方式,因为通过遍历applicationVariants,你可以修改属性,名字,描述,输出文件名等,如果是Android library库,那么就将applicationVariants替换为libraryVariants。很多人可能在build.gradle中这样定义过闭包:

 
    
1
2
3
4
5
6
7
8
9
10
11
 
    
buildTypes {
release {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
def fileName = "xxx_${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}

其实这也是一种插件的创建方式,Hook Android gradle 插件动态修改variants属性值,修改打包输出的apk文件名。
创建自定义gradle插件,Gradle提供了多种方式:

  • 在build.gradle脚本中直接创建(上述代码即是)
  • 在独立Module中创建(replugin-host-gradle即是)
    • 继续看代码,addShowPluginTask(variant)这个方法执行了,但是方法内指定的task并未挂到android gradle task上,即task不会执行。这个task是方便调试时查看插件信息的,任务内容同接下来将讲到的生成 plugins-builtin.json 插件信息文件task一致。
    • checkUserConfig(config),获取到AppConstant.USER_CONFIG内一系列参数后,做数据类型正确性校验。
    • 关键代码来了,下面一行代码,搞定了宿主中AndroidManifest.xml中的组件坑位生成,注意,结合结构概览中的gradle Flow 看,这里只是生成组件坑位的xml代码,最终的xml文件是在后续的task中拼装出来的,稍后会讲到。
       
              
      1
       
              
      def newManifest = ComponentsGenerator.generateComponent(appID, config)

在代码面前,一切都是纸老虎。上车,进去看如何生成坑位的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def static generateComponent(def applicationID, def config) {
// 是否使用 AppCompat 库(涉及到默认主题)
if (config.useAppCompat) {
themeNTS = THEME_NTS_NOT_APP_COMPAT
} else {
themeNTS = THEME_NTS_NOT_USE_APP_COMPAT
}
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
/* UI 进程 */
xml.application {
/* 透明坑 */
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
...
}
...
/* 不透明坑 */
config.countNotTranslucentStandard.times{
}
...
}
// 删除 application 标签
def normalStr = writer.toString().replace("<application>", "").replace("</application>", "")
// println "${TAG} normalStr: ${normalStr}"
// 将单进程和多进程的组件相加
normalStr + generateMultiProcessComponent(applicationID, config)
}

一定要用一句话总结的话,那就是:基于 Groovy 的 MarkupBuilder api,根据 RepluginConfig 类中的配置,拼出组件坑位的xml 字符串。
就像搭积木一样,看一组就明白了。
生成坑位的代码:

 
    
1
2
3
4
5
6
7
8
 
    
config.countTranslucentStandard.times {
activity(
"${name}": "${applicationID}.${infix}N1NRTS${it}",
"${cfg}": "${cfgV}",
"${exp}": "${expV}",
"${ori}": "${oriV}",
"${theme}": "${themeTS}")
}

注:config.countTranslucentStandard.times 含义:根据config.countTranslucentStandard的值循环
生成的坑位:

 
    
1
2
3
4
5
6
 
    
<activity
android:theme= "@ref/0x01030010"
android:name= "com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS0"
android:exported= "false"
android:screenOrientation= "1"
android:configChanges= "0x4b0" />

一个字总结:replace.

Tips. 可以用Android Studio的Analyze APK…功能查看host gradle插件构建后宿主的AndroidManifest.xml,看看生成的坑位的样子就明白了。

##生成 RePluginHostConfig 配置文件

 
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 
    
@Override
public void apply(Project project) {
...
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
...
def variantData = variant.variantData
def scope = variantData.scope
//host generate task
def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
def generateHostConfigTask = project.task(generateHostConfigTaskName)
generateHostConfigTask.doLast {
FileCreators.createHostConfig(project, variant, config)
}
generateHostConfigTask.group = AppConstant.TASKS_GROUP
//depends on build config task
String generateBuildConfigTaskName = variant.getVariantData().getScope().getGenerateBuildConfigTask().name
def generateBuildConfigTask = project.tasks.getByName(generateBuildConfigTaskName)
if (generateBuildConfigTask) {
generateHostConfigTask.dependsOn generateBuildConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
...
}
}
}

继续回到 apply 方法,接下来该到生成 RePluginHostConfig 的时候了,即 注释中的host generate task

  • 首先生成了 HostConfig 的gradle task 名字,并调用project的task()方法创建此Task。
  • 指定了 generateHostConfigTask 的task任务:自动创建RePluginHostConfig.java至BuildConfig目录。
     
          
    1
    2
    3
     
          
    generateHostConfigTask.doLast {
    FileCreators.createHostConfig(project, variant, config)
    }

注:createHostConfig(...)方法内的实现,也是根据配置类 RepluginConfig中的配置信息拼装生成的java文件。

  • 设置generateHostConfigTask的执行依赖
     
          
    1
    2
    3
    4
    5
     
          
    //depends on build config task
    if (generateBuildConfigTask) {
    generateHostConfigTask.dependsOn generateBuildConfigTask
    generateBuildConfigTask.finalizedBy generateHostConfigTask
    }

因为此task中创建的RePluginHostConfig.java希望放置到编译输出目录..\replugin-sample\host\app\build\generated\source\buildConfig\{productFlavors}\{buildTypes}\...下,所以此task依赖于生成 BuildConfig.java 的task并设置为 BuildConfigTask 执行完后,就执行HostConfigTask。
关于gradle 的 task 相关知识,可以去gradle 官网或某搜索引擎查看学习,属于字典型知识点,需要时候查阅下。

##生成 plugins-builtin.json 插件信息文件

 
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 
    
@Override
public void apply(Project project) {
...
if (project.plugins.hasPlugin(AppPlugin)) {
def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
...
//json generate task
def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
generateBuiltinJsonTask.doLast {
FileCreators.createBuiltinJson(project, variant, config)
}
generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP
//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
}
...
}
}
}

继续回到 apply 方法,接下来该到生成 plugins-builtin.json 这个包含了插件信息的文件的时候了,即 注释中的json generate task

  • 首先生成个gradle task 名字,并调用project的task()方法创建此Task。
  • 指定了 generateBuiltinJsonTask 的task任务:扫描宿主\assets\plugins目录下的插件文件,并基于apk文件规则解析出插件信息,包名,版本号等,然后拼装成json文件。

     
          
    1
    2
    3
     
          
    generateBuiltinJsonTask.doLast {
    FileCreators.createBuiltinJson(project, variant, config)
    }
  • 设置 generateBuiltinJsonTask 的执行依赖

     
          
    1
    2
    3
    4
    5
     
          
    //depends on build config task
    if (mergeAssetsTask) {
    generateBuiltinJsonTask.dependsOn mergeAssetsTask
    mergeAssetsTask.finalizedBy generateBuiltinJsonTask
    }

因为此task中创建的 plugins-builtin.json 希望放置到编译输出目录...\replugin-sample\host\app\build\intermediates\assets\{productFlavors}\{buildTypes}\...下,所以此task依赖于merge assets文件 的task并设置为 mergeAssetsTask 执行完后,就执行BuiltinJsonTask。

##拼装 AndroidManifest.xml

 
    
1
2
3
4
5
 
    
output.processManifest.doLast {
def manifestPath = output.processManifest.outputFile.absolutePath
def updatedContent = new File(manifestPath).getText( "UTF-8").replaceAll( "</application>", newManifest + "</application>")
new File(manifestPath).write(updatedContent, 'UTF-8')
}
  • 将坑位 xml 字符串 与 原有xml 标签内的配置信息合二为一。

至此,replugin-host-gradle 插件的工作就全部结束了。

##End

replugin-host-gradle 插件是一个compile-time gradle plugin,基于赋予android gradle 构建任务流中新的构建任务及修改已有的构建任务,进而实现动态修改构建目标文件的为replugin宿主服务的gradle插件。


https://wangfuda.github.io/2017/07/15/replugin-host-gradle/

java精神(基于函数式组合子逻辑的javaparser框架) 一。 释名。 为什么叫精神? 如果你熟悉c++,那么你可能知道一个叫做”spirit”的parser库。它利用c++的模板元编程能力,使用c++语言本身提供了一个递归下降文法解析的框架。 我这里介绍的jparsec库,就是一个java里面的递归下降文法解析框架。 不过,它并非是spirit的java版本。 Jparsec的蓝本来自Haskell语言的parsec库。Parsec是一个基于monad的parser组合子库。 这个库的目的是要在java中提供一个类似parsec, spirit的库,这种组合子库并非c++的专利,java/c#也可以做到。这个库还将在java5.0上被改写,类型安全上它将也不再逊色于c++。 那么,为什么叫“函数式”呢?java是面向对象的嘛。 如果你使用过haskell, lisp等语言,这个函数式不用解释你也知道是怎么回事了。 如果你是一个老牌的c++/java程序员,那么这里还要稍微解释一下。当然如果您对这些虚头八脑的名词不感兴趣,那么,你尽可以跳过这一章,不知道什么是“函数式”,并不会影响你对这个库的理解的。 C++这几年随着gp的普及,“函数式”这个老孔乙己逐渐又被人从角落里面拽了出来。一个c++程序员所熟悉的“函数式”很可能是stl的for_each, transform,count_if这些函数。 怎么说呢,就象我不能否定str.length()这个调用属于OO一样,我也无法说for_each, transform不是函数式。 但是,“函数式”的精髓不在于此。 一般归纳起来,就像我们说OO是什么多态,封装,继承一样,“函数式”的特征被总结为: 1。无副作用。 2。高阶函数。 3。延迟计算 而最最有意义的(至少我认为如此),是基于高阶函数的函数组合能力。一些人把这叫做glue。 简短地说,什么让函数式编程如此强大?是用简单的函数组合出复杂函数的能力。 我可以想象,说到这里,你还是一头雾水。“什么是组合?1+1不是也把两个1组合成2了吗?new A(new B(), new C())不也是从B和C组合成A了?”
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值