Android Gradle知识分享

By | 2016/11/03

现在大家开发都开始用AndroidStudio了,然后在新建工程的时候,发现跟之前eclipse的结构很不一样了,多了一个叫Gradle的东西,有对应的build.gradle文件。

Gradle是什么呢?我个人认为其实就是一个构建项目的一个配置脚本,可以用于构建Java、Android等等语言,早起我们脚本编译很多用的都是Ant方式编译,当然rom用的是mk我们都经常用。那既然有了之前那些编译方式,为什么还要改成用Gradle呢~~

看看它都能做些什么吧~

  1. 添加依赖jar包
    a.平时我们在开发app里,避免不了要加一些jar包之类的,然后我们就要去先下载对应版本的jar包,然后复制到项目的libs文件里。当有新的版本时,我们还要再去那个网站下载最新的jar包,或者自己拉新代码再编包。比如support v7 recycleView之类的,经常更新。
    有了Gradle,我们就好办了,可以这样写就行:

    dependencies {
            compile 'com.android.support:recyclerview-v7:22.2.1'
            //或者可以这样写:compile group: 'com.android.support', name:'recyclerview-v7', version:'22 rso0npb.2.1'
        }
    

    如果更新的话呢,就只需要更改后面的版本号即可,这个版本号,可以在androidSdk(升级sdk那里面)或者官网搜support里,获取到最新support库的版本号。

    b. 如果我们有时需要添加自己的jar,我们可以这样写:

    dependencies {
            compile fileTree(dir: 'libs', include: '*.jar')// 除了include外,还有excludes,用来排除某些jar包,如:excludes:['a.jar','b.jar']
        }
    

    c. 第二句话的意思是将libs里面文件夹里的所有jar包添加进来,当然我们也可以只添加指定的jar包,类似这样写:

    dependencies {
        compile files('libs/lib1.jar','libs/lib2.jar')
    }
    

    d. 如果我们开发的是插件的话,而主程序包里面已经有对应的jar包了,但是插件编译也需要,但如果两个同时都写成compile的话,会出现类重复的问题,这时,我们可以把插件的dependencies,里面的compile改为provided,类似这样:

    dependencies {
            provided 'com.android.support:recyclerview-v7:22.2.1'
        }
    

    这样写,就只会参与编译,而不会打包里文件里了。

    e. 如果我们要依赖某个库项目,比如二维码扫描zxing,我们可以这样写:

    compile project(':zxing')</pre>
    

    f. 如果我们依赖库A,而不想排除掉里面所依赖的库B,那我们可以这样写:

    compile (A, {exclude B}) // 这种场景可能很少碰到,之前我好像用QiniuSDk的时候,有一个冲突的依赖库,当时还不知道可以这样写,以至于一直没有敢使用最新版的sdk,现在这样就可以啦~ 
    //默认gradle都会帮我们解决冲突的,一般是用共同依赖库的最新的那个版本
    

    说了这么多,那我们的这些库去哪里知道具体的名字或版本号呢?安卓默认都是从jcenter这个网站下载的,大家可以去看一下 https://bintray.com/bintray/jcenter ,这个相当于版本库,类似maven,当然gradle也可以用maven下管理的库,或者自己的url。
    我们通过修改gradle文件里面的repositories里面的东西来改,可以写的方式如下:

    repositories {
        mavenCentral()
        mavenLocal()
        jcenter()
        maven { url 'http://sdk.pt.miui.com/miuisdk/maven2' }
        
        ivy//或 maven {
        url 'https://xxxx'
        // 登录此网址的用户名密码
        credentials {
            username 'user' // 用户名
            password 'pwd' //密码
        }
    }
    }
  2. 来看一下 buildTypes
    buildTypes默认新建项目会是这样的:
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    我们看到了release编译类型,我们可以理解为正式发布版本的一些相关配置,当然默认还有一个debug的,我们还可以自己写一些配置,比如QATest(名字自定义),然后我们在AS里看一下右侧的Gradle tab,可以发现多了QATest的编译模式
    1

    这样我们就可以进行一些特殊的定制编译,比如像debug,默认不混淆,不压缩包或定义一些变量控制log输出,类似这样:

           debug {
                versionNameSuffix "-debug"
                minifyEnabled false
                debuggable true
                zipAlignEnabled false
                shrinkResources false
                buildConfigField "boolean", "LOG_DEBUG", "true"
            }
    
            release {
                debuggable false
                minifyEnabled true
                zipAlignEnabled true
                //移除无用的resource文件
                shrinkResources true
                proguardFile 'proguard.cfg'
                signingConfig signingConfigs.myownsignname
                buildConfigField "boolean", "LOG_DEBUG", "false"
            }
    

    里面的buildConfigField,可以定义一些变量,控制不同的编译值不同,回编译到BuildConfig类里面,作为常量存在,java里就可以这样读取了:

    BuildConfig.LOG_DEBUG;
    

    除了上面这种定制区分外,我们还可以做一些别的事情,比如release包里面的strings文件里面这样的:

    <resources>
        <string name="app_name">Release_App</string>
    </resources>
    

    而我们在debug里面却需要显示成Debug_App,我们可以这样操作:

    2
    这里选择debug

    3

    然后string文件随便命名,然后见如下结构,
    4
    在对应的xml里写下:

    <resources>
        <string name="app_name">Debug_App</string>
    </resources>

    这样就可以根据不同的编译方式,打出来的包名字就不一样了,是不是很方便,在debug目录下就可以做很多类似overlay的事情了,相同的key会进行相应的覆盖操作。比如我们可以定制不同的图片,便于方便的确定是开发版还是发布版。。。。
    这里介绍的是编译类型的区分,除了这个,还有另一个方式,而且这两种方式还能交叉混合使用。下面介绍一下这个:productFlavors

    3. productFlavors
    查了一下,网上翻译成这个:不同定制的产品
    比如我们平时开发会有这种需求:免费版与收费版,比如修改包名:

    productFlavors {
        free{
            applicationId "com.myproduct.xxx.free"
        }
    
        pay {
            applicationId "com.myproduct.xxx project management tasks.pay"
        }
    }

    除了能修改包名,我们还可以像buildtype一样,进行相应的定制,这里就不重复介绍方法了,跟上面类似,source set可以设置指定
    添加完上面两个后,我们再来看一下Gradle里面的build项:
    5
    发现变成了这样,对应的free和pay两种方式,然后再跟debug和release进行2*2组合,就产生了4中编译方式,Test先不看,类似这样:
    gradle_buildType_productflavors

    buildTypes 与 productFlavors 做一个矩阵乘,得到不同的组合,这样一来,我们的定制方式又丰富了很多。

    除了上面这功能外,我们平时还有这种场景需要用,大家都知道安卓市场相当多,而我们的统计却又需要知道各个渠道的下载量之类的信息,便于大力推广,比如用友盟统计,我们一般会在Manifest里面添加这个:

    <meta-data android:value="channel_360" android:name="UMENG_CHANNEL"/>

    那我们每个渠道打包,我们就要改一下这个,是不是很烦呢?要不我们也采用上面的覆盖?但是大部分东西都没变,也没有必要,有时用string的话可能有些渠道还读取失败,我们只能写死在里面靠谱些,我们该怎么弄呢?
    有了gradle的productFlavors,我们就方便了很多啦~
    首先我们先把上面这段改成这样:

    <meta-data android:value="${UMENG_CHANNEL_VALUE}" android:name="UMENG_CHANNEL"/>

    然后这样写:

    productFlavors {
            channel_xiaomi {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_xiaomi"]
            }
            channel_360 {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_360"]
            }
            channel_baidu {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_baidu"]
            }
            channel_wandoujia {
                manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_wandoujia"]
            }
        }

    这样就会在编译不同渠道包的时候,对应的Manifest就会替换成不同的名字。
    但代码是不是看起来代码还是有点冗余,能否像java一样循环设置呢,答案是可以的,修改如下:

    productFlavors {
            channel_xiaomi {
            }
            channel_360 {
            }
            channel_baidu {
            }
            channel_wandoujia {
            }
        }

    先声明一些productFlavor,然后遍历所有的flavor并设置manifestPlaceholders这个属性值,这样代码就简化了好多(其中manifestPlaceholders的值可以设置多个map对):

    productFlavors.all {
            flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
        }

    除了上面这些外,我们依赖也可以不同,就拿推送来说,由于Google的推送我们国内用不了,所以就只能用第三方或自己写,自己写的问题是像我们小米这样的机型可能都把你杀死了根本收不到消息,那只能用小米自己出的sdk,华为用华为自己出的,其他机型用别的,那我岂不是要集成很多sdk,小米渠道的包里面还有华为的,很不好这样,增大了包的体积,那我们能否根据渠道不同,依赖不同的包呢?可以!

    我们这样来写:

    dependencies {
        channel_xiaomiCompile xxx.xiaomi.sdk:xx
        channel_360Compile xxx.360.sdk
    }

    这样就会根据不同渠道,放入不同的jar包,然后我们写不同的java文件就行,使用统一接口调用即可

  3. 介绍完上面这个后,我们再来看一下这个 两种对象类型,用于配置不同的task
    分别是: applicationVariants(对应的是控制APK相关的变量), libraryVariants(对应的是控制AAR库相关的变量)
    这里就只说一下applicationVariants了,它可以控制编译apk时的一些配置,比如我们想定制生成apk的名字,我们可以这样用:

    applicationVariants.all { variant -&gt;
    //            variant.buildConfigField "int", "VALUE", "1" 添加BuildConfig变量
    //            variant.resValue "string", "name", "value"   添加res string值
            variant.outputs.each { output -&gt; // 遍历输出
                def outputFile = output.outputFile
                if (outputFile != null &amp;&amp; outputFile.name.endsWith('.apk')) {
                    def fileName
                    if (variant.buildType.debuggable) {
                        fileName = "test_v${defaultConfig.versionName}_debug.apk"  // out: test_v1.0_debug.apk
                    } else {
                        fileName = "test_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk" // out: test_v1.0_20161103_channel_360.apk
                    }
                    output.outputFile = new File(outputFile.parentFile, fileName) // 这里我们还可以修改apk的存放目录,parentFile可以指定到自己想要的目录里,但建议相对目录,不要绝对目录,便于合作
                }
            }
     
    def releaseTime() {
        return new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08")) //HH
    }
    

    然后在执行编译渠道包的时候,就会定制好需要的名字,很方便的~

经过上面一系列的配置后,我们在AS的右侧gradle-》build-》assemble,运行一下,然后再去对应的build目录下,就会发现所有渠道的包,按照我们约定的名字都就打包好了,相当方便~~

相关文档查询可以到这里来看:https://docs.gradle.org/current/dsl/

ps:gradle 除了支持安卓,还支持java的构建~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注