Android 重構 | 持續優化統一管理 Gradle

javascript

101
php

次推文
html


LZ-Saysjava



我介意着你的不介意。android



前言web


藉着韓哥哥要求重構的機會,正好好好回顧下之前遺忘/忽略的知識點。shell

記錄下有關 Gradle 優化之路:json



大概的方向或者說最終目標精簡後以下:


一次引用,全文(項目)使用,避免團隊協做引入重複依賴;

自帶依賴更新提示;

支持跳轉等常規操做。


最重要的,依然是便於維護。


從最初的建立 config.gradle 到如今的 basic_depend.gradle,雖然說今天更比昨天強,可是依然不是很滿意。


ext 方式雖然是 Google 官方目前推薦,而且當前一些主流庫也採用此種方式,實際使用起來,我的仍是有部分不方便。好比說不支持跳轉,不支持更新等等,人吶,總想獲得更多。


在查閱了多個文檔後,再次準備優化/升級一波,繼續讓韓總蒙圈。 


1、buildSrc 搞起來


將官方的描述用 Google 翻譯了一遍,以下:


複雜的構建邏輯一般很適合做爲自定義任務或二進制插件進行封裝。自定義任務和插件實現不該存在於構建腳本中。buildSrc 只要不須要在多個獨立項目之間共享代碼,就能夠很是方便地使用該代碼。


該目錄 buildSrc 被視爲包含的構建。發現目錄後,Gradle 會自動編譯並測試此代碼,並將其放入構建腳本的類路徑中。對於多項目構建,只能有一個 buildSrc 目錄,該目錄必須位於根項目目錄中。buildSrc 應該比腳本插件更可取,由於它更易於維護,重構和測試代碼。


buildSrc 使用適用於 Java 和 Groovy 項目的相同源代碼約定。它還提供對 Gradle API 的直接訪問。

Google Develop


思索許久,我的簡單總結下:


  • buildSrc 存在於 Gradle 編譯期;

  • 一樣 buildSrc 支持(單獨項目)共享代碼,例如一個項目中多個 module 均可以直接調用。


buildSrc 實踐


描述下操做步驟:


  • 在項目根目錄下建立 buildSrc 目錄,隨後新建 build.gradle.kts 文件;

  • 建立 src 目錄,以及對應管理版本文件;

  • 替換直接使用原有依賴


build.gradle.kts 內容以下: 


// 導入 Kotlin 插件import org.gradle.kotlin.dsl.`kotlin-dsl`
plugins { `kotlin-dsl`}
repositories { jcenter()}/** * 禁用測試報告(Gradle 默認會自動建立測試報告) */tasks.withType<Test> { reports.html.isEnabled = false reports.junitXml.isEnabled = false}/** * isFork:將編譯器做爲單獨的進程運行。 * 該過程在構建期間將被重用,所以分叉開銷很小。分叉的好處是,內存密集型編譯是在不一樣的過程當中進行的,從而致使主 Gradle 守護程序中的垃圾回收量大大減小。 * 守護程序中較少的垃圾收集意味着 Gradle 的基礎架構能夠運行得更快,尤爲是在您還使用的狀況下 --parallel。 * * isIncremental:增量編譯。Gradle 能夠分析直至單個類級別的依賴關係,以便僅從新編譯受更改影響的類。自 Gradle 4.10 起,增量編譯是默認設置。 */tasks.withType<JavaCompile> { options.isFork = true options.isIncremental = true}/** * 禁用關於使用實驗性 Kotlin 編譯器功能的警告 */kotlinDslPluginOptions { experimentalWarning.set(false)}


Dependencies.kt,這是我定義的版本管理的文件,部份內容以下:


@file:Suppress("SpellCheckingInspection")
/** * @author HLQ_Struggle * @date 2020/7/27 * @desc 統一管理類 */
// 統一管理項目中的版本信息object Versions { // Build Config const val compileSDK = 29 // 編譯 SDK 版本 const val buildTools = "29.0.3" // Gradle 編譯項目工具版本 const val minSDK = 23 // 最低兼容 Android 版本    const val targetSDK = 29        // 最高兼容 Android 版本
// App Version const val appVersionCode = 1 // 當前版本編號    const val appVersionName = "1.0"       // 當前版本信息
// 。。。}
// 統一管理項目中使用的依賴庫object Deps {
// Gradle    const val androidGradle = "com.android.tools.build:gradle:${Versions.androidGradlePlugin}"
// Kotlin const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" const val kotlinxCoroutines =        "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.kotlinxCoroutines}" // ...}


舉個兩個栗子,如何使用:


  • 根目錄下 build 如何使用:


直接經過在 Dependencies 文件中定義的分組名去獲取對應的屬性便可,以下所示:


buildscript { // ... dependencies {        classpath Deps.androidGradle
        classpath Deps.kotlinGradlePlugin
// NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}// ...


  • 其它 module 目錄下 build 如何使用:


同理,固然也能夠採用直接倒入整個對應分組方式,直接使用對應屬性,例如:


// 這裏採用直接倒入定義的 Deps 以及 Versions 分組方式import static Deps.*import static Versions.*
apply plugin: 'com.android.library'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'
android {
// 這裏就能夠直接使用對應的屬性 compileSdkVersion compileSDK buildToolsVersion buildTools
defaultConfig { minSdkVersion minSDK targetSdkVersion targetSDK versionCode appVersionCode versionName appVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' } // ...}
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // 同理,這裏也是同樣,直接使用對應的屬性名便可 implementation kotlinStdLib implementation appcompat implementation coreKtx api 'com.google.android.material:material:1.2.0' testImplementation junit androidTestImplementation extJunit androidTestImplementation espresso
api mmkv api 'com.airbnb.android:lottie:3.4.1'}


這種方式比較有好的幾個特色以下:


  • 支持跳轉;

  • 支持智能提示;

  • Gradle 編譯時介入,感腳很溼高大上


可是關鍵的更新提示呢?


ummm,不開森。


加個 gif 配圖吧~



手動編寫 buildSrc 須要注意:


  • 目錄結構:例如:buildSrc/src/main/kotlin(java)

  • 在 build.gradle.kts 中添加 jcenter(),不然 kotlin-dsl 加載失敗


2、refreshVersions 使用(2020/09/15)


網上搜到關於 refreshVersions 的描述,以爲蠻合適,嘗試一波。


大概的優點在於如下幾點:


  • 集中管理依賴

  • 以最小成本提示依賴升級


操做步驟以下:


Step 1:修改 settings.gradle 文件

 

// settings.gradle.ktsimport de.fayard.refreshVersions.RefreshVersionsSetup
// Here you might have some pluginManagement block:pluginManagement { //...}
buildscript { repositories { gradlePluginPortal() } dependencies.classpath("de.fayard.refreshVersions:refreshVersions:0.9.5")}
rootProject.name = 'Your Android Project Name'include ':app'include ':helper'include ':weight'// include other module
RefreshVersionsSetup.bootstrap(settings)


Step 2:同步後執行命令


./gradlew migrateToRefreshVersionsDependenciesConstants --console=plain


根據提示進行依賴替換:



隨後生成 versions.properties 文件:


## suppress inspection "SpellCheckingInspection" for whole file## suppress inspection "UnusedProperty" for whole file#### Dependencies and Plugin versions with their available updates## Generated by $ ./gradlew refreshVersions## Please, don't put extra comments in that file yet, keeping them is not supported yet.
version.androidx.appcompat=1.1.0## # available=1.2.0-alpha01## # available=1.2.0-alpha02## # available=1.2.0-alpha03## # available=1.2.0-beta01## # available=1.2.0-rc01## # available=1.2.0-rc02## # available=1.2.0## # available=1.3.0-alpha01## # available=1.3.0-alpha02
## 。。。


有一點以爲不舒服的地方是,它內置了 Android 一部分的依賴,而對於咱們實際開發中使用其它依賴,則顯示不太友好了,以下圖:



研究好一段時間,各類蒙圈,實際的效果仍是不是太滿意,若是能在 buildSrc 的基礎上新增版本更新就更好了。


3、buildSrc 結合 task(2020/09/17)


不得不說,掘金大佬不少,很友善,這不,沉璧浮光cbfg 大佬教我一招~

文末已附上連接,感興趣的小夥伴能夠直接拉到底自行學習~


我簡單總結下大佬的實踐思路:


  • 新建 versions.gradle 用於存放依賴/插件配置,在這裏支持依賴更新/提示;

  • 新建 updateDependencies.gradle task,用於將更新後的依賴/插件同步 groovy;

  • 使用直接調用 groovy 便可。


Step 1:在項目根目錄下建立 buildSrc 目錄


Step 2:新建 version.gradle 依賴/插件管理


大佬在日誌中以及寫的很明確了,這裏我單獨說下我期間遇到的坑,或者是重點吧,讓看到此文的小夥伴更快的上手。


  • version 之間是一些版本的配置 , 解析後會放到 Dependencies.kt 的 object Versions 中,必須存在,以下: 

/*<version>*/  <--- 必須存在// 對應的版本信息def compileSDK = 29 // 編譯 SDK 版本def buildTools = "29.0.3" // Gradle 編譯項目工具版本def minSDK = 23 // 最低兼容 Android 版本def targetSDK = 29        // 最高兼容 Android 版本/*</version>*/  <--- 必須存在


  • dep 之間是插件/依賴庫引用路徑,解析後會放到 Dependencies.kt 的 object Deps 中,一樣必須存在,以下:


    /*<dep>*/  <--- 必須存在
    // gradlePluginimplementation "com.android.tools.build:gradle:4.0.1"
    // permissionsDispatcher:Android 6.0 動態權限管理implementation "org.permissionsdispatcher:permissionsdispatcher:4.7.0"
    /*</dep>*/  <--- 必須存在


這裏須要注意下,// 後第一位表明你在使用中調用的名稱,:後表明對當前依賴的描述。


完整的 version.gradle 內容以下(篇幅有限,移除部分項目中使用依賴):


dependencies { /* readme * * * 爲了統一管理插件/依賴庫版本,避免版本衝突,統一將插件/依賴庫信息配置在此文件中, * 經過gradlew updateDependencies task * 解析此文件生成對應內容到Dependencies.kt中進行統一引用 * * <version> </version> 之間是一些版本的配置 , 解析後會放到Dependencies.kt的object Versions中 * * <dep> </dep> 之間是插件/依賴庫引用路徑 , 解析後會放到Dependencies.kt的object Deps中 * * 配置插件/依賴庫引用說明: * 0、版本配置格式:def <name> = <value> * 一、配置插件/依賴庫引用路徑前備註格式://<插件/依賴庫名> : <備註>,這個部分會被解析肯定插件/依賴庫引用名稱 * 二、配置插件/依賴庫引用路徑時以 implementation 做爲開頭 * 三、更新配置後執行 updateDependencies.gradle 的 updateDependencies task 同步更新到Dependencies.kt * * Extra: * [Google's Maven Repository] (https://dl.google.com/dl/android/maven2/index.html) */
/* * Version 部分 */
/*<version>*/
def compileSDK = 29 // 編譯 SDK 版本 def buildTools = "29.0.3" // Gradle 編譯項目工具版本 def minSDK = 23 // 最低兼容 Android 版本 def targetSDK = 29 // 最高兼容 Android 版本
/*</version>*/
/* * 插件/依賴庫路徑部分 */
/*<dep>*/
// gradlePlugin implementation "com.android.tools.build:gradle:4.0.1"
/** * Third Lib */
// indicator:指示器 implementation "com.hlq-struggle:indicator:1.0.0"
// permissionsDispatcher:Android 6.0 動態權限管理 implementation "org.permissionsdispatcher:permissionsdispatcher:4.7.0" // permissionsDispatcherProcessor implementation "org.permissionsdispatcher:permissionsdispatcher-processor:4.7.0"
// mmkv:基於 mmap 的高性能通用 key-value 組件 implementation "com.tencent:mmkv-static:1.1.0"
/*</dep>*/}


Step 3:新建(拷貝)大佬提供的 updateDependencies.gradle


相關日誌寫的很清楚了,你們仔細閱讀便可。


如下內容主要是將 version 中按照規則寫好的依賴/插件進行同步 groovy 中。


/** * 將versions.gradle/xVersion.gradle中配置的版本信息生成到src/main/groovy/Dependencies.groovy中 * 執行該task方法: * 方法1: * New: 在gradle task列表面板點擊'Execute Gradle Task'(相似大象的)按鈕,在輸入框輸入'-p buildSrc updateDependencies'而後點回車鍵; * Deprecated: 在gradle task列表面板點擊'Run Gradle Task'(相似大象的)按鈕,在'Gradle Project'欄選中buildSrc模塊,在'Command line'欄輸入'updateDependencies'而後點擊'OK'; * 方法2: * New: 在Terminal輸入'gradlew -p buildSrc updateDependencies'而後執行 * Deprecated: 在Terminal輸入'gradlew updateDependencies'而後執行 * 方法3: * AS->Edit Configurations->Gradle,點擊左上角'+'添加配置: * Name: $projectName:buildSrc [updateDependencies] * Gradle project: $projectName/buildSrc * Tasks: updateDependencies * 點擊'Apply'保存此配置,後續在項目的 gradle task 列表中就能夠找到此 task 雙擊執行了 */task("updateDependencies") { String inputFilePath = "versions.gradle" String outputFilePath = "src/main/groovy/Dependencies.groovy"
// 將inputFilePath聲明爲該Task的inputs inputs.file(inputFilePath) // 將outputFilePath聲明爲outputs outputs.file(outputFilePath)
doLast { File inputFile = file(inputFilePath) if (!inputFile.exists()) { return }
String inputTxt = inputFile.text StringBuilder builder = new StringBuilder()
/* * 解析拼接版本object */ builder.append("/**\n") .append(" * 版本信息\n") .append(" */\n") .append("interface Versions {\n")
String startFlag = "/*<version>*/" String endFlag = "/*</version>*/"
int start = inputTxt.indexOf(startFlag) int end = inputTxt.indexOf(endFlag)
builder.append(" ") .append(inputTxt.substring(start + startFlag.length(), end).trim()) .append("\n}\n\n")
/* * 解析拼接依賴object */ builder.append("/**\n") .append(" * 依賴庫路徑\n") .append(" */\n") .append("interface Deps {\n")
startFlag = "/*<dep>*/" endFlag = "/*</dep>*/"
start = inputTxt.indexOf(startFlag) end = inputTxt.indexOf(endFlag)
String depsTxt = inputTxt.substring(start + startFlag.length(), end).trim()
int implementationIndex int doubleSlashIndex
while (true) { implementationIndex = depsTxt.indexOf("implementation") if (implementationIndex == -1) { break } doubleSlashIndex = depsTxt.lastIndexOf("//", implementationIndex) String namePart String name while (true) { namePart = depsTxt.substring(doubleSlashIndex + 2, implementationIndex) name = namePart.split(":")[0].trim() if (!name.contains("/")) { break } doubleSlashIndex = depsTxt.lastIndexOf("//", doubleSlashIndex - 2) } depsTxt = depsTxt.replaceFirst("implementation", String.format("def %s =", name)) }
builder.append(" ") .append(depsTxt) .append("\n}\n")
String resultTxt = builder.toString()
file(outputFilePath).withPrintWriter("utf-8", { writer -> writer.print(resultTxt) writer.flush() }) }}


Step 4:新建最後的 build.gradle


apply plugin: 'groovy'apply from: 'updateDependencies.gradle'


Step 5:執行同步命令


固然大佬提供了三種方案,挑選我的習慣的一種便可。



Step 3 中拷貝以下命令:


-p buildSrc updateDependencies


注意我畫紅線的地方,這是 AS 提供的一個相似歷史記錄的操做,很方便的記錄下咱們上次使用的 task,省的每次都輸入。



執行速度仍是蠻快的,隨後變生成了咱們的 groovy 文件:



大概截取此文件內容,其實就是和咱們的 versions.gradle 同樣,不信你看:



Step 6:如何使用?


到這裏其實就很 easy 了,簡單舉例。


Versions 使用:



Deps 使用:



如何更新以及同步?


圖片沒抓到,查看原文吧~


感謝掘金大佬~


4、基於 basic 繼續封裝抽取 build


基本完善以後,默默的趕忙仍是有點不舒服的地方,例如:


如今的架構是一個 app 下對應其它 module,而每一個 module 都會有一些相同卻不相同的內容,若是後期調整,難倒我要一個個去修改嗎?豈不是又讓雞老大一通鄙視麼。


想一想,再好好想一想。 



以前曾經作過一個 basic 抽取,一樣將共有參數/信息提取到 basic.gradle 中,每一個 module apply,這樣就是減小很多代碼量。


請看我封裝好的 basic.gradle


apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'
android {
// 指定用於編譯項目的 API 級別 compileSdkVersion Versions.compileSDK // 指定在生成項目時要使用的 SDK 工具的版本,Android Studio 3.0 後不須要手動配置。 buildToolsVersion Versions.buildTools
// 指定 Android 插件適用於全部構建版本的版本屬性的默認值 defaultConfig { minSdkVersion Versions.minSDK targetSdkVersion Versions.targetSDK versionCode 1 versionName "1.0"
// 僅保留中文資源 resConfigs "zh" // 啓用多 dex 文件 multiDexEnabled true
ndk { // 設置支持的SO庫架構 abiFilters "armeabi", "x86" }
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }
// 配置 Java 編譯(編碼格式、編譯級別、生成字節碼版本) compileOptions { encoding = 'utf-8' sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() }
// 開啓視圖綁定 兼容 Gradle 4.x 及以上版本 buildFeatures { dataBinding = true viewBinding = true // gradle 5.x + }
lintOptions { // lint 異常後繼續執行 abortOnError false }}
/** * implementation:不會向下傳遞,僅在當前 module 生效; api:向下傳遞,所依賴的 module 都可使用 */dependencies { implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation Deps.kotlinStdlibJdk7 implementation Deps.appcompat implementation Deps.coreKtx
testImplementation Deps.junit androidTestImplementation Deps.extJunit androidTestImplementation Deps.espressoCore}


個人 app build.gradle


apply plugin: 'com.android.application'
apply from: "../basic.gradle"
android {
// 指定 Android 插件適用於全部構建版本的版本屬性的默認值 defaultConfig { applicationId "com.pwccn.fadvisor" }
// 封裝項目的全部構建類型配置 buildTypes { debug { // Log 控制器 - 輸出日誌 buildConfigField "boolean", "LOG_DEBUG", "true" // 對調試 build 停用 Crashlytics ext.enableCrashlytics = false // 禁止自動生成 build ID ext.alwaysUpdateBuildId = false // 關閉資源縮減 shrinkResources false // 關閉代碼縮減 minifyEnabled false // 關閉 zipAlign 優化 zipAlignEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } release { // Log 控制器 - 禁止輸出日誌 buildConfigField "boolean", "LOG_DEBUG", "false" // 啓用資源縮減 shrinkResources true // 啓動代碼縮減 minifyEnabled true // 開啓 zipAlign 優化 zipAlignEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }
}
/** * implementation:不會向下傳遞,僅在當前 module 生效;api:向下傳遞,所依賴的 module 都可使用 */dependencies { implementation Deps.legacySupportV4 implementation Deps.lifecycleExtensions implementation Deps.lifecycleViewModelKtx
// 模塊化部分導入部分
// helper implementation project(path: ':helper') // weight implementation project(path: ':weight')
// 經常使用三方依賴導入部分 // 。。。
}


個人 helper module


apply plugin: 'com.android.library'apply from:"../basic.gradle"
dependencies { api Deps.constraintLayout api Deps.xPopup
// helper implementation project(path: ':helper')}


ummmbasic 配合 buildSrc,果真以爲巴適許多。


強烈推薦第三種以及第四種結合使用,那感受,真的是 feel 倍兒爽~


參考資料


  • 配置項目全局屬性

  • Use buildSrc to abstract imperative logic

  • refreshVersions 

  • 掘金之路()統一管理插件和依賴庫信息->buildSrc

  • maven.google.com

  • BuildSrcDemo 






歡迎各位關注

不按期發佈

見證成長路






以爲不錯,右下角點個好看唄~

本文分享自微信公衆號 - 賀利權(hlq_struggle)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索