Android常用的Gradle配置和加速編譯

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()
    }

寫在最後

如果那裏寫的不對或者有更好的方式,歡迎指出批評,感恩。歡迎關注~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章