Android之Gradle基础知识及常用功能配置

Gradle介绍

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。对Android开发人员来说,就是AS引入的Android工程的管理,帮我们做了依赖,打包,部署,发布,各种渠道的差异管理等工作(官方的解释比较绕口,我就这么通俗的理解)。

举个例子,以前用eclipse,那是导入一个网上的下载的module还需要一步步的import,但使用Android Studio,Gradle很贴心的完成了这个繁杂的工作,而且往往只需要添加一句话,这就是效率,可以让开发人员专心写代码。

Gradle文件

一个Android的AS工程通常有几个地方涉及Gradle文件:内置的Wrapper、settings.gradle、build.gradle(Project)、build.gradle(Module):

我们看到,一个主Project的Gradle文件管理全局,另外每个Module都会对应有一个Gradle文件。

内置的Wrapper

Wrapper是对Gradle的一层包装,便于在团队开发过程中统一Gradle构建的版本号,这样大家都可以使用统一的Gradle版本进行构建。先看gradle-wrapper.properties这个文件的作用:

#Fri May 18 13:56:55 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

我们其实最关心的应该是distributionUrl这个属性,他是下载Gradle的路径。

我们经常在导入项目的时候一直会停留在这个界面加载Gradle界面,短则几分钟,长则30分钟,甚至更长,这是为什么?其实原因就是你常用项目的Gradle版本跟你新导入项目的Gradle版本不一致造成的,通常可以这么解决:

  • 网速好或者科学上网的时候,由它自己去下载,不过下载时间有长有短,不能保证;
  • 把其他项目的gradle-wrapper.properties文件替换掉你要导入项目的该文件,特别是多人协作开发,因个人电脑环境的问题导致的,这样处理非常有效。

settings.gradle

settings.gradle文件其实是用于初始化以及工程树的配置的,放在根工程目录下。

在Gradle众多工程是通过工程树表示的,相当于我们在Android Studio看到的Project和Module概念一样。根工程相当于Android Studio的Project,一个根工程可以有很多自工程,也就是很多Module,这样就和Android Studio定义的Module概念对应上了。

include ':app',':swipemenulib',':GaiaLibrary',':tuling-master',':speechutils-master','nineoldandroids',':lib_csmart'

我们可以看到这个项目我们添加了7个module,一一对应,如果你的项目添加了项目依赖,那就会出现在这个文件当中。

build.gradle(Project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {

    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • buildscript:buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等;
  • repositories:顾名思义就是仓库的意思啦,而jcenter()、maven()和google()就是托管第三方插件的平台;
  • dependencies:当然配置了仓库还不够,我们还需要在dependencies{}里面的配置里,把需要配置的依赖用classpath配置上,因为这个dependencies在buildscript{}里面,所以代表的是Gradle需要的插件;
  • allprojects:allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。

buildscript和allprojects的作用和区别,buildscript中的声明是gradle脚本自身需要使用的资源,就是说他是管家自己需要的资源;而allprojects声明的却是你所有module所需要使用的资源,就是说如果每个module都需要用同一个第三库的时候,你可以在allprojects里面声明。

gradle clean执行此处定义的task,该任务继承自Delete,删除根目录中的build目录,相当于执行Delete.delete(rootProject.buildDir),其实这个任务的执行就是可以删除生成的Build文件的,跟Android Studio的clean是一个道理。

build.gradle(Module)

该文件是整个项目的关键,能让Gradle发挥多大的用处,就在于此配置文件。

apply plugin:'×××'
apply plugin: 'com.android.application'

apply plugin: 'com.android.library'

基本上每个项目第一句都是一样,若是主Module,则用"application",库的话,就用"library"。通过查阅资料,这种叫做引入Gradle插件,而Gradle插件大致分为分为两种:

  • apply plugin:'×××':叫做二进制插件,二进制插件一般都是被打包在一个jar里独立发布的,比如我们自定义的插件,再发布的时候我们也可以为其指定plugin id,这个plugin id最好是一个全限定名称,就像你的包名一样;
  • apply from:'×××':叫做应用脚本插件,其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字.后面紧跟的坫一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用HTTP URL。
android{}
android {
    compileSdkVersion 27

}

android{}是Android插件提供的一个扩展类型,可以让我们自定义Android Gradle工程,是Android Gradle工程配置的唯一入口。

compileSdkVersion

compileSdkVersion是编译所依赖的Android SDK的版本,这里是API Level。

defaultConfig{}

defaultConfig是默认的配置,它是一个ProductFlavor。ProductFlavor允许我们根据不同的情况同时生成多个不同的apk包。

    defaultConfig {
        applicationId "com.cchip.***"
        minSdkVersion 18
        targetSdkVersion 27
        versionCode 1
        versionName "1.0.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
        multiDexEnabled false
    }
  • applicationId:配置我们的包名,包名是app的唯一标识,其实他跟AndroidManifest里面的package是可以不同的,他们之间并没有直接的关系。
  • minSdkVersion:是支持的Android系统的api level,这里是18,也就是说低于Android 18版本的机型不能使用这个app。
  • targetSdkVersion:表明我们是基于哪个Android版本开发的,这里是27。
  • versionCode:表明我们的app应用内部版本号,一般用于控制app升级,当然我在使用的bugly自动升级能不能接受到升级推送就是基于这个。
  • versionName:表明我们的app应用的版本名称,一般是发布的时候写在app上告诉用户的(特别是在某个版本上修复了一个重要的Bug,客户还一直纠结着没有修复,此时,首先对下这个版本号,避免双方不在一个频道上讨论问题)。
  • multiDexEnabled:用于配置该BuildType是否启用自动拆分多个Dex的功能。一般用程序中代码太多,超过了65535个方法的时候。
  • ndk{}:多平台编译,生成有so包的时候使用,包括四个平台'armeabi', 'x86', 'armeabi-v7a', 'mips'。一般使用第三方提供的SDK的时候,可能会附带so库。
buildType

构建类型,在Android Gradle工程中,它已经帮我们内置了debug和release两个构建类型,两种模式主要车别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的。

    buildTypes {
        release {
            minifyEnabled false
            shrinkResources false 
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            zipAlignEnabled true  
            ndk {
                abiFilters "armeabi", "x86"
            }
        }
        debug {
            ndk {
                abiFilters "armeabi", "x86"
            }
        }
    }
  • minifyEnabled:是否混淆
  • shrinkResources:是否去除未利用的资源,默认false,表示不去除
  • multiDexKeepProguard:指定混淆文件编译进主Dex文件中
  • proguardFiles:混淆文件
signingConfigs

签名配置,一个app只有在签名之后才能被发布、安装、使用,签名是保护app的方式,标记该app的唯一性。如果app被恶意删改,签名就不一样了,无法升级安装,一定程度保护了我们的app。而signingConfigs就很方便为我们提供这个签名的配置。

    signingConfigs {
        debug {
            keyAlias 'cchip'
            keyPassword '*******'
            storeFile file('cchip.keystore')
            storePassword '*******'
        }
    }
  • keyAlias签名证书中秘钥别名
  • keyPassword签名证书中改密钥的密码
  • storeFile签名文件
  • storePassword签名证书文件的密码
applicationVariants.all{}

自定义导出的APK名称,默认android studio生成的apk名称为app-debug.apk或者app-release.apk,当有多个渠道的时候,需要同时编出50个渠道包的时候,就麻烦了,不知道谁是谁了。这个时候,就需要自定义导出的APK名称了,不同的渠道编出的APK的文件名应该是不一样的。

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def oldFile = output.outputFileName
            def releaseApkName = ''
            if (variant.buildType.name.equals('release')) {
                if (variant.productFlavors[0] != null) {
                    releaseApkName = 'C Baby-V' + variant.productFlavors[0].name + '-v' + versionName + '-' + getDate() + '.apk'
                } else {
                    releaseApkName = 'C Baby-V' + versionName + '-' + getDate() + '.apk'
                }
                output.outputFileName  = new File("", releaseApkName)
            }
            if (variant.buildType.name.equals('debug')) {

            }
        }
    }



def getDate() {
    return new Date().format('MMddHHmm')
}
dependencies{}

项目依赖包管理,这个功能非常强大:

首先第一句compile fileTree(include: ['.jar'], dir: 'libs')*,这样配置之后本地libs文件夹下的扩展名为jar的都会被依赖,非常方便。
如果你要引入某个本地module的话,那么需要用compile project('×××')。
如果要引入网上仓库里面的依赖,我们需要这样写compile group:'com.google.code.gson',name:'gson',version:'2.8.1',当然这样是最完整的版本,缩写就把group、name、version去掉,然后以":"分割即可。
compile 'com.google.code.gson:gson:2.8.1'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:support-v4:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.jakewharton:butterknife:7.0.0'

    compile project(path: ':lib_csmart')
    compile project(path: ':GaiaLibrary')

    compile 'com.alibaba:fastjson:1.2.9'

    compile 'com.android.volley:volley:1.0.0'
    compile 'com.google.code.gson:gson:2.8.1'

    //登陆
    compile 'com.ali.auth.sdk:alibabauth_core:1.4.3@jar'
    compile 'com.ali.auth.sdk:alibabauth_ui:1.4.3@aar'
    compile 'com.ali.auth.sdk:alibabauth_ext:1.4.3@jar'

    //安全基础
    compile 'com.taobao.android:securityguardaar3:5.1.81@aar'

    //Mtop
    compile 'com.taobao.android:mtopsdk_allinone_open:1.3.0@jar'

    //applink
    compile 'com.taobao.android:alibc_applink:2.0.0.2@jar'

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'com.android.support:design:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    implementation 'com.android.support:support-vector-drawable:27.1.1'
    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'
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    implementation 'com.umeng.analytics:analytics:latest.integration'
    implementation 'io.reactivex.rxjava2:rxjava:2.1.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    implementation project(':swipemenulib')
    implementation project(':GaiaLibrary')
    implementation project(':tuling-master')
    implementation project(':lib_csmart')
    compile 'com.umeng.analytics:analytics:latest.integration'
}

但是到了gradle3.0以后build.gradle中的依赖默认为implementation,而不是之前的compile。

常用功能配置

定制APP

在Eclipse时代,定制APP,除了修改包名,可以还要修复因修改包名引起的编译异常问题,但在AS工程中,定制APP只需修改下Gradle中的:applicationId

applicationId "com.cchip.***"

只需修改applicationId,而不用理会Manifest中的package="com.cchip.XXX"

替换AndroidManifest中的占位符

举个例子,在AndroidManifest文件中,我们将盟统计的key值指定为一个占位符:

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

在build.gradle文件中,这里介绍3种方法去替换该占位符

  • 接收gradlew assemble命令输入的自定义参数的值
manifestPlaceholders = [
    UMENG_APPKEY: "\"" + UMENG_APPKEY_PARA + "\""
]
  • 使用string文件的值
manifestPlaceholders = [UMENG_APPKEY:"@string/UMENG_APPKEY"]
  • 使用gradle.properties文件的值:
# 友盟统计
UMENG_APPKEY=**************

多渠道打包

多渠道打包用得比较少,不涉及利益分配问题,都固定死一个来源,反正无所谓。但涉及利益分配的时候,还是需要的:

  • 配置AndroidManifest.xml

以友盟渠道为例,渠道信息一般都是写在 AndroidManifest.xml文件中:

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

如果不使用多渠道打包方法,那就需要我们手动一个一个去修改value中的值,xiaomi,360,qq,wandoujia等等。使用多渠道打包的方式,就需要把上面的value配置成下面的方式:

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

其中${UMENG_CHANNEL_VALUE}中的值就是你在gradle中自定义配置的值。

  • 在build.gradle设置productFlavors

写法如下:

productFlavors { 
  wandoujia  { 
    manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"] 
  } 
  Baidu  { 
    manifestPlaceholders = [UMENG_CHANNEL_VALUE: "Baidu"] 
  }
  qq  { 
    manifestPlaceholders = [UMENG_CHANNEL_VALUE: "qq"] 
  } 
  360  { 
    manifestPlaceholders = [UMENG_CHANNEL_VALUE: "360"] 
  }
}