杂篇:一代版本一代神[-Gradle-]

作者: e4e52c116681 | 来源:发表于2019-02-12 19:54 被阅读18次

    零、前言

    本文主要包括:
    |---Gradle简介,下载,安装
    |---Groovy语言的简单认识
    |---Gradle构建java项目
    |---Gradle构建脚本的书写
    |---Gradle构建java多模块项目
    |---Gradle在Android中的应用
    |---最后写一个创建文件夹的小插件
    

    一、Gradle简介

    1.我与Gradle的邂逅

    百分之八十的Gradle使用者应该都是从AndroidStudio接触Gradle

    想当年用Eclipse喜欢收藏jar包,但版本迭代后,还要重新找  
    虽然挺麻烦,但是有jar包用感觉也是无比幸福的,毕竟别人的心血能省自己不少事
    
    毕竟简单的学习小项目不需要那么多第三方依赖,更不用说什么依赖管理  
    当我刚用AndroidStudio时,最不解的就是Gradle,开始一段时间基我俩井水不犯河水  
    我依然用着我的jar包,在src下写代码,似乎不用Gradle也没什么影响  
    
    第一次接触Gradle是看一片介绍oKHttp的文章,照着写一句,然后神奇的就OK了  
    这让我很惊讶,当看到源码时发现已经下载到本地了。心想:现在这么智能了?
    当我发现断网情况下依然可以使用本地的库文件,jar包就被我彻底抛弃了  
    
    一直以来Gradle对我来说就是添加依赖,感觉熟悉又陌生
    就像一个人一直帮你干活,你却对它除了工作之外一无所知,这显然不太好
    

    2.构建工具
    Ant: 长江后浪推前浪,前浪已经over了
    |---编译、测试、打包
    
    Maven:使用xml标记构建脚本
    |---依赖管理、编译、测试、打包、发布
    
    Gradle:使用Groovy语言构建脚本
    |---依赖管理、编译、测试、打包、发布、灵活的脚本
    

    3.Gradle是什么,怎么安装?

    一个基于Groovy语言的开源项目自动化构建工具
    如果你用过AndroidStudio,Gradle已经被你下好了,直接打开下面的路径
    你可以将bin目录加入环境变量,下面的几点就不用看了,


    3.1:确保jdk已安装
    C:\Users\Administrator>java -version
    java version "10.0.1" 2018-04-17
    Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
    Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
    

    3.2:下载Gradle,地址:

    将解压后的bin文件夹目录加到环境变量即可,

    下载Gradle.png 添加环境变量.png
    3.3:查看是否安装正确
    C:\Users\Administrator>gradle -v
    
    Welcome to Gradle 5.2!
    
    Here are the highlights of this release:
     - Define sets of dependencies that work together with Java Platform plugin
     - New C++ plugins with dependency management built-in
     - New C++ project types for gradle init
     - Service injection into plugins and project extensions
    

    二、Groovy语言

    1.Groovy简介
    基于java虚拟机的动态语言
    面向对象/脚本,完全兼容java语法
    

    2.创建一个gradle项目
    创建一个gradle项目
    3.修改Gradle配置的方法

    注:目前2019-2-7日:gradle-5.2在Idea里Build失败,gradle-4.10.1没问题
    估计是Idea插件的版本未更新,PS(Android目前也是用的gradle-4.10.1)

    Idea插件报错.png

    修改Gradle配置,出现下面的界面,Gradle插件就运行ok了

    成功.png
    4.Java VS Groovy
    Java VS Groovy
    ---->[java]------------------
    public class Version {
        private int major;
        private int minor;
        public Version(int major, int minor) {
            this.major = major;
            this.minor = minor;
        }
        public int getMajor() {
            return major;
        }
        public void setMajor(int major) {
            this.major = major;
        }
        public int getMinor() {
            return minor;
        }
        public void setMinor(int minor) {
            this.minor = minor;
        }
    }
    Version version = new Version(2, 1);
    System.out.println(version.getMajor()); //2
    
    ---->[Groovy]------------------
    class Version {
        private int major
        private int minor
        Version(int major, int minor) {
            this.major = major
            this.minor = minor
        }
    }
    def version = new Version(2, 1)
    println version.major //2
    

    5.Groovy的特色
    类及方法默认public,字段自动getter,setter,直接点号获取
    最后一个表达式的值作为返回值
    == 等用于equals(),assert语句,弱类型,
    分号可选,扩号可选,字符串三种,闭包
    
    groovy简介.png
    def age = 24//推到类型
    assert age < 25 //断言
    println age//省略括号
    def name = '张风捷特烈'//单引号字符串
    def say = "我是${name}"//双引号插值字符串
    def code = //三引号原样输出
            '''
    我是${name}
    def age = 24//推到类型
    assert age < 25 //断言
    '''
    
    println say
    println code
    
    //list
    def platform = ["java", "Android", "ios"]//定义集合
    platform << 'Linux'//添加元素
    println platform.class//class java.util.ArrayList
    println platform.size()//4
    
    //map
    def release = [
            'v0.01': '2018-12-13',
            'v0.02': '2018-12-14',
            'v0.03': '2018-12-15']
    release.'v0.04' = '2018-12-16'//添加
    println release.getClass()//class java.util.LinkedHashMap
    println release.size()//4
    
    //闭包
    def update ={
        v-> return v+1
    }
    
    def updateFun(Closure closure,int version) {
        closure(version)
    }
    
    def newVersion = updateFun(update, 1)
    println newVersion//2
    

    三.创建项目

    1.新建项目

    Idea会为我们自动生成项目结构

    创建新的module.png
    2.写一个方法
    /**
     * 作者:张风捷特烈
     * 时间:2019/2/7/007:8:58
     * 邮箱:1981462002@qq.com
     * 说明:将字符大写方法
     */
    public class UpCase {
        public static String toUpCase(String str) {
            System.out.println(str);
            return str.toUpperCase();
        }
    }
    

    3.导出jar包

    打jar包非常简单,点两下就行了,(其中字符集的问题后面解决,不影响jar包使用)

    打jar包.png
    4.使用jar包

    打了jar包就用用吧,虽然实际中已经很少用jar包依赖了,这里演示一下
    新建一个App的module,将jar包导入,并依赖,然后就能正常使用了

    引入jar包.png

    四、关于Gradle构建脚本

    gradle根目录\src\core-api\org\gradle\api\Project.java是一个interface
    它定义了一个项目类,而build.gradle中即使用了项目对象的属性和方法
    这两个类是Gradle的核心,其中定义了很多方法,可以在.gradle文件中随意调用

    实体类.png
    |---比如打印一下当前项目目录
    ---->[org.gradle.api.Project#getProjectDir]-------
    /**
     * <p>The directory containing the project build file.</p>
     *
     * @return The project directory. Never returns null.
     */
    File getProjectDir();
    
    ---->[App/build.gradle]----------
    println getProjectDir()//J:\Java\GradleTest\toly\App
    

    1.解放双手task
    1.1 :简单的创建文件夹任务

    public interface Task extends Comparable<Task>, ExtensionAware
    Task是一个接口,可以助你完成一些无聊的工作,这里以创建三个文件夹为例

    创建任务.png
    def mkDir = {//创建文件夹的方法
        path ->
            def dir = new File(path)
            if (!dir.exists()) {
                dir.mkdirs()
            }
    }
    
    task mkDirTask() {//自定义一个任务
        def paths = [
                'src/main/java/com/toly1994/app/adapter',
                'src/main/java/com/toly1994/app/activity',
                'src/main/java/com/toly1994/app/fragment',
        ]
        doFirst {
            paths.forEach(mkDir)
        }
    }
    

    如果上面的看懂了,这里用projectName替换一下项目名
    这样就可以在任意项目里创建这三个文件夹了,

    task mkDirTask() {//自定义一个任务
        def projectName = project.name.toLowerCase()//获取工程目录
        def paths = [
                "src/main/java/com/toly1994/${projectName}/adapter",
                "src/main/java/com/toly1994/${projectName}/activity",
                "src/main/java/com/toly1994/${projectName}/fragment",
        ]
        doFirst {
            paths.forEach(mkDir)
        }
    }
    

    1.2:Task之间的依赖dependsOn

    也就是在执行之前,先执行被依赖的Task

    task依赖.png
    task mkDirTaskWithUtils{
        dependsOn 'mkDirTask'//依赖mkDirTask任务
        def projectName = project.name.toLowerCase()//获取去工程目录
        def paths = [
                "src/main/java/com/toly1994/${projectName}/utils"
        ]
        doFirst {
            paths.forEach(mkDir)
        }
    }
    

    2.构建的生命周期
    构建的生命周期及回调.png
    //在构建项目前调用的钩子函数
    gradle.beforeProject {
        project ->
            println "-------beforeProject-------"
    }
    
    //配置解析前回调
    gradle.taskGraph.whenReady {
        graph ->
            println "-------whenReady-------"
    }
    
    gradle.buildFinished {
        result ->
            println "-------buildFinished-------"
    }
    

    3.依赖管理
    3.1.简介

    关于implementation和compile的区别,这里简单说一下。可详见:

    依赖管理.png
    工件坐标:(group,name,version)
    工件仓库:mavenCentral/jcenter
    依赖的传递:若A-->B  B-->C  则A-->C 
    
    implementation(编译期)-----曾经为compile
    |--1.加快编译速度(本模块编译)。
    |--2.对外隐藏不必要的接口。
    
    runtime(运行期)、testCompile(测试编译期)、testRuntime(测试运行期)
    
    //使用mavenCentral仓库
    repositories {
        mavenCentral()
    }
    //依赖管理
    dependencies {
        //测试编译时依赖
        testCompile group: 'junit', name: 'junit', version: '4.12'
    

    3.2:mavenCentral与依赖使用

    maven仓库相比大家都知道吧,简单说一下怎么查看一个依赖(okhttp为例):仓库网址
    可以看到okhttp的工件坐标(group,name,version),它确定了唯一的存在

    寻找okhttp依赖.png
    //依赖管理
    dependencies {
        //测试编译时依赖
        testCompile group: 'junit', name: 'junit', version: '4.12'
        // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
        implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.13.1'
    }
    
    添加依赖.png
    //简写形式 implementation'组:名:版本号' -----是不是很亲切
    implementation 'com.squareup.okhttp3:okhttp:3.13.1'
    

    3.3:okhttp的使用
    /**
     * 作者:张风捷特烈
     * 时间:2019/2/8/008:10:27
     * 邮箱:1981462002@qq.com
     * 说明:测试使用okhttp
     */
    public class Api {
        public static void main(String[] args) {
            doGet("http://www.toly1994.com:8089/api/android/note");
        }
        public static void doGet(String url) {
            //1.获取OkHttpClient对象
            OkHttpClient okHttpClient = new OkHttpClient();
            //2.获取Request对象
            Request request = new Request.Builder().get().url(url).build();
            //3.将Request封装为Call对象
            Call call = okHttpClient.newCall(request);
            //4.执行Call
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    System.out.println(e);
                }
                @Override
                public void onResponse(Call call, Response response) throws IOException
                    String str = response.body().string();
                    System.out.println(str);
                }
            });
        }
    }
    
    访问网络成功.png 缓存到本地的源码.png
    3.4:使用其他的仓库

    前面找不到就找下一个

    repositories {
        mavenCentral()//使用mavenCentral仓库
        google()//使用google仓库
        jcenter()//使用jcenter仓库
        maven {//maven私服
            url 'https://jitpack.io'
        }
    }
    

    4:版本冲突
    4.1:版本冲突简介

    okhttp:3.13.1依赖了okio:1.17.2,如果项目中再依赖okio:2.2.2就会版本冲突
    默认情况下版本冲突时,Gradle会自动使用最高版本,所以我们并不怎么烦神

    版本冲突.png 版本冲突-.png
    4.2:自己解决版本冲突

    自动使用最高版本,大多数情况都适用,但你还是有自定义解决方案的机会的
    首先,显示版本冲突在哪里

    显示版本冲突.png
    configurations.all{
        resolutionStrategy{
            failOnVersionConflict()//版本冲突时报错
        }
    }
    

    4.3:强制使用某版本
    强制使用制定版本.png
    configurations.all {
        resolutionStrategy {
    //        failOnVersionConflict()//版本冲突时报错
            force 'com.squareup.okio:okio:1.17.2'//强制指定版本
        }
    }
    

    4.4:排除一个依赖中的依赖
    implementation('com.squareup.okhttp3:okhttp:3.13.1'){
        exclude group: 'com.squareup.okio',module:'okio'
    }
    

    五、项目的模块化构建及测试

    0.项目的模块化
    创建三个module.png
    1.settings.gradle

    一个项目只有一个,用来管理子模块名称

    rootProject.name = 'toly-all'
    include 'model'
    include 'repository'
    include 'player'
    

    2.添加模块间依赖关系

    implementation project(":模块名")-或compile
    注意:项目间的传递型依赖要用compile,不然引用不到

    四个模块间的依赖关系.png
    ---->[toly-all\repository\build.gradle]------------
    dependencies {
        compile project(":model")
        testCompile group: 'junit', name: 'junit', version: '4.12'
    }
    
    ---->[toly-all\player\build.gradle]------------
    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
        compile project(":repository")
    }
    
    ---->[toly-all\build.gradle]------------
    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
        implementation project(":player")
    }
    

    3.Gradle信息的公共处理

    每个build.gradle都有的东西,统一处理一下,以后改起来方便
    在根项目下的build.gradle里使用allprojects

    ---->[toly-all\build.gradle]------------
    allprojects {
        apply plugin:'java'
        sourceCompatibility = 1.8
    
        repositories {
            mavenCentral()
        }
        dependencies {
            testCompile group: 'junit', name: 'junit', version: '4.12'
        }
    }
    

    4.使用gradle.properties统一配置参数
    ---->[toly-all\gradle.properties]------------
    group='com.toly1994'
    version='0.01'
    

    5.测试
    idea自动生成测试方法.png
    public class PlayerTest {
        @Test
        public void tolySay() {
            Player player = new Player();
            Person person = player.tolySay();
            assert person.name.equals("toly");
            assert person.age == 24;
        }
    }
    
    build.png
    6.发布

    使用maven-publish插件,发表到中央仓库挺麻烦的,还是自己搭个私服,或直接本地吧

    maven.png

    以上是Gradle在java中的使用,现在回头看一下Android里的Gradle,你应该更有感觉


    六、Gradle在Android中

    1.现在新建一个Android普通项目
    ---->[模块:build.gradle]-----------------
    apply plugin: 'com.android.application'  //启用插件 com.android.application
    
    android {//安卓
        compileSdkVersion 27//SDK编译版本
        defaultConfig {//默认配置
            applicationId "com.toly1994.gradletest"//应用id
            minSdkVersion 21//兼容的SDK最低版本
            targetSdkVersion 27//SDK目标版本(本应用的SDK--向下兼容)
            versionCode 1//版本号
            versionName "1.0" //版本名称
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {//build配置
            release {//发布设置
                minifyEnabled false //是否混淆
                //混淆文件
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {//依赖
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation 'com.android.support:appcompat-v7:27.1.1'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
    
    
    ---->[项目:build.gradle]-----------------
    buildscript {//构建脚本
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.2.1'
            
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {//对所有模块适用
        repositories {//仓库
            google()//google仓库
            jcenter()//jcenter仓库
        }
    }
    
    task clean(type: Delete) {//清除的task
        delete rootProject.buildDir
    }
    

    2.现在新建一个JNI的Android项目

    模块级的gradle文件多了externalNativeBuild

    android {
        ...
        defaultConfig {
            ...
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
        }
        ...
        externalNativeBuild {//设置cmake的目录
            cmake {
                path "CMakeLists.txt"
            }
        }
    }
    

    stringFromJNI抽取出来放在一个类中作为静态方法,然后生成so文件

    jni目录结构.png 生成so文件.png
    3.回到前一个工程,使用so文件

    注意so文件在其他工程下需要保证接口名的一致,比不刚才的C++中的函数:
    Java_com_toly1994_jni_HelloJNI_stringFromJNI
    该函数只能用在:com.toly1994.jni下的HelloJNI类中的stringFromJNI方法,错一个字都不行

    使用so.png 使用so文件.png
    android {//安卓
        ...
        sourceSets {//------默认如下,可不用配置
            main {
                jni.srcDirs = []
                jniLibs.srcDirs = ['src/main/jniLibs']//默认路径,可修改
            }
        }
    }
    

    4.资源分包

    这里以布局为例,其他资源文件夹也一样

    布局分包.png
    android {
        ...
        sourceSets {
            main {
                res.srcDirs = [
                        'src/main/res/layouts/home',
                        'src/main/res/layouts/player',
                        'src/main/res/layouts/news',
                        'src/main/res'
                ]
            }}
    }
    
    

    public interface AndroidSourceSet接口定义很多文件的位置
    都可以根据自己的需要,自行修改

    res.srcDirs         资源文件目录
    assets.srcDirs      assets文件目录
    aidl.srcDirs        aidl文件目录
    jniLibs.srcDirs     .so文件目录
    jni.srcDirs         jni文件目录
    manifest.srcFile    AndroidManifest.xml的位置
    java.srcDirs        java代码的文件目录
    

    5.混淆与发布

    提一下:gradle.properties里的键值对可以在.gradle里直接使用
    你可以将密码写在里面,.gitignore配置一下,不上传到github就行了

    签名.png
    android {
       ...
        signingConfigs {
            release {
                storeFile file("tolyapp.jks")
                storePassword APK_SIGN_STORE_PASSWORD
                keyAlias "toly"
                keyPassword APK_SIGN_KEY_PASSWORD
            }
        }
        
    buildTypes {
        release {
            shrinkResources true//是否去除未利用的资源,默认false,表示不去除。
            minifyEnabled true//是否混淆
            signingConfig signingConfigs.release//签名
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    
    ---->[gradle.properties]--------------
    APK_SIGN_KEY_PASSWORD=777777
    APK_SIGN_STORE_PASSWORD=666666
    
    发布.png
    6.公共信息提取:ext

    对于很多公共的东西,提取出来容易统一管理和修改,特别对于多模块项目而言

    自定义参数.png
    7.引用其他的.gradle文件

    .gradle 文件一直被我认为是神圣的存在,不能乱改,更别提自己创建了
    骑士gradle文件是可以相互引用的,下面通过创建文件夹小插件来说明

    文件夹插件.png
    ---->[mkdir.gradle]---------
    import java.util.function.Consumer
    apply plugin: 'com.android.application'
    apply plugin: MkDirPlugin//声明使用插件
    
    mkDir {//根据拓展参数来自定义文件夹
        pkg = 'com.toly1994.gradletest_'
        names = ['adapter','activity','app/config',
                 'app/compat','utils','view','presenter','model']
    }
    //----------------------------以下是插件部分--------------------------------
    class MkDirPlugin implements Plugin<Project> {
        //该接口定义了一个apply()方法,在该方法中,我们可以操作Project,
        //比如向其中加入Task,定义额外的Property等。
        void apply(Project project) {
            //加载Extension
            project.extensions.create("mkDir", MkDirPluginPluginExtension)
    
            def mkDir = {
                    //创建文件夹的方法
                path ->
                    def dir = new File(path)
                    if (!dir.exists()) {
                        dir.mkdirs()
                    }
            }
            //使用Extension配置信息
            project.task('mkDirTask') << {
                String pkg = project.mkDir.pkg
                ArrayList<String> names = project.mkDir.names
                def dir='src/main/java/'+pkg.replaceAll("\\.",'''/''')
                ArrayList<String> paths = new ArrayList<>()
                names.forEach(new Consumer<String>() {
                    @Override
                    void accept(String s) {
                        paths.add(dir+"/"+s)
                        println dir+"/"+s
                    }
                })
                paths.forEach(mkDir)
            }
    
        }
    }
    
    class MkDirPluginPluginExtension {//拓展参数  
        String pkg = ''
        def names = []
    }
    //----------------------------插件结束--------------------------------
    
    ---->[模块级build.gradle]--------------
    apply from: 'mkdir.gradle' //引用mkdir.gradle ---一行搞定
    

    插件部分你不用Groovy,全部用java写都可以,Groovy对java是兼容的
    插件你也可以新建一个项目来制作,可以发布一下,给更多人使用
    所以燃烧你的小宇宙,用gradle尽情偷懒吧!相信你会发现另一片天地!


    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 附录
    V0.1-- 2018-2-12

    发布名:一代版本一代神[-Gradle-]
    捷文链接:https://www.jianshu.com/p/075f846207a9

    2.更多关于我
    笔名 QQ 微信
    张风捷特烈 1981462002 zdl1994328

    我的github:https://github.com/toly1994328
    我的简书:https://www.jianshu.com/u/e4e52c116681
    我的掘金:https://juejin.im/user/5b42c0656fb9a04fe727eb37
    个人网站:http://www.toly1994.com

    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持

    icon_wx_200.png

    相关文章

      网友评论

        本文标题:杂篇:一代版本一代神[-Gradle-]

        本文链接:https://www.haomeiwen.com/subject/xztleqtx.html