美文网首页MobDevGroupAndroid那些事gradle的使用
Android常用的Gradle配置和加速编译

Android常用的Gradle配置和加速编译

作者: Yat3s | 来源:发表于2016-04-13 15:23 被阅读2708次

    Why Gradle

    Gradle makes the impossible possible, the possible easy and the easy elegant.

    在Android开发中经常会用Gradle来构建项目,Gradle能很方便的项目的版本集成和打包,虽Gradle官方已经给出很详细的文档了,但还是有必要抽离出一些常用的配置。

    整个Android项目的编译依赖于Gradle的编译,虽然前几天发布了Android 2.0 stable,拥有了Instant Run强大的功能,但是有“改一行布局代码Run一次”习惯的同学有必要知道如何加速Gradle的编译。

    主要介绍一下:

    • 如何配置Gradle
    • 如何加速Gradle的编译
    • 一些常用的项目构建知识

    How Gradle

    gradle.properties文件适合配置IDE的属性,当然也适合配置你在项目的关键/敏感参数,因为它将运行在Incubating parallel mode(

    孵化并行模式,应该解释为运行时候嵌入在项目当中),也是属于默认的gitignore,这样你的敏感信息(key账号密码,appkey等)就不会被push到git了,你需要注意的是** 属性中有中文的话,记得转成unicode来显示,不然可能引发一些莫名的错误 **

    类似于这样,你可以把你的签名keystore的信息,服务端的endpoint,第三方服务的appkey,ide的配置信息放在这。

      KEY_ALIAS=yat3s
      KEYSTORE_PASSWORD=123456
      KEY_PASSWORD=123456
      umeng_appkey_product=adcdefghijk
      umeng_appkey_dev=adcdefghijk
      deepshare_appid=adcdefghijk
      bugly_appid_product=adcdefghijk
      bugly_appid_dev=adcdefghijk
      rong_appkey=adcdefghijk
      endpoint_product=http://api.yat3s.com
      endpoint_dev=http://api.yat3s.com:9000
      app_name=\u006f\u0070\u0065\u006e\u5f00\u8154`
    

    ** 那么配置好这些信息如何在Gradle和Java中使用呢?**

    其实Groovy语言和Java很接近也很好用,往下看↓

    buildTypes

      buildTypes {
       release {
           minifyEnabled true
           shrinkResources true
           signingConfig signingConfigs.release
           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
           buildConfigField "String", "ENDPOINT", "\"${endpoint_product}\""
           resValue "string", "umeng_appkey", "${umeng_appkey_product}"
           resValue "string", "deepshare_appid", "${deepshare_appid}"
           resValue "string", "bugly_appid", "${bugly_appid_product}"
           resValue "string", "rong_appkey", "${rong_appkey}"
           resValue "string", "channel", "product"
           resValue "string", "op_app_name", "${app_name}"
       }
       debug {
           signingConfig signingConfigs.release
           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
           buildConfigField "String", "ENDPOINT", "\"${endpoint_product}\""
           resValue "string", "umeng_appkey", "${umeng_appkey_dev}"
           resValue "string", "deepshare_appid", "${deepshare_appid}"
           resValue "string", "bugly_appid", "${bugly_appid_dev}"
           resValue "string", "rong_appkey", "${rong_appkey}"
           resValue "string", "channel", "dev"
           resValue "string", "op_app_name", "dev_${app_name}"
         }
       }
    
    • 引用gradle.properties 只需要加 ${key}
    • 定义在 buildConfigField "String", "ENDPOINT",""${endpoint_product}"",在Java享用时候只需要读取BuildConfig.ENDPOINT即可
    • 定义在 resValue "string", "umeng_appkey", "${umeng_appkey_product}" , 在xml中享用只需要R.string.umeng_appkey即可

    记住几个点:

    • 在你确保你minifyEnabled(混淆)没问题的时候debug模式尽量别开minifyEnabled和shrinkResources,这样会大大降低编译速度
    • 如果真要开混淆,把生成mapping关掉
    • 尽量把一些第三方key分成staging和product,这样容错性高,避免你推送给你的用户一个"Test"
    • 定义buildConfigField时候注意双引号的问题 ""${endpoint_product}""
    • 如果不方便管理签名可debug和release同一个签名。** 如果需要同时安装debug和release包只需要修改applicationId即可 **

    defaultConfig

    这里尽量新建一个Conifg.gradle文件来统一管理这些基本配置(在project下右键new file即可) ,

    Config.gradle文件如下

    ext {
    android = [compileSdkVersion: 23,
               buildToolsVersion: "23.0.1",
               applicationId    : "com.yat3s.d3v",
               minSdkVersion    : 15,
               targetSdkVersion : 23,
               versionCode      : 36,
               versionName      : "1.3.1"]
    }
    dependencies = [
                   design              : "com.android.support:design:23.3.0",
                   nineoldandroids     : "com.nineoldandroids:library:2.4.0",
                   retrofit            : "com.squareup.retrofit:retrofit:2.0.0",
                   rxandroid           : "io.reactivex:rxandroid:1.0.0",
                   okhttp-urlconnection: "com.squareup.okhttp:okhttp-urlconnection:2.0.0",
                   okhttp              : "com.squareup.okhttp:okhttp:2.0.0",
                   butterknife         : "com.jakewharton:butterknife:7.0.1"] 
    

    然后在项目的build.gradle文件下添加 ** apply from: "config.gradle" **

      apply from: "config.gradle"
      buildscript {
          repositories {
            jcenter()
          }
          dependencies {
            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 {
          repositories {
            jcenter()
          }
        }       ``
    

    然后你使用起来就简单多了

    def config = rootProject.ext.android // 配置
    def dep = rootProject.ext.dependencies // 依赖
    useLibrary 'org.apache.http.legacy' // 如果你是api23的话,在用到apache的http库时候,记得加上这个,不然混淆时候因为urlConnection的升级导致异常
    compileSdkVersion config.compileSdkVersion
    buildToolsVersion config.buildToolsVersion
    defaultConfig {
      applicationId config.applicationId
      minSdkVersion config.minSdkVersion
      targetSdkVersion config.targetSdkVersion
      versionCode config.versionCode
      versionName config.versionName
      manifestPlaceholders = [UMENG_CHANNEL_VALUE: "open"]
    }
    
    dependencies {
      compile fileTree(dir: 'libs', include: ['*.jar'])
      compile dep.design
      compile dep.retrofit
      compile dep.okhttp
    }
    

    那么这样用的好处是什么呢?

    • 如果你有多个Module都需要依赖design包的话,design包一升级导致了多个module需要改版本,你就可能同时引入了多个依赖导致apk增大
    • 统一管理versionCode和versionName
    • 看起来是不是更简洁

    signingConfigs

    def keystore = file('keystore/yat3s.jks')
    signingConfigs {
        release {
            keyAlias KEY_ALIAS
            keyPassword KEY_PASSWORD
            storePassword KEYSTORE_PASSWORD
            storeFile keystore
        }
    }    
    
    • 在非资源定义(下面有提到的resValue为资源定义,其他的地方则为非资源定义)下引用gradle.properties可直接输入properties的key
    • 如果你要公开你的源码记得把你的keystore ignore掉,私有库无视该项

    packagingOptions

    packagingOptions {
      exclude 'META-INF/LICENSE'
      exclude('META-INF/LICENSE.txt')
      exclude('META-INF/NOTICE.txt')
      exclude 'META-INF/NOTICE'
      exclude 'META-INF/DEPENDENCIES'
      // umeng推送的jar包含有的okio库跟okhttp的okio库冲突
      exclude 'META-INF/maven/com.squareup.okio/okio/pom.xml'
      exclude 'META-INF/maven/com.squareup.okio/okio/pom.properties'
    }
    
    • 主要处理第三方库在导入时候的一些声明文件冲突,Option this

    compileOptions {

    compileOptions {
      sourceCompatibility JavaVersion.VERSION_1_7
      targetCompatibility JavaVersion.VERSION_1_7
    }
    
    • 个人觉得如果你不用lambda表达式的话,可以用java1.7,因为1.8还不是完美的支持Android

    lintOptions

     lintOptions {
        disable "InvalidPackage"
        disable "MissingTranslation" // 禁用中英文string.xml的强制lint
        lintConfig file("lint.xml")
    }
    

    打包管理

    def getDate() {
        return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC")) // 注意时区
    }     
    
    // 获取当前git的Revision
    def getRevision() {
        return ext.hash = 'git rev-parse --short HEAD'.execute().text.trim()
    }
    productFlavors {
       rc_open {}
       rc_360 {}
       rc_yingyongbao {}
       rc_baidu {}
       rc_91 {}
       rc_wandoujia {}
       rc_anzhuo {}
       rc_xiaomi {}
       rc_meizu {}
       rc_oppo {}
       rc_huawei {}
       rc_weibo {}
       rc_dev {}
       }
    productFlavors.all { flavor ->
        // 这里只是方便友盟统计每个渠道的数据
        flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
     }
    
      // 修改打包后APK的文件名
      applicationVariants.all { variant ->
       variant.outputs.each { output ->
           def oldFile = output.outputFile
           if (variant.buildType.name.equals('release')) {
               // 输出apk名称为yat3s_v1.0_2016-04-12_yingyongbao_a23f2e1.apk
               def releaseApkName = 'yat3s-v' + defaultConfig.versionName + '_' + getDate() + '_' + variant.productFlavors[0].name + "_" + getRevision() + '.apk'
               output.outputFile = new File(oldFile.parent, releaseApkName)
           }
           if (variant.buildType.name.equals('debug')) {
              // Do nothing
           }
       }
     }
    
    • 多渠道打包用的是Gradle的productFlavors
    • git的Revision 方便你管理你的release和tag
    • 打包的时候可以用Jenkins来自动Build你的包

    Speed Gradle

    我们都知道编译项目时候是依赖gradle的,gradle的构建速度决定了你的工作效率,上面零散的提到几点,下面总结一下:

    • 第一个大招 升级Android studio 2.0 并且使用最新版的gradle 2.0 使用Instant Run
    • 在你确保你的混淆没问题时候在debug模式下关闭所有混淆minifyEnabled false, shrinkResources false
    • 尽可能的让你的所有module的依赖库版本一致。
    • 在允许的情况下,在android studio的配置中,开启offline模式,在构建项目时候在命令后面加上--daemon --parallel --offline即可
    • 在添加依赖的时候尽量明确版本号,省去gradle查找最新版的时间,不要compile 'com.facebook.fresco:fresco:latest' 或compile 'com.facebook.fresco:fresco:1.+',
    • 开启守护线程并行编译,在gradle.properties中添加
      org.gradle.parallel=true
      org.gradle.daemon=true
      org.gradle.jvmargs=-Xms256m -Xmx1024m
    • 在gradle中def方法的时候尽量在debug情况下减少耗时操作或者不操作,比如:
      def getRevision() {
      if (!System.getenv('CI_BUILD')) {
      return 0
      }
      return ext.hash = 'git rev-parse --short HEAD'.execute().text.trim()
      }

    写在最后

    如果那里写的不对或者有更好的方式,欢迎指出批评,感恩。欢迎关注~

    相关文章

      网友评论

      • Tang1024:写的很赞!!!
      • MidTse:什么是“非资源定义下引用gradle.properties”呢?
        Yat3s:@BoyceTse 恩,完全可以的。但是你需要考虑的是是否合适,就好比在Java中可以定义String,在values也可以定义String,那在哪里定义才符合语义呢?
        MidTse:@Yat3s 比如我想把versionName变成gradle.properties中的key声明,也是可直接输入gradle.properties的key对么?
        Yat3s:@BoyceTse 资源定义指的是这些:buildConfigField "String", "ENDPOINT", "\"${endpoint_product}\""
        resValue "string", "umeng_appkey", "${umeng_appkey_dev}",当然我觉得我的说明不是很明确,谢谢指出。
      • Yat3s:补充一点,Speed gradle可以开启Offline模式

      本文标题:Android常用的Gradle配置和加速编译

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