Android Studio 和Gradle Plugin 3.0 遷移不完全指南

Android Studio 3.0 默認Gradle版本為4.1,如果你需要手動升級版本的話,記得修改gradle/wrapper/gradle-wrapper.properties文件的URL地址:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

對應的Gradle插件版本為3.0.0,手動修改的話,需要修改項目級的build.gradle文件:

buildscript {
    repositories {
        ...
        // You need to add the following repository to download the
        // new plugin.
        google()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
    }
}

注意上面記得添加google這個repository,某些官方依賴需要下載

對應我們的構建工具buildToolsVersion版本為26.0.2,對應Module級項目build.gradle文件:

android {
    compileSdkVersion 26
    ...
    defaultConfig {
      ...
    }
}
  • 使用變體感知(variant-aware)依賴管理機制

    Android 3.0的插件使用一種新的依賴機制,這種機制能自動的匹配我們項目中依賴庫的變體,即app變體debug會自動消費它所依賴的library的debug變體。當然了,我們在給產品定制不同的風味時,它依然能夠適用。所以呢為了保證能夠準確的匹配這些變體,我們需要為所有的產品風味聲明風味維度(flavor dimensions),以及不可能直接匹配的需要我們提供matching fallbacks(PS:不知道怎么翻譯了...)

    好了,上面說了那么多"廢話",其實只是想說明一點,如果你項目中用了build type 或者product flavor一種或一種以上, 那么你就需要注意了,這里可能需要做相應的適配:

  1. 添加風味維度的聲明
    當我們在配置文件中配置產品風味的時候,現在需要聲明風味維度,然后在每個產品風味中指定你前面所聲明的某一個風味維度,如下:

    //定義兩個風味維度
    flavorDimensions "api", "mode"
    
    productFlavors {
         demo {
             //指定風味維度
            dimension "mode"
            ...
         }
         
         full {
            dimension "mode"
            ...
         }
         
         minApi24 {
             dimension "api"
             minSDKVersion '24'
             versionNameSuffix "-minApi24"
         }
         
         minApi23 {
             dimension "api"
             minSDKVersion '23'
             versionNameSuffix "-minApi23"
         }
         
         minApi21 {
             dimension "api"
             minSDKVersion '21'
             versionNameSuffix "-minApi21"
         }
    }
    

    如上,配置完后,Gradle創建的構建變體數量等于每個風味維度中的風味數量與你配置的構建類型數量的乘積,在 Gradle 為每個構建變體或對應 APK 命名時,屬于較高優先級風味維度的產品風味首先顯示,之后是較低優先級維度的產品風味,再之后是構建類型。以上面的構建配置為例,Gradle 可以使用以下命名方案創建總共 12 個構建變體:
    構建變體:[minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
    對應 APK:app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
    例如構建變體:minApi24DemoDebug,對應 APK:app-minApi24-demo-debug.apk
    當然如果有些特定的變體不是你需要的,你也可以過濾:

    android{
        variantFilter { variant ->
            def names = variant.flavors*.name
            // To check for a certain build type, use variant.buildType.name == "<buildType>"
            if (names.contains("minApi21") && names.contains("demo")) {
            // Gradle ignores any variants that satisfy the conditions above.
            setIgnore(true)
            }
        }
    }
    

    如果組合多個產品風味,產品風味之間的優先級將由它們所屬的風味維度決定。上面所列示的第一個風味維度中的產品風味比第二個維度中的產品風味擁有更高的優先級,以此類推。此外,與屬于各個產品風味的源集相比,你為產品風味組合創建的源集擁有更高的優先級。

    如果不同源集包含同一文件的不同版本,Gradle 將按以下優先順序決定使用哪一個文件(左側源集替換右側源集的文件和設置):

    構建變體 > 構建類型 > 產品風味 > 主源集 > 庫依賴項

    如以下優先級順序:

    1. src/demoDebug/(構建變體源集)
    2. src/debug/(構建類型源集)
    3. src/demo/(產品風味源集)
    4. src/main/(主源集)
      這里說下源集的概念,Android Studio 按邏輯關系將每個模塊的源代碼和資源分組稱為源集,默認情況下,Android Studio 會創建 main/源集和目錄,用于存儲要在所有構建變體之間共享的一切資源。然而,我們也可以創建新的源集來控制 Gradle 要為特定的構建類型、產品風味(以及使用風味維度時的產品風味組合)和構建變體編譯和打包的確切文件。例如,可以在 main/ 源集中定義基本的功能,使用產品風味源集針對不同的客戶更改應用的品牌,或者僅針對使用調試構建類型的構建變體包含特殊的權限和日志記錄功能等。
      • src/main/:此源集包括所有構建變體共用的代碼和資源。
      • src/<buildType>/:創建此源集可加入特定構建類型專用的代碼和資源。
      • src/<productFlavor>/:創建此源集可加入特定產品風味專用的代碼和資源。
      • src/<productFlavorBuildType>/:創建此源集可加入特定構建變體專用的代碼和資源。

    配置完后我們可以通過Android Studio窗口右側Gradle,導航至YourApplication>Tasks>android下雙擊sourceSets,在Android Studio底部右下角 Gradle Console處查看項目是如何組織源集的

  1. 關于構建類型的配置,假設App中配置了一個叫做"jniDebug"的構建類型,但是該App所依賴的庫中沒有配置,這時候當我們構建"jniDebug"的時候,插件就不知道庫該使用什么構建類型,這時候就會給報出下面的錯誤:

    Error:Failed to resolve: Could not resolve project :mylibrary.
    Required by:project :app
    

    這類問題就是由于上面的依賴管理機制變化導致的,我們可以下面的幾種情況來分別解決:

    • 你的 Module App 包含了它所依賴的庫沒有的構建類型

      例如,我們的App包含了一個jniDebug的構建類型,但是它所依賴的庫中沒有這個,而是有debug和release這兩個構建類型,這時候我們就可以在Module App的build.gradle文件中使用matchingFallbacks 來指定可以替換的匹配項,如下:

      android {
          buildTypes {
              debug {}
              release {}
              jniDebug {
                  // Specifies a sorted list of fallback build types that the
                  // plugin should try to use when a dependency does not include a
                  // "jniDebug" build type. You may specify as many fallbacks as you
                  // like, and the plugin selects the first build type that's
                  // available in the dependency.
                  matchingFallbacks = ['debug', 'release']
              }
          }
      }
      

      值得一提的是插件會選擇matchingFallbacks列表中第一個可用的構建類型來替換匹配項。

      注意當依賴的庫中包含了Module App沒有的構建類型,則不會出現上述問題。

    • 對于一個給定的存在于App和它所依賴的庫中的風味維度,我們的主Module App包含了庫中沒有的風味:

      例如,主Module App和庫中都包含了一個mode的風味維度,我們的App中指定mode維度的是free和paid風味,而庫中指定mode維度的是demo和paid風味,這時候我們就可以用`matchingFallbacks 來為App中的free指定可以替換的匹配項。如下:

      android {
                defaultConfig{
                // Do not configure matchingFallbacks in the defaultConfig block.
                // Instead, you must specify fallbacks for a given product flavor in the
                // productFlavors block, as shown below.
              }
                flavorDimensions 'mode'
                productFlavors {
                    paid {
                        dimension 'mode'
                        // Because the dependency already includes a "paid" flavor in its
                        // "mode" dimension, you don't need to provide a list of fallbacks
                        // for the "paid" flavor.
                    }
                    free {
                        dimension 'mode'
                        // Specifies a sorted list of fallback flavors that the plugin
                        // should try to use when a dependency's matching dimension does
                        // not include a "free" flavor. You may specify as many
                        // fallbacks as you like, and the plugin selects the first flavor
                        // that's available in the dependency's "mode" dimension.
                        matchingFallbacks = ['demo', 'trial']
                    }
                }
            }
      

      值得注意的是,上述情況中,如果說庫中包含了一個主Module App沒有的產品風味,則不會出現上述問題。

    • 庫中包含了一個主Module App沒有的風味維度

      例如,庫中聲明了一個minApi的風味維度,但是你的App中只有mode維度,因此當你要構建freeDebug這個變種版本的App時,插件就不知道你是想用minApi23Debug還是用minApi25Debug變種版本的庫,這時候我們可以在主Module App中的defaultConfig代碼塊通過配置missingDimensionStrategy來讓插件從丟失的維度中指定默認的風味,當然你也可以在productFlavors代碼塊中覆蓋先前的選擇,因此每一個風味都可以為丟失的維度指定一個不同的匹配策略。

      android {
         defaultConfig{
                // Specifies a sorted list of flavors that the plugin should try to use from
                // a given dimension. The following tells the plugin that, when encountering
                // a dependency that includes a "minApi" dimension, it should select the
                // "minApi23" flavor. You can include additional flavor names to provide a
                // sorted list of fallbacks for the dimension.
                missingDimensionStrategy 'minApi', 'minApi23', 'minApi25'
                // You should specify a missingDimensionStrategy property for each
                // dimension that exists in a local dependency but not in your app.
                missingDimensionStrategy 'abi', 'x86', 'arm64'
          }
          flavorDimensions 'mode'
          productFlavors {
              free {
                  dimension 'mode'
                  // You can override the default selection at the product flavor
                  // level by configuring another missingDimensionStrategy property
                  // for the "minApi" dimension.
                  missingDimensionStrategy 'minApi', 'minApi25', 'minApi23'
              }
              paid {}
          }          
      }
      

      值得注意的是,當你的主Module App中包含了一個庫中依賴項沒有的風味維度時,則不會出現上述問題。例如,當庫中依賴項不包含abi這個維度時,freeX86Debug版本將會使用freeDebug版本的依賴。

  • 使用新的依賴配置

    先來看下以前的配置:

    android {...}
    ...
    dependencies {
        // The 'compile' configuration tells Gradle to add the dependency to the
        // compilation classpath and include it in the final package.
    
        // Dependency on the "mylibrary" module from this project
        compile project(":mylibrary")
    
        // Remote binary dependency
        compile 'com.android.support:appcompat-v7:26.1.0'
    
        // Local binary dependency
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }
    

    上面也是我們常見的三種依賴項聲明:

    1. 模塊依賴項

    compile project(':mylibrary') 行聲明了一個名為“mylibrary”的本地 Android 庫模塊作為依賴項,并要求構建系統在構建應用時編譯并包含該本地模塊。

    1. 遠程二進制依賴項

    compile 'com.android.support:appcompat-v7:26.1.0' 行會通過指定其 JCenter 坐標,針對 Android 支持庫的 26.1.0 版本聲明一個依賴項。

    1. 本地二進制依賴項

    compile fileTree(dir: 'libs', include: ['*.jar']) 行告訴構建系統在編譯類路徑和最終的應用軟件包中包含 app/libs/ 目錄內的任何 JAR 文件。

    再來看下以前配置的關鍵字:

    1. compile

      指定編譯時依賴項。Gradle 將此配置的依賴項添加到類路徑和應用的 APK。這是默認配置。

    2. apk

      指定 Gradle 需要將其與應用的 APK 一起打包的僅運行時依賴項。我們可以將此配置與 JAR 二進制依賴項一起使用,而不能與其他庫模塊依賴項或 AAR 二進制依賴項一起使用。

    3. provided

      指定 Gradle 不與應用的 APK 一起打包的編譯時依賴項。如果運行時無需此依賴項,這將有助于縮減 APK 的大小。我們可以將此配置與 JAR 二進制依賴項一起使用,而不能與其他庫模塊依賴項或 AAR 二進制依賴項一起使用。

    4. 使用構建變體或者測試源集的名稱配置關鍵字

      這種方式可以為特定的構建變體或者測試源集配置依賴項,如:

      dependencies {
          ...
          // Adds specific library module dependencies as compile time dependencies
          // to the fullRelease and fullDebug build variants.
          fullReleaseCompile project(path: ':library', configuration: 'release')
          fullDebugCompile project(path: ':library', configuration: 'debug')
      
          // Adds a compile time dependency for local tests.
          testCompile 'junit:junit:4.12'
      
          // Adds a compile time dependency for the test APK.
          androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
      }
      

    我們再來看下新的配置項:

    1. implementation

      原有compile已經廢棄掉,新增了implementation,用它來配置模塊時,它是用來告訴Gradle該Module不會在編譯時暴露其依賴給其他Module,而僅僅是在運行時才會暴露出來,即對其他Module可用。因此它也常常用在該模塊不需要有別的模塊依賴時聲明使用,例如我們的App Module或者test Module。這樣做的好處是減少了我們構建的時間。

    2. api

      同原有compile,它和上面的區別就是它在編譯器和運行期都會暴露它所配置的模塊,因此也常常用在 library module中。這是因為一旦我們api配置的這個Module有變化,Gradle會在編譯期重新編譯那個依賴項的所有依賴。所以,如果我們項目中有大量的api配置項依賴,那么無形中就增加了構建的時間,除非你想暴露這個模塊的API給其他Module使用,否則,我們應盡可能使用implementation來代替。

    3. compileOnly

      同上面的provided,只在編譯時用,不會打包到我們的APK中

    4. runtimeOnly

      同上面的apk

  • 一些API的變化

    尤其注意的是我們重命名打包的APK文件,以及輸出路徑。
    變化前:

    applicationVariants.all { variant ->
      variant.outputs.each { output ->
          def outputFile = output.outputFile
          if (outputFile != null && outputFile.name.endsWith('.apk')) {
              if (variant.buildType.name == 'lotteryTest') {
                  def fileName = "myApp_v${defaultConfig.versionName}_${releaseTime()}.apk"
                  output.outputFile = new File(outputFile.parent, fileName)
              }
          }
      }
    }
    

    變化后:

    applicationVariants.all { variant ->
      variant.outputs.all { output ->
          def outputFile = output.outputFile
          if (outputFile != null && outputFile.name.endsWith('.apk')) {
              if (variant.buildType.name == 'lotteryTest') {
                  def fileName = "myApp_v${defaultConfig.versionName}_${releaseTime()}.apk"
                  outputFileName = new File(fileName)
              }
          }
      }
    }
    

    即我們需要修改each() 和 outputFile() 方法為 all() 和 outputFileName

  • 默認啟用AAPT2
    在遷移的過程中,如果發現由于aapt2導致的異常,可以在gradle.properties中加入:

    android.enableAapt2=false
    
  • 支持Java8新特性

    1. Gradle帶來全新的Java8支持方案desugar,啟用十分簡單,只需要配置下面代碼:

      compileOptions {
          sourceCompatibility JavaVersion.VERSION_1_8
          targetCompatibility JavaVersion.VERSION_1_8
      }
      

      如果你不想使用,也可以禁用,可以在gradle.properties中加入:

      android.enableDesugar=false
      

      記得刪除上面的兼容Java8代碼。

    2. 移除Jack工具鏈,不再支持

      android {
         ...
         defaultConfig {
             ...
             // Remove this block.
             jackOptions {
                 enabled true
                 ...
             }
         }
         // Keep the following configuration in order to target Java 8.
         compileOptions {
             sourceCompatibility JavaVersion.VERSION_1_8
             targetCompatibility JavaVersion.VERSION_1_8
         }
      }
      
    3. 移除Retrolambda插件

      項目級build.gradle 文件:

      buildscript {
         ...
         dependencies {
             // Remove the following dependency.
             classpath 'me.tatarka:gradle-retrolambda:<version_number>'
         }
      }
      

      Module級build.gradle文件:

      // Remove the following plugin.
      apply plugin: 'me.tatarka.retrolambda'
      ...
      // Remove this block after migrating useful configurations.
      retrolambda {
         ...
         // If you have arguments for the Java VM you want to keep,
         // move them to your project's gradle.properties file.
         jvmArgs '-Xmx2048m'
      }
      
    4. 目前兼容支持的功能特性有:

      • Lambda expressions
      • Method References
      • Type Annotations
      • Default and static interface methods
      • Repeating annotations

參考

  1. https://developer.android.com/studio/write/java8-support.html#migrate
  2. https://android-developers.googleblog.com/2017/10/android-studio-30.html
  3. https://developer.android.com/studio/build/gradle-plugin-3-0-0.html#known_issues
  4. https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容