Gradle for Android 问题总结

作者: Jinwong | 来源:发表于2016-04-17 02:05 被阅读5751次

    Gradle是什么?

    Gradle 是以Groovy为基础,面向java应用,基于DSL语法的自动化构建工具。是google引入,替换ant和maven的新工具,其依赖兼容maven和ivy。

    使用gradle的目的:
    更容易重用资源和代码;
    可以更容易创建不同的版本的程序,多个类型的apk包;
    更容易配置,扩展;
    更好的IDE集成;

    首先明确gradle跟maven一样,也有一个配置文件,maven里面是叫pom.xml,而在gradle中是叫build.gradle。Android Studio中的android项目通常至少包含两个build.gradle文件,一个是project范围的,另一个是module范围的,由于一个project可以有多个module,所以每个module下都会对应一个build.gradle。这么说有点抽象,看下面这个图:


    1. Project中build.gradle

    project下的build.gradle是基于整个project的配置,主要配置gradle 版本及 全局依赖仓库、库或者其他全部参数。
    android studio 现在重要仓库采用jcenter(),之前版本放在mavenCentral。
    另外有时还没有加入jcenter()仓库的第三方库,也需要在这里配置他们的库地址。
    需要在这里配置,才能将第三方库拉下来

    buildscript {
       //构建过程依赖的仓库
      repositories {
          jcenter()
      }
      
      //构建过程需要依赖的库
      dependencies {
          //声明的是gradle插件的版本
          classpath 'com.android.tools.build:gradle:2.0.0'
    
          // NOTE: Do not place your application dependencies here; they belong
          // in the individual module build.gradle files
      }
    }
    
    allprojects {
      //这里面配置整个项目依赖的仓库,这样每个module就不用配置仓库了
      repositories {
          jcenter()
    
          maven {
              // LeanCloud 的包仓库
              url "http://mvn.leancloud.cn/nexus/content/repositories/releases"
          }
      }
    }
    
    //配置全局变量
    ext {
      // module依赖库公共版本号
      SupportXVersion = '23.2.0'
      GsonVersion = '2.6.2'
      LeanCloudVersion = 'v3.13.4'
      JunitVersion = '4.12'
      
      compileSdkVersion = 22
      buildToolsVersion = "23.0.1"
      minSdkVersion = 10
      targetSdkVersion = 22
      versionCode = 34
      versionName = "v2.6.1"
    }
    

    注:大家可能很奇怪,为什么仓库repositories需要声明两次,这其实是由于它们作用不同,buildscript中的仓库是gradle脚本自身需要的资源,而allprojects下的仓库是项目所有模块需要的资源

    2. module中build.gradle

    //申明使用插件,表明要编译的内容和产物,
    //com.android.application 表明该module 为android 应用
    //com.android.library 表明为library库
    //java 表名是java库
    apply plugin: 'com.android.application'
    
    //安卓构建过程需要配置的参数
    android {
        //编译SDK的版本
        compileSdkVersion COMPILE_SDK_VERSION as int
        //buildtool 的版本
        buildToolsVersion BUILD_TOOLS_VERSION
        
        /默认配置,会同时应用到debug和release版本上
        defaultConfig {
            //应用包名
            applicationId APPLICATION_ID
            //支持最小android sdk 版本
            minSdkVersion MIN_SDK_VERSION as int
            // 目标版本
            targetSdkVersion TARGET_SDK_VERSION as int
            //应用版本号
            versionCode VERSION_CODE as int
            //应用版本名称
            versionName VERSION_NAME
            
            /// 配置生成的 BuildConfig 文件中的常量,代码引用直接 
            buildConfigField "String", "LOG_TAG", LOG_TAG // 日志tag
            buildConfigField "String", "LOG_HTTP_TAG", LOG_TAG_HTTP // http日志tag
            buildConfigField "String", "LOG_WEB_TAG", LOG_TAG_WEB // web日志tag
    
          // 默认是UMENG_CHANNEL_VALUE为umeng
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
            
          // dex突破65535的限制
            multiDexEnabled true
        }
        
        //java版本号
        compileOptions{
            sourceCompatibility JavaVersion.VERSION_1_7
            targetCompatibility JavaVersion.VERSION_1_7
        }
        
        //签名
        signingConfigs {
            release {
                //签名文件
                storeFile file(STORE_FILE)
                storePassword STORE_PASSWORD
                keyAlias KEY_ALIAS
                keyPassword KEY_PASSWORD
            }
        }
        // 为了解决部分第三方库重复打包了META-INF的问题
        packagingOptions {
            exclude 'META-INF/LICENSE.txt'
            exclude 'META-INF/NOTICE.txt'
        }
        
        //移除lint检测的error
        lintOptions {
            abortOnError false
        }
        
        //编译类型
        //其中debug, release是gradle默认自带的两个build type, 当然你可以定义其他类型。
        //可以针对不停编译的版本中配置不同的参数,比如混淆、签名等。preview
        buildTypes {
            debug {
                minifyEnabled false
                zipAlignEnabled false
                shrinkResources false
            }
            
             preview {
                debuggable false // 是否保留调试信息
                minifyEnabled true  //是否混淆
                zipAlignEnabled true // 包优化
                shrinkResources true // 移除不必要的资源
    
                // 签名
                signingConfig signingConfigs.release
                // 代码混淆规则文件
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
            
            release {
                //加上后缀
                applicationIdSuffix ".release"
                minifyEnabled true //是否混淆
                zipAlignEnabled true // zip对齐优化
                shrinkResources true // 移除不必要的资源
                
                // 不显示Log
                buildConfigField "boolean", "LOG_DEBUG", "false"
    
                // 签名
                signingConfig signingConfigs.release
                //混淆文件的位置
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        // 多渠道
        productFlavors {
            //可以设置不同渠道渠道号,应用名称
            dev { // 开发
                buildConfigField "String", "CHANNEL_NUMBER", '"11111"'
            }
            '360' {
                buildConfigField "String", "CHANNEL_NUMBER", '"11112"'
            }
            GooglePlay {
                buildConfigField "String", "CHANNEL_NUMBER", "11113"'
    
        }
        // 多渠道批量替换
        productFlavors.all { flavor ->
             //批量修改Manifest占位符替换
            //在Manifest使用`${UMENG_CHANNEL_VALUE}`,`LEANCLOUD_CHANNEL_VALUE`,打包时将替换成渠道名,例如UMENG_CHANNEL_VALUE="dev";
            flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name, LEANCLOUD_CHANNEL_VALUE: name]
            // Project Properties->_myAPPBuildVersionName,用于程序集成下命令行修改
            if (project.hasProperty('_myAPPBuildVersionName')) {
                defaultConfig.versionName = _myAPPBuildVersionName
            }
        }
         
         //定义变量
         //gradle 可以用def定义一些值例如:def KeyPassword = "123123" 
         def releaseTime() {
             return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
         }
    
        
        // 批量打包
        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                def outputFile = output.outputFile
                if (outputFile != null && outputFile.name.endsWith('.apk')) {
                    def fileName
                    if (variant.buildType.name.equals('release')) {
                        // myAPP_v版本号_渠道名.apk
                        fileName = "myAPP_v${variant.versionName}_${variant.productFlavors[0].name}.apk"
                    } else {
                        // myAPP_v版本号_渠道名_时间_编译类型名.apk
                        fileName = "myAPP_v${variant.versionName}_${variant.productFlavors[0].name}_${releaseTime()}_${variant.buildType.name}.apk"
                    }
                    output.outputFile = new File(outputFile.parent + "/${variant.buildType.name}", fileName)
                }
            }
        }
    
    
    }
    
    //依赖第三方库
    dependencies {
       //编译libs目录下所以jar包
       //compile files('libs/xxx.jar')   导入一个特定jar包
        compile fileTree(dir: 'libs', include: ['*.jar'])//导入所有的jar包
        compile project(':core')
        compile 'com.android.support:appcompat-v7:23.1.1'
        compile 'com.android.support:design:23.1.1'
        compile 'com.android.support:recyclerview-v7:23.1.1'
        compile 'com.android.support:cardview-v7:23.1.1'
    }
    

    注意:

    • buildToolsVersion这个需要你本地安装该版本才行,很多人导入新的第三方库,失败的原因之一是build version的版本不对,这个可以手动更改成你本地已有的版本或者打开 SDK Manager 去下载对应版本。
    • proguardFiles这部分有两段,前一部分代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在 /tools/proguard/proguard-android.txt , 后一部分是我们项目里的自定义的混淆文件,目录就在 app/proguard-rules.pro ,在这个文件里你可以声明一些第三方依赖的一些混淆规则,最终混淆的结果是这两部分文件共同作用的。
    • 一般重要的信息,例如签名信息,可以直接将信息写到gradle.properties,然后在然后在build.gradle中引用即可。
    • 多渠道的关键在于定义不同的product flavor。
      注意:这里的flavor名如果是数字开头,必须用引号引起来。
    • buildTypes是指建构的类型,一般只用两种默认类型 debug 和 release ,顾名思义 debug 用来配置开发过程中的一些内容;release 用来配置正式发布版本的内容。有时我们需要发布介于debug与release之间的preview 版本。

    3. Project中setting.gradle

    这个文件是全局的项目配置文件,里面主要声明Project中所包括的所有module,

    //一个Project中所包括的所有module
    include ':app', ':model',':lib', ':core'
    

    4. Project中gradle.properties

    gradle.properties为gradle的配置文件,里面可以定义一些常量供build.gradle使用,比如可以配置签名相关信息如keystore位置,密码,keyalias等,build.gradle就可以直接引用
    gradle 中的一些配置参数建议写到gradle.properties

    //编译版本信息
    APPLICATION_ID = com.jin.myAPP
    COMPILE_SDK_VERSION = 23
    BUILD_TOOLS_VERSION = 23.0.1
    MIN_SDK_VERSION = 15
    TARGET_SDK_VERSION = 1
    VERSION_CODE = 1
    VERSION_NAME = 1.0.0.0
    
    //keystore信息
    STORE_FILE = ../app/mykey.keystore
    STORE_PASSWORD = your password
    KEY_ALIAS = your alias
    KEY_PASSWORD = your password
    

    5. ext配置全局参数

    project的build.gradle中的ext可以为各位module进行全局配置参数,防止各个module之间的不统一,不可控。而且当我们升级sdk、build tool、target sdk等,几个module都要更改,非常的麻烦。

    ext {
       compileSdkVersion = 22
       buildToolsVersion = "23.0.1"
       minSdkVersion = 10
       targetSdkVersion = 22
       versionCode = 34
       versionName = "v2.6.1"
    }
    

    然后在各自module的build.gradle中引用:

    android {
    
      compileSdkVersion rootProject.ext.compileSdkVersion
    
      buildToolsVersion rootProject.ext.buildToolsVersion
    
     defaultConfig {
    
        applicationId "com.xxx.xxx"
    
        minSdkVersion rootProject.ext.minSdkVersion
    
        targetSdkVersion rootProject.ext.targetSdkVersion
    
        versionCode rootProject.ext.versionCode
    
        versionName rootProject.ext.versionName
      }
    }
    

    6. resValue 定义资源。

    例如resValue "string" 就是字符串资源,可以用R.String 来引用对应的字符串资源

    android {
       defaultConfig {
           resValue "string", "build_time", buildTime()
           resValue "string", "build_host", hostName()
           resValue "string", "build_revision", revision()
        }
    }
    
    def buildTime() {
       return new Date().format("yyyy-MM-dd HH:mm:ss")
    }
    
    def hostName() {
       return System.getProperty("user.name") + "@" +InetAddress.localHost.hostName
     }
    
    def revision() {
      def code = new ByteArrayOutputStream()
         exec {
            commandLine 'git', 'rev-parse', '--short', 'HEAD'
            standardOutput = code
          }
      return code.toString()
      }
    

    上述代码实现了动态的添加了3个字符串资源: build_time、build_host、build_revision, 然后在其他地方可像如引用字符串一样使用如下:

    // 在Activity里调用
    getString(R.string.build_time) // 输出2015-11-07 17:01
    getString(R.string.build_host) // 输出jay@deepin,这是我的电脑的用户名和PC名
    getString(R.string.build_revision) // 输出3dd5823, 这是最后一次commit的sha值
    

    7. PlaceHolder

    manifest的一些值我们可以用PlaceHolder处理。例如

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

    在gradle中改成

     manifestPlaceholders = [UMENG_CHANNEL_VALUE: "360"]
    

    可以放在defaultConfig中设置默认值,或者放在productFlavors中根据不同渠道修改成不同值。或者批量修改

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

    8. 导入某个project

    如果你的app是多模块的,假设有两个模块app和lib,并且app模块是依赖lib的,这时候我们就需要在app模块的build.gradle中的dependencies结点下配置依赖:

    compile project(':lib')
    

    并且你需要在settings.gradle中把lib模块包含进来:

    include ':lib',':app'
    

    此外,这种情况下lib模块是作为库存在的,因而它的build.gradle中的插件声明通常应该是这样的:

    apply plugin: 'com.android.library'
    

    而且,作为library的模块lib的build.gradle文件的defaultConfig中是不允许声明applicationId的,这点需要注意。

    9. 引用本地aar:

    • 首先将你的库通过android studio 运行打包成aar文件。运行后在/build/output/aar文件夹中。
    • 首先将aar文件放到模块的libs目录下,然后在该模块的build.gradle中声明flat仓库:
    repositories{
         flatDir {
            dirs 'libs'
        }
    }
    
    • 最后在dependencies结点下依赖该aar模块:
    dependencies{
        compile (name:'xxx',ext:'aar')
    }
    

    10. BuildConfig

    在build.gradle中配置buildConfigField参数,编译后会在..\app\build\generated\source\buildConfig文件夹下会自动生成对应版本对应module的BuildConfig.java。BuildConfig就会包含对应版本的配置信息。程序中可以直接引用这些数据。例如BuildConfig.DEBUG。

    public final class BuildConfig {
     public static final boolean DEBUG = Boolean.parseBoolean("true");
     public static final String BUILD_TYPE = "debug";
     public static final String FLAVOR = "360";
     public static final int VERSION_CODE = 45;
     public static final String VERSION_NAME = "3.2.0.0";
     // Fields from build type: debug
     public static final String HOST_IMG_SERVER = "img";
     public static final String HOST_SERVER = "test";
     // Fields from product flavor: 360
     public static final String CHANNEL_NUMBER = "232100";
    }
    
    

    11. module 调整目录结构sourceSets

    默认情况下,java文件和resource文件分别在src/main/java和src/main/res目录下,在build.gradle文件,andorid{}里面添加下面的代码,便可以将java文件和resource文件放到src/java和src/resources目录下。

    sourceSets {
       main {
          java {
              srcDir 'src/java'
          }
          resources {
            srcDir 'src/resources'
         }
       }
    }
    

    更简便的写法是:

    sourceSets {
       min.java.srcDirs = ['src/java']
       min.resources.srcDirs = ['src/resources']
    }
    

    12. Gradle常用命令

    上面大家接触了一些命令如 ./gradlew -v ./gradlew clean ./gradlew build, 这里注意是./gradlew, ./代表当前目录,gradlew代表 gradle wrapper,意思是gradle的一层包装,大家可以理解为在这个项目本地就封装了gradle,即gradle wrapper, myAPP/gradle/wrapper/gralde-wrapper.properties**文件中声明了它指向的目录和版本。只要下载成功即可用grdlew wrapper的命令代替全局的gradle命令。

    理解了gradle wrapper的概念,下面一些常用命令也就容易理解了。

    • ./gradlew 下载更新gradle
    • ./gradlew -v 版本号
    • ./gradlew assemble 构建项目输出
    • ./gradlew check 运行检测和测试任务
    • ./gradlew clean 清除9GAG/app目录下的build文件夹
    • ./gradlew build 运行check和assemble,检查依赖并编译打包
      这里注意的是 ./gradlew build 命令把debug、release环境的包都打出来,如果正式发布只需要打Release的包,该怎么办呢,下面介绍一个很有用的命令 assemble<build type="" name="">, 如</build>
    • ./gradlew assembleDebug 编译并打Debug包
    • ./gradlew assembleRelease 编译并打Release的包所有渠道的
    • ./gradlew assembleWandoujiaRelease 编译并打包豌豆荚的Release版本
    • ./gradlew assembleWandoujia 编译并打包豌豆荚的所有版本
    • ./gradlew installRelease Release模式打包并安装
    • ./gradlew uninstallRelease 卸载Release模式包

    相关文章

      网友评论

      • King4Ghost:问个2个问题?
        1. *.iml是干嘛的?能删掉吗?从svn上check out下时,提示
        Do you want to schedule the following file for addition to Subversion?
        C:\....\xxx.iml

        选择YES or NO有啥区别?

        2. 我从eclipse导入到AS,为什么没有gradle.properties这个文件?如果是通过AS直接创建一个Android Project的话,就会有这个文件?

      本文标题:Gradle for Android 问题总结

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