瞭解如何從源碼層尋找消除 Android Build Gradle 過時 API 的方案

前言背景

前段時間把公司一個項目中用到的 Gradle 版本從 com.android.tools.build:gradle:3.2.1 升級到了 com.android.tools.build:gradle:3.5.2 版本,這個項目因爲起步晚,因此對於 build.gradle 文件中的用法基本都符合新版本的要求,不過有兩個 Warning 點:html

  • The following project options are deprecated and have been removed: android.useDeprecatedNdk.
  • API 'variantOutput.getProcessManifest()' is obsolete and has been replaced with 'variantOutput.getProcessManifestProvider()'. It will be removed at the end of 2019.

這兩個 Warning 點是怎麼產生的呢,咱們分別來看一下:android

第一點是因爲在 gradle.properties 中加入了 android.useDeprecatedNdk=true,這個主要是因爲項目中用到的一個自研 SO 包用的是低版本的 ndk-bundle 構建出來的,因爲歷史久遠,改動工做量大,因此這個短時間只能先這樣了;git

第二點則是因爲下面一段處理邏輯引起的,這段邏輯主要是用於動態替換清單文件中定義的一些第三方庫用到的 meta-data 配置信息,原理挺簡單的就是拿到清單文件路徑,而後對相應的 KEY 進行文本內容的替換github

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifest.doLast {

            def manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
            // 省略後面對清單文件的替換處理邏輯
            ...
            ...
        }
    }
}
複製代碼

好比這個是配置的 FB SDK 用到的應用 IDapi

// 好比這個
<!-- facebook sdk -->
<meta-data
        android:name="com.facebook.sdk.ApplicationId"
        android:value="FACEBOOK_APPID" />
複製代碼

如何消除第二個 Warning

以往升級 Gradle 版本的時候,遇到有 Warning,通常的消除流程都是直接先網絡進行一番搜索,看是否有前人已經輸出對應的消除策略,而後就會有一堆的解決方案出如今咱們眼前,接着就會欣喜若狂地逐個去試,直接 Warning 消除爲止,而後繼續陷入搬磚 Coding 碼需求。bash

然而此次無比尷尬,搜了一大圈,發現相關的內容少之又少,並且試了一圈以後還沒法消除,場面一度尷尬,傷心之餘,痛定思痛,決定仍是從 Android 的 Gradle 插件源碼入手尋找如何消除這個 Warning。網絡

通過一番努力,邏輯調整以下,便可順利消除這個 Warning:app

// output.processManifest.doLast ==> output.processManifestProvider.get().doLast
// manifestOutputDirectory ==> {manifestOutputDirectory.get()}
android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifestProvider.get().doLast {

            def manifestPath = "${manifestOutputDirectory.get()}/AndroidManifest.xml"
        }
    }
}
複製代碼

OK,到這裏 Warning 已經完美的消除了,若是你只是尋求一個消除方案,那後續的內容其實能夠忽略,而若是你是想之後能夠從容面對,這種因爲 Gradle 版本升級帶來的過時 API 調整方案,那麼很高興咱們能夠一塊兒接着往下探索。ide

先了解如何開發一個簡單的 Gradle 插件

要想知道爲什麼這麼調整能夠消除 Warning,以及編譯時提示這個 Warning 的根源在哪裏,咱們須要先緩一緩,買個關子,須要先了解一下:若是開發一個簡單的 Gradle 插件。工具

開發 Gradle 前須要先確認機器是否已經安裝好 Gradle 環境,本人環境狀況以下

------------------------------------------------------------
Gradle 6.0
------------------------------------------------------------

Kotlin:       1.3.50
Groovy:       2.5.8
JVM:          1.8.0_45 (Oracle Corporation 25.45-b02)
OS:           Mac OS X 10.15.1 x86_64
複製代碼

用 Gradle 的 init 命令基本初始化一個自定義插件項目,

» gradle init

// 選擇類型,4 即爲插件
Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 4

// 選擇開發插件用到的語言
Select implementation language:
  1: Groovy
  2: Java
  3: Kotlin
Enter selection (default: Java) [1..3] 2

// 選擇 DSL 的語言,也就是 build.gradle 裏面用到那些配置項
Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

// 插件代碼包名路徑
Project name (default: plugin): fireantPlugin
Source package (default: fireantPlugin): com.fireantzhang.plugin

> Task :init
Get more help with your project: https://guides.gradle.org?q=Plugin%20Development

BUILD SUCCESSFUL in 1m 34s
2 actionable tasks: 2 executed
複製代碼

生成初始項目目錄結構以下:

.
├── build.gradle
├── gradle
│   └── wrapper
├── gradlew
├── gradlew.bat
├── local.properties
├── plugin.iml
├── settings.gradle
└── src
    ├── functionalTest
    ├── main
    └── test
複製代碼

插件代碼類,增長了咱們本身插件的邏輯代碼以後,內容以下:

public class FireantPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register a task,Gradle init 時自動生成的任務,能夠自行刪除
        project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println("Hello from plugin 'com.fireantzhang.plugin.greeting'"));
        });

        // 經過 project 的 extensions 建立本身開發插件的配置參數
        MyAndroidInfo myAndroidInfo = project.getExtensions().create("myAndroidInfo", MyAndroidInfo.class);

        // 接下來建立咱們本身的任務,這個任務能夠讀取 build.gradle 中配置的自定義參數,以下:
        project.task("myAndroidTask", task -> {
            // 爲了方便找到咱們插件的任務,給添加分組
            task.setGroup("fireantzhang");

            task.doLast(action -> {
                System.out.println("自定義插件中執行任務:myAndroidTask,獲取到的參數爲:" + myAndroidInfo.toString());
            });
        });
    }
}
// 配置信息
public class MyAndroidInfo {

    public String devName;
    public int devAge;
}
複製代碼

通過這麼一番折騰,咱們自定義開發的插件有一個 Task: myAndroidTask,而且支持在 build.gradle 中配置信息,引入以後以下:

apply plugin: 'com.fireantzhang.plugin.greeting'

myAndroidInfo {
    devName="fireantzhang"
    devAge=18
}

// gradle 任務項
app
  >Tasks
    >android
    >build
    >cleanup
    >fireantzhang
        myAndroidTask
複製代碼

運行插件的自定義任務:./gradlew myAndroidTask,輸出內容以下,也表明着配置項和任務讀取配置項都是成功的:

» ./gradlew myAndroidTask

> Task :app:myAndroidTask
自定義插件中執行任務:myAndroidTask,獲取到的參數爲:MyAndroidInfo{devName='fireantzhang', devAge=18}
複製代碼

這個簡單插件相關的示例代碼,能夠直接訪問:github.com/fireantzhan…

瞭解 Android 官方提供的插件

有了前面瞭解的基本的 Gradle 插件開發知識以後,其實開發 Android 項目的時候,項目引入的就是 Android 官方開發的 Gradle 插件。

Android 項目裏面最多見的就是官方提供的兩個插件:com.android.applicationcom.android.library

  1. com.android.application 用於構建可用的 Android 應用程序;
  2. com.android.library 做爲 module,用於生成 aar 包,能夠引入到項目中;

而 Android 項目裏面的 build.gradle 文件中常見的這些配置邏輯,其實就是這兩個主要插件提供中的配置信息

android {

    compileSdkVersion 28
    buildToolsVersion "28.0.3"

    defaultConfig {
        applicationId = "com.fireantzhang.pluginsample"

        minSdkVersion 19
        targetSdkVersion 28
    }
}
複製代碼

文章開頭的這些處理邏輯,實際上就是這些插件中相關類提供的 API 方法:

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifest.doLast {

            def manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
            // 省略後面對清單文件的替換處理邏輯
            ...
            ...
        }
    }
}
複製代碼

Android 官方開發的這兩個插件地址也是開源的,源碼訪問方式有:

  • 第一種是用 Google 提供的 repo 工具從源碼倉庫:android.googlesource.com 直接克隆到本地查看,編譯等,不過整個源碼很是大,30G 以上,因此本文不推薦用這個方式,操做方式本文不做展開,能夠自行了解細節;
  • 第二種是利用已有的 Android 項目,直接在 Android Studio 中能夠查看,也是本文推薦的方式;

這裏着重介紹經過第二種方式,如何查看 Android 官方提供的 Gradle 插件的源碼,從而能夠得知官方提供了那些配置項和 API

首先咱們在一個已有的 Android 項目的應用級 build.gradle 中引入咱們想要查看的插件版本,以下引入的是 3.5.2 版本(由於文章開頭咱們的項目就是升級到這個版本):

dependencies {
    ...
    ...
    
    implementation 'com.android.tools.build:gradle:3.5.2'
    ...
}
複製代碼

接着點擊 Sync Now,在 External Libraries 下便可看到引入的 Android 插件代碼結構

Android插件代碼結構

開始尋找消費第二個 Warning 的方案

前面提到了幾點:

  1. 如何開發一個簡單的 Gradle 插件,而且能夠支持配置信息,旨在說明 Android 中常見的這些配置項是怎麼來的;
  2. 簡單介紹 Android 插件,以及如何查看對應插件版本的源碼;

有了這些前提條件,就能夠回過頭來跟你們介紹該如何尋找 Warning 解決方案了,Warning 產品的配置邏輯以下:

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifest.doLast {

            def manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
            // 省略後面對清單文件的替換處理邏輯
            ...
            ...
        }
    }
}
複製代碼

而後 Warning 內容以下:

API 'variantOutput.getProcessManifest()' is obsolete and has been replaced with 'variantOutput.getProcessManifestProvider()'. It will be removed at the end of 2019.
複製代碼

從 Warning 內容,能夠很清楚知道 output.processManifest 這個方法已經被廢棄,不建議繼續使用,而且 2019 年底會進行移除,因此能夠對這段邏輯調整以下,打印 output 的類信息,方便定位到 output 這個類的具體位置:

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        println("output 實現類信息:${output.getClass()}")
        output.processManifest.doLast {

            def manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
            // 省略後面對清單文件的替換處理邏輯
            ...
            ...
        }
    }
}
複製代碼

運行 ./gradle clean assembleDebug 以後獲得:

output 實現類信息:class com.android.build.gradle.internal.api.ApkVariantOutputImpl_Decorated
複製代碼

接着直接訪問 ApkVariantOutputImpl 這個類,發現沒有相似 processManifest 的方法,別灰心,他有一個繼承類,接着往父類找,能夠看到下面一段邏輯:

@Override
@NonNull
public ManifestProcessorTask getProcessManifest() {
    deprecationReporter.reportDeprecatedApi(
            "variantOutput.getProcessManifestProvider()",
            "variantOutput.getProcessManifest()",
            TASK_ACCESS_DEPRECATION_URL,
            DeprecationReporter.DeprecationTarget.TASK_ACCESS_VIA_VARIANT);
    return taskContainer.getProcessManifestTask().get();
}

@NonNull
@Override
public TaskProvider<ManifestProcessorTask> getProcessManifestProvider() {
    //noinspection unchecked
    return (TaskProvider<ManifestProcessorTask>) taskContainer.getProcessManifestTask();
}
複製代碼

看到這裏調整邏輯已經開始呼之欲出了,Warning 提議使用 variantOutput.getProcessManifestProvider(),而這個方法返回的是一個 TaskProvider,因此能夠接着查看 TaskProvider 如何拿到咱們須要的 ManifestProcessorTask,一直往上追溯,能夠得知調用 get() 方法便可拿到:

TaskProvider<T> --> NamedDomainObjectProvider<T> --> Provider<T>

@NonExtensible
public interface Provider<T> {
    T get();
    ...
    ...
}
複製代碼

另外 manifestOutputDirectory 參數也是有變化,能夠經過查看 ManifestProcessorTask 類,最終發現這個參數也是 Provider<T> 類型的,因此也是經過 get() 方法來獲取到文件夾路徑:

public abstract class ManifestProcessorTask extends IncrementalTask {
    ...
    ...

    @SuppressWarnings("unused")
    @Nonnull
    private final DirectoryProperty manifestOutputDirectory;
}

DirectoryProperty --> FileSystemLocationProperty<Directory> --> Property<T> --> Provider<T>
複製代碼

因此綜合上面的信息,最終的邏輯調整方案以下便可消除 Warning:

// output.processManifest.doLast ==> output.processManifestProvider.get().doLast
// manifestOutputDirectory ==> {manifestOutputDirectory.get()}
android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifestProvider.get().doLast {

            def manifestPath = "${manifestOutputDirectory.get()}/AndroidManifest.xml"
        }
    }
}
複製代碼

結語

升級 Aroid Gradle 編譯插件版本時,面對 Warning 提示時,若是沒法快速從前人的踩坑經驗中找到靠譜的解決方案時,該如何從源碼層面找到可靠的消除方案,畢竟本身動手,豐衣足食。

好了,今天的文章就先分享這麼多,你的關注與留言是我輸出分享內容的最大源動力,動動小手關注,纔不會漏掉下一次的分享內容。

最後附上兩個 Android 官方關於 Android Gradle 插件的說明文檔地址,不過發現其實更新不是很及時,像此次升級的 3.5.2 版本我當時升級的時候就找不到相關的介紹:

相關文章
相關標籤/搜索