关于android:Android-Gradle-学习笔记整理

45次阅读

共计 13428 个字符,预计需要花费 34 分钟才能阅读完成。

前言

Gradle 是将软件编译、测试、部署等步骤分割在一起自动化构建工具。

对于 Android 开发人员曾经理解 build.gradle 的 android{} 和 dependencies{},然而他的编译过程是什么样的?这个过程中能够干些什么事理解吗?

此文是学习 Gradle 时的学习笔记,让你重新认识 Gradle,让 Gradle 放慢并提效构建你的我的项目。此时分享给大家,与大家共勉

此笔记次要内容如下

  • Gradle 最根底的一个我的项目配置
  • Groovy 根底语法 并解释 apply plugin: ‘xxxx’ 和 dependencies{}
  • Gradle Project/Task 并自定义 Task 和 Plugin
  • 自定义一个重命名 APP 名的插件 流程
  • APT 技术 - Java AbstractProcessor
  • Android 字节码加强技术 – Transform(Android 中应用字节码加强技术)

文章内容略长,如果你曾经把握 Gradle 基础知识,能够间接通过目录查看你想看的内容,回顾或者学习都还不错。

初识 Gradle 我的项目构建配置

gralde 我的项目构造

如图所示,是一个比拟小的 gradle 配置, 这里次要说两局部

  1. 绿色局部:gralde 版本配置及 gralde 所须要的脚本,其中 gradlew 为 linux/mac 下的脚本,gradle.bat 为 windows 下所需的脚本
  2. 红色局部:settings.gradle 为根我的项目的我的项目配置,外层的 build.gradle 为根我的项目的配置,内层的 build.gradle 为子项目的配置

gradle 配置程序

gralde 的我的项目配置是先辨认 settings.gradle,而后在配置各个 build.gradle.
为了阐明构建执行程序,在上述最根底的 gradle 我的项目构造外面设置了对应的代码

// settings.gradle
println "settings.gradle start"
include ':app'
println "settings.gradle end"
//root build.gradle
println "project.root start"
buildscript {repositories {}
    dependencies {}}

allprojects {
}
println "project.root end"
//app build.gradle
println "project.app start"
project.afterEvaluate {println "project.app.afterEvaluate print"}
project.beforeEvaluate {println "project.app.beforeEvaluate print"}
println "project.app end"

如果是 mac/linux,执行./gradlew 失去如下后果:

settings.gradle start
settings.gradle end

> Configure project :
project.root start
project.root end

> Configure project :app
project.app start
project.app end
project.app.afterEvaluate print

Groovy 语法

上面讲一些对于 groovy 的语法,能够关上 Android Studio Tools-> Groovy Console 练习 Groovy 语法,如下

可选的类型定义, 能够省略语句结束符分号(;)

int vs = 1
def version = 'version1'

println(vs)
println(version)

括号也是可选的

println vs
println version

字符串定义

def s1 = 'aaa'
def s2 = "version is ${version}"
def s3 = ''' str
is
many
'''
println s1
println s2
println s3

汇合

def list = ['ant','maven']
list << "gradle"
list.add('test')
println list.size()
println list.toString()
//map
def years = ['key1':1000,"key2":2000]
println years.key1
println years.getClass()

输入后果

[ant, maven, gradle, test]
1000
class java.util.LinkedHashMap

闭包

groovy 语法中反对闭包语法,闭包简略的说就是代码块, 如下:

def v = {v -> println v}
static def testMethod(Closure closure){closure('闭包 test')
}
testMethod v

其中定义的 v 就为闭包,testMethod 为一个办法,传入参数为闭包,而后调用闭包.

解释 apply plugin: ‘xxxx’ 和 dependencies{}

筹备工作,看 gradle 的源码

咱们先把子我的项目的 build.gradle 改为如下模式

apply plugin: 'java-library'
repositories {mavenLocal()
}
dependencies {compile gradleApi()
}

这样,咱们就能够间接看 gradle 的源码了,在 External Libraries 里如下

解释

进入 build.gradle 点击 apply 会进入到 gradle 的源码, 能够看到

//PluginAware
 /**
     * Applies a plugin or script, using the given options provided as a map. Does nothing if the plugin has already been applied.
     * <p>
     * The given map is applied as a series of method calls to a newly created {@link ObjectConfigurationAction}.
     * That is, each key in the map is expected to be the name of a method {@link ObjectConfigurationAction} and the value to be compatible arguments to that method.
     *
     * <p>The following options are available:</p>
     *
     * <ul><li>{@code from}: A script to apply. Accepts any path supported by {@link org.gradle.api.Project#uri(Object)}.</li>
     *
     * <li>{@code plugin}: The id or implementation class of the plugin to apply.</li>
     *
     * <li>{@code to}: The target delegate object or objects. The default is this plugin aware object. Use this to configure objects other than this object.</li></ul>
     *
     * @param options the options to use to configure and {@link ObjectConfigurationAction} before“executing”it
     */
    void apply(Map<String, ?> options);

用 Groovy 语法很分明的解释,apply 其实就是一个办法, 前面传递的就是一个 map,其中 plugin 为 key.

那么 dependencies{} 也是一样

//Project
/**
     * <p>Configures the dependencies for this project.
     *
     * <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link
     * DependencyHandler} is passed to the closure as the closure's delegate.
     *
     * <h3>Examples:</h3>
     * See docs for {@link DependencyHandler}
     *
     * @param configureClosure the closure to use to configure the dependencies.
     */
    void dependencies(Closure configureClosure);

dependencies 是一个办法 前面传递的是一个闭包的参数.

问题:思考那么 android {} 也是一样的实现吗?前面解说

Gradle Project/Task

在后面章节中提到 gralde 初始化配置,是先解析并执行 setting.gradle,而后在解析执行 build.gradle,那么其实这些 build.gradle 就是 Project,外层的 build.gradle 是根 Project, 内层的为子 project,根 project 只能有一个,子 project 能够有多个.

咱们晓得了最根底的 gradle 配置,那么怎么来应用 Gradle 外面的一些货色来为咱们服务呢?

Plugin

后面提到 apply plugin:’xxxx’,这些 plugin 都是依照 gradle 标准来实现的,有 java 的有 Android 的,那么咱们来实现一个本人的 plugin.

把 build.gradle 改为如下代码

//app build.gradle
class LibPlugin implements Plugin<Project>{
    @Override
    void apply(Project target) {println 'this is lib plugin'}
}
apply plugin:LibPlugin

运行./gradlew 后果如下

> Configure project :app
this is lib plugin

Plugin 之 Extension

咱们在自定义的 Plugin 中要获取 Project 的配置,能够通过 Project 去获取一些根本配置信息,那咱们要自定义的一些属性怎么去配置获取呢,这时就须要创立 Extension 了,把上述代码改为如下模式。

//app build.gradle
class LibExtension{
    String version
    String message
}
class LibPlugin implements Plugin<Project>{
    @Override
    void apply(Project target) {
        println 'this is lib plugin'
        // 创立 Extension 
        target.extensions.create('libConfig',LibExtension)
        // 创立一个 task
        target.tasks.create('libTask',{
           doLast{
               LibExtension config = project.libConfig
               println config.version
               println config.message
           }
        })
    }
}
apply plugin:LibPlugin
// 配置
libConfig {
    version = '1.0'
    message = 'lib message'
}

配置实现后,执行./gradlew libTask 失去如下后果

> Configure project :app
this is lib plugin
> Task :lib:libTask
1.0
lib message

看完上述代码,咱们就晓得 android {} 其实他就是一个 Extension,他是由 plugin ‘com.android.application’ 或者 ’com.android.library’ 创立。

Task

上述代码中,创立了一个名字为 libTask 的 task,gradle 中创立 task 的形式由很多中, 具体的创立接口在 TaskContainer 类中

//TaskContainer
Task create(Map<String, ?> options) throws InvalidUserDataException;
Task create(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
Task create(String name, Closure configureClosure) throws InvalidUserDataException;
Task create(String name) throws InvalidUserDataException;
<T extends Task> T create(String name, Class<T> type) throws InvalidUserDataException;
<T extends Task> T create(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException;

Project 不能够执行跑起来,那么咱们就要定义一些 task 来实现咱们的编译,运行,打包等。com.android.application 插件 为咱们定义了打包 task 如 assemble,咱们方才定义的插件为咱们增加了一个 libTask 用于输入。

Task API

咱们看到创立的 task 外面能够间接调用 doLast API,那是因为 Task 类中有 doLast API, 能够查看对应的代码看到对应的 API

Gradle 的一些 Task

gradle 为咱们定义了一些常见的 task,如 clean,copy 等,这些 task 能够间接应用 name 创立,如下:

task clean(type: Delete) {delete rootProject.buildDir}

依赖 task

咱们晓得 Android 打包时,会应用 assemble 相干的 task,然而仅仅他是不能间接打包的,他会依赖其余的一些 task. 那么怎么创立一个依赖的 Task 呢? 代码如下

task A{println "A task"}
task B({println 'B task'},dependsOn: A)

执行./graldew B 输入

A task
B task

自定义一个重命名 APP 名字的插件

通过上述的一些入门解说,大略晓得了 gradle 是怎么构建的,那当初来自定义一个安卓打包过程中,重命名 APP 名字的一个插件。

上述在 build.gradle 间接编写 Plugin 是 OK 的,那么为了复用性更高一些,那咱们怎么把这个抽出去呢?

如下

其中 build.gradle 为

apply plugin: 'groovy'
apply plugin: 'maven'
repositories {mavenLocal()
    jcenter()}

dependencies {compile gradleApi()
}

def versionName = "0.0.1"
group "com.ding.demo"
version versionName
uploadArchives{ // 以后我的项目能够公布到本地文件夹中
    repositories {
        mavenDeployer {repository(url: uri('../repo')) // 定义本地 maven 仓库的地址
        }
    }
}

apkname.properties 为

implementation-class=com.ding.demo.ApkChangeNamePlugin

ApkChangeNamePlugin

package com.ding.demo

import org.gradle.api.Project
import org.gradle.api.Plugin

class ApkChangeNamePlugin implements Plugin<Project>{

    static  class ChangeAppNameConfig{
        String prefixName
        String notConfig
    }

    static def buildTime() {return new Date().format("yyyy_MM_dd_HH_mm_ss", TimeZone.getTimeZone("GMT+8"))
    }

    @Override
    void apply(Project project) {if(!project.android){throw new IllegalStateException('Must apply \'com.android.application\'or \'com.android.library\'first!');
        }
        project.getExtensions().create("nameConfig",ChangeAppNameConfig)
        ChangeAppNameConfig config
        project.afterEvaluate {config = project.nameConfig}
        project.android.applicationVariants.all{
            variant ->
                variant.outputs.all {
                    output ->
                        if (output.outputFile != null && output.outputFile.name.endsWith('.apk')
                                && !output.outputFile.name.contains(config.notConfig)) {
                            def appName = config.prefixName
                            def time = buildTime()
                            String name = output.baseName
                            name = name.replaceAll("-", "_")
                            outputFileName = "${appName}-${variant.versionCode}-${name}-${time}.apk"
                        }
                }
        }
    }
}

定义实现后,执行./gradlew uploadArchives 会在本目录生成对应对应的插件

利用插件 在根 build.gralde 配置

buildscript {
    repositories {maven {url uri('./repo/')}
        google()
        jcenter()}
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1'
        classpath 'com.ding.demo:apkname:0.0.1'
    }
}

在 app.gralde 设置

apply plugin: 'apkname'
nameConfig{
    prefixName = 'demo'
    notConfig = 'debug'
}

Gradle doc 官网

Gradle 的根底 API 差不多就介绍完了。

官网地址:docs.gradle.org/current/use…
能够去查看对应的 API, 也能够间接通过源码的形式查看

然而笔记还没完,学习了 Gradle 的根底,咱们要让其为咱们服务。上面介绍几个理论利用.

APT 技术

APT 全称 Annotation Processing Tool,编译期解析注解,生成代码的一种技术。罕用一些 IOC 框架的实现原理都是它,驰名的 ButterKnife,Dagger2 就是用此技术实现的,SpringBoot 中一些注入也是应用他进行注入的.

在介绍 APT 之前,先介绍一下 SPI(Service Provider Interface)它通过在 ClassPath 门路下的 META-INF/** 文件夹查找文件,主动加载文件里所定义的类。下面自定义的 ApkNamePlugin 就是应用这种机制实现的,如下.

SPI 技术也有人用在了组件化的过程中进行解耦合。

要实现一个 APT 也是须要这种技术实现,然而谷歌曾经把这个应用 APT 技术从新定义了一个,定义了一个 auto-service,能够简化实现, 上面就实现一个简略 Utils 的文档生成工具。

Utils 文档生成插件

咱们晓得,我的项目中的 Utils 可能会很多,每当新人入职或者老员工也不能实现晓得都有那些 Utils 了,可能会反复退出一些 Utils,比方获取屏幕的密度,框高有很多 Utils. 咱们通过一个小插件来生成一个文档,当用 Utils 能够看一眼文档就很高深莫测了.

新建一个名为 DocAnnotation 的 Java Libary

定义一个注解

@Retention(RetentionPolicy.CLASS)
public @interface GDoc {String name() default "";

   String author() default "";

   String time() default "";}

新建一个名为 DocComplie 的 Java Libary 先

而后引入谷歌的 auto-service,引入 DocAnnotation

apply plugin: 'java-library'
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation 'com.alibaba:fastjson:1.2.34'
    implementation project(':DocAnnotation')
}

定义一个 Entity 类

public class Entity {

    public String author;
    public String time;
    public String name;
}

定义注解处理器

@AutoService(Processor.class) // 其中这个注解就是 auto-service 提供的 SPI 性能
public class DocProcessor extends AbstractProcessor{

    Writer docWriter;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);

    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 可解决的注解的汇合
        HashSet<String> annotations = new HashSet<>();
        String canonicalName = GDoc.class.getCanonicalName();
        annotations.add(canonicalName);
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {Messager messager = processingEnv.getMessager();
        Map<String,Entity> map = new HashMap<>();
        StringBuilder stringBuilder = new StringBuilder();
        for (Element e : env.getElementsAnnotatedWith(GDoc.class)) {GDoc annotation = e.getAnnotation(GDoc.class);
            Entity entity = new Entity();
            entity.name = annotation.name();
            entity.author = annotation.author();
            entity.time = annotation.time();
            map.put(e.getSimpleName().toString(),entity);

            stringBuilder.append(e.getSimpleName()).append("").append(entity.name).append("\n");
        }

        try {docWriter = processingEnv.getFiler().createResource(
                    StandardLocation.SOURCE_OUTPUT,
                    "","DescClassDoc.json"
            ).openWriter();

            //docWriter.append(JSON.toJSONString(map, SerializerFeature.PrettyFormat));
            docWriter.append(stringBuilder.toString());
            docWriter.flush();
            docWriter.close();} catch (IOException e) {//e.printStackTrace();
            // 写入失败
        }
        return true;
    }
}

我的项目中援用

dependencies {implementation project(':DocAnnotation')
    annotationProcessor project(':DocComplie')
}

利用一个 Utils

@GDoc(name = "色彩工具类",time = "2019 年 09 月 18 日 19:58:07",author = "dingxx")
public final class ColorUtils {}

最初生成的文档如下:

 名称              性能            作者
ColorUtils      色彩工具类        dingxx

当然最初生成的文档能够由本人决定,也能够间接是 html 等.

Android Transform

在说 Android Transform 之前,先介绍 Android 的打包流程,在执行 task assemble 时

在.class /jar/resources 编译的过程中,apply plugin: ‘com.android.application’ 这个插件反对定义一个回调 (com.android.tools.build:gradle:2.xx 以上),相似拦截器,能够进行你本人的一些定义解决,这个被成为 Android 的 Transform

那么这个时候,能够动静的批改这些 class,实现咱们本人想干的一些事,比方修复第三方库的 bug,主动埋点,给第三方库增加函数执行耗时,实现动静的 AOP 等等.

咱们所晓得的 ARoute 就应用了这种技术. 当然他是先应用了 APT 学生成路由文件,而后通过 Transform 加载.

以下内容援用自 ARoute ReadMe

应用 Gradle 插件实现路由表的主动加载 (可选)

apply plugin: 'com.alibaba.arouter'
buildscript {
    repositories {jcenter()
    }

    dependencies {classpath "com.alibaba:arouter-register:?"}
}

可选应用,通过 ARouter 提供的注册插件进行路由表的主动加载 (power by AutoRegister),默认通过扫描 dex 的形式 进行加载通过 gradle 插件进行主动注册能够缩短初始化工夫解决利用加固导致无奈间接拜访 dex 文件,初始化失败的问题,须要留神的是,该插件必须搭配 api 1.3.0 以上版本应用!

看 ARoute 的 LogisticsCenter 能够晓得,init 时,如果没有应用 trasnform 的 plugin, 那么他将在注册时,遍历所有 dex,查找 ARoute 援用的相干类,如下

//LogisticsCenter
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {if (registerByPlugin) {logger.info(TAG, "Load router map by arouter-auto-register plugin.");
    } else {
        Set<String> routerMap;

        // It will rebuild router map every times when debuggable.
        if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
            // These class was generated by arouter-compiler.
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            if (!routerMap.isEmpty()) {context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP,routerMap).apply();}
            PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
        } else {logger.info(TAG, "Load router map from cache.");
            routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
        }
    }
    ....
}

Android Transform 的实现简介

通过以上,咱们晓得,回调的是.class 文件或者 jar 文件,那么要解决.class 文件或者 jar 文件就须要字节码解决的相干工具,罕用字节码解决的相干工具都有

  • ASM
  • Javassist
  • AspectJ

具体的具体,能够查看美团的推文 Java 字节码加强探秘

怎么定义一个 Trasnfrom 内,回顾下面的 gradle plugin 实现,看以下代码

public class TransfromPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {AppExtension appExtension = (AppExtension) project.getProperties().get("android");
        appExtension.registerTransform(new DemoTransform(), Collections.EMPTY_LIST);
    }
    
    class DemoTransform extends Transform{

        @Override
        public String getName() {return null;}

        @Override
        public Set<QualifiedContent.ContentType> getInputTypes() {return null;}

        @Override
        public Set<? super QualifiedContent.Scope> getScopes() {return null;}

        @Override
        public boolean isIncremental() {return false;}
    }
}

联合字节码减少技术,就能够实现动静的一些 AOP,因为篇幅起因,这里就不在具体把笔记拿进去了,如果想进一步学习,举荐 ARoute 作者的一个哥们写的 AutoRegister,能够看看源码

总结

到这里 Gradle 的学习笔记根本整顿实现了,因为作者程度无限,如果文中存在谬误,还请斧正,感激. 也举荐浏览我的另一篇文章,Android 修图(换证件照背景,污点修复)

本文转自 https://juejin.cn/post/6844903944439726087,如有侵权,请分割删除。

相干视频:

【2021 最新版】Android studio 装置教程 +Android(安卓)零基础教程视频(适宜 Android 0 根底,Android 初学入门)含音视频_哔哩哔哩_bilibili

Android 性能优化学习【二】:APP 启动速度优化_哔哩哔哩_bilibili

Android 性能优化学习【三】:如何解决 OOM 问题_哔哩哔哩_bilibili

正文完
 0