关于Gradle的知识
Question¶
话题:关于Gradle的知识
1、如何理解Gradle?Grade在Android的构建过程中有什么作用?
2、实践如下问题。
问题:我们都知道,Android中时常需要发布渠道包,需要将渠道信息附加到apk中,然后在程序启动的时候读取渠道信息。仍然拿VirtualAPK来举例
动态指定一个渠道号(比如1001),那么构建的apk中,请在它的AndroidManifest.xml文件里面的application节点下面添加如下meta-data,请写一段Gradle脚本来自动完成:
<application android:allowBackup="false" android:supportsRtl="true">
<meta-data android:name=“channel" android:value=“1001" />
</application>
要求:当通过如下命令来构建渠道包的时候,将渠道号自动添加到apk的manifest中。
PS:禁止使用manifestPlaceholders
Answer¶
Tip
可以通过assembleRelease时的编译子任务获取所有编译的子任务
在project afterEvaluate以后,找到处理manifest的那个task,然后再它的doLast后面通过Groovy xml API来直接修改构建生成的xml文件即可,至于用不用Gradle插件,其实原理都一样,这里直接写在build.gradle里面了。
// app build.gradle
...
import groovy.xml.XmlUtil
project.afterEvaluate {
android.applicationVariants.each {
String variantName = it.name.capitalize()
def mergeManifestTask = project.tasks.getByName("process${variantName}Manifest")
mergeManifestTask.doLast { mm ->
def manifest = mm.manifestOutputFile
if (project.hasProperty("channel")) {
addChannel(manifest)
}
}
}
}
def addChannel(File manifest) {
def channelNo = project.property("channel")
def xml = new XmlParser().parse(manifest)
xml.application[0].appendNode("meta-data", ['android:name': 'channel', 'android:value': channelNo])
manifest.withPrintWriter("UTF-8") {
XmlUtil.serialize(xml, it)
}
}
Link¶
Gradle开发入门
Gradle从入门到实战 - Groovy基础
全面理解Gradle - 执行时序
全面理解Gradle - 定义Task
One More Thing¶
这里简单说一下AGP里面相关流程,一个plugin项目是如何定义以及如何使用的。我们以MethodTracer插件为例。
首先看 Plugin 入口的配置:
plugins/src/main/resources/META-INF/gradle-plugins/method-trace.properties
这里说明一下:
- 插件入口配置都应该在
/src/main/resources/META-INF/gradle-plugins/
这个特定目录下。 - 该目录下的文件名就是 apply plugin 时需要填写的插件名,且该目录下可以有多个文件来对应多个不同功能的插件。
- 例子说明该插件入口类是
xyz.yorek.plugin.mt.MethodTracePlugin
,且 apply plugin 时的名称应该是文件名method-trace
其次在看插件的实现类是如何注册transform的:
这里的实现方式一般有两种:
-
通过系统 API 来注册 transform。下面的例子就注册两个 transform,执行顺序为 BTransform > ATransform > TransformClassedWithDexBuilderForDebug
class MethodTracePlugin implements Plugin<Project> { @Override void apply(Project project) { ... def android = project.getExtensions().getByType(AppExtension.class) def b = new SimpleBTransform() def a = new SimpleATransform() android.registerTransform(b) android.registerTransform(a) } } public class SimpleATransform extends Transform { public SimpleATransform() { } @Override public String getName() { return "SimpleATransform"; } @Override public Set<QualifiedContent.ContentType> getInputTypes() { return TransformManager.CONTENT_CLASS; } @Override public Set<? super QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT; } @Override public boolean isIncremental() { return true; } @Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { super.transform(transformInvocation); Log.w(getName(), "SimpleATransform registered by android.registerTransform ....... "); } }
-
通过反射获取特定 Task 的 transfrom, 然后用我们的 transform wrap 原始的 transform。
class MethodTracePlugin implements Plugin<Project> { @Override void apply(Project project) { project.afterEvaluate { android.applicationVariants.all { variant -> MethodTraceTransform.inject(project, variant) } } } } class MethodTraceTransform extends ProxyTransformWrapper { static void inject(Project project, MethodTraceExtension configuration, def variant, List<Class<BaseClassVisitor>> visitorList) { String hackTransformTaskName = getTransformTaskName("", "", variant.name) String hackTransformTaskNameForWrapper = getTransformTaskName("", "Builder", variant.name) project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() { @Override void graphPopulated(TaskExecutionGraph taskGraph) { for (Task task : taskGraph.getAllTasks()) { if ((task.name.equalsIgnoreCase(hackTransformTaskName) || task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper)) && !(((TransformTask) task).getTransform() instanceof MethodTraceTransform)) { Field field = TransformTask.class.getDeclaredField("transform") field.setAccessible(true) field.set(task, new MethodTraceTransform(project, configuration, variant, task.transform, visitorList)) break } } } }) } static private String getTransformTaskName(String customDexTransformName, String wrapperSuffix, String buildTypeSuffix) { if (customDexTransformName != null && customDexTransformName.length() > 0) { return customDexTransformName + "For${buildTypeSuffix}" } return "transformClassesWithDex${wrapperSuffix}For${buildTypeSuffix}" } }
最后看 plugin 传到maven的地址
这一步决定了我们应用插件时 classpath 是如何填写的。
比如上面的示例 MathodTracer 插件通过 jitpack 进行的打包,打完包之后告诉我地址为 com.github.YorekLiu.MethodTracer:plugins:1.0.0
。
因此,在应用插件时 classpath 就是 com.github.YorekLiu.MethodTracer:plugins:1.0.0
。