Android 解决 API 'variantOutput.getPackageApplication()' is obsolete 的小问题

今天升级了 Android Studio,自然而然也就升级 Gradle(3.2.1 –> 3.3.0),于是乎,出现了一个警告,对于一个强迫症者来说,这能忍?才怪

先看看这个红色警告是啥

嗯,好像是什么东西废弃了,问题的源头倒是很好找,因为下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile == null) return

def fileName
if (output.name == 'debug') {
fileName = "AppName-v${defaultConfig.versionName}-c${defaultConfig.versionCode}-test.apk"
} else {
fileName = "AppName-v${defaultConfig.versionName}-c${defaultConfig.versionCode}.apk"
}
output.outputFileName = fileName
}
}

这是写在 build.gradle 中用于自定义生成 apk 名的一段代码,短短几行,却经历过不少辛酸,之前好几次升级也是因为方法废弃被迫修改,看来现在又得改了

根据提示,是因为 variantOutput.getPackageApplication() 这个方法废弃了,要咱们替换成 variant.getPackageApplicationProvider() 问题是,上面短短的几行代码,哪有用到这个方法,既然明里没有,那肯定是暗地里用了,经过排查这个问题是由 output.outputFile 引起的,说明在调用 getOutputFile() 这个方法时,方法里调用了 getPackageApplication() 这个方法,既然是暗里调用,那只能去看源码了

这里小插一下

1
2
def outputFile = output.outputFile
if (outputFile == null) return

这段代码此时用处其实并不大,删除即可,但之所以一直留到了现在,是因为之前有用到它来修改 apk 的保存路径,虽然现在只用到了修改 apk 的文件名,路径并没有修改,但万一哪天用到呢,既然现在是这段代码造成的问题,那肯定以后也不能用这段代码来修改保存路径了,so,进入源码了解下

首先进入 gradle-3.3.0-sources.jar ,那怎么进入呢,如下点击

便进入源码文件

经过一番寻找,找到了 getApplicationVariants() 方法所在

代码是这样的

1
2
3
public DomainObjectSet<ApplicationVariant> getApplicationVariants() {
return applicationVariantList;
}

这样我们就知道了 applicationVariants.all { variant -> 中的 variant 是 ApplicationVariant ,但是 ApplicationVariant 是一个接口

1
2
3
4
5
6
7
8
package com.android.build.gradle.api;

import com.android.build.gradle.internal.api.TestedVariant;

/**
* A Build variant and all its public data.
*/
public interface ApplicationVariant extends ApkVariant, TestedVariant {}

继续寻找具体类,然后找到了 ApplicationVariantImpl ,路径

接着,我们继续寻找 variant.outputs.each { output -> 中的 output 变量所指,在 BaseVariant 中找到

1
2
3
4
5
6
7
/**
* Returns the variant outputs. There should always be at least one output.
*
* @return a non-null list of variants.
*/
@NonNull
DomainObjectCollection<BaseVariantOutput> getOutputs();

output 是 BaseVariantOutput ,会发现 BaseVariantOutput 又是一个接口,面向接口编程的坏处就是,别人看的时候要一层层剥离,才能找到内心,最终发现具体类是 ApkVariantOutputImpl ,路径

现在来看 def outputFile = output.outputFile 这段代码暗地里做了啥

1
2
3
4
5
6
7
8
9
10
11
@NonNull
@Override
public File getOutputFile() {
PackageAndroidArtifact packageAndroidArtifact = getPackageApplication();
if (packageAndroidArtifact != null) {
return new File(
packageAndroidArtifact.getOutputDirectory(), apkData.getOutputFileName());
} else {
return super.getOutputFile();
}
}

终于找到问题源头了,在 getOutputFile() 方法里头调用了 getPackageApplication() 方法,跟我们一开始分析的一样,但这是 gradle 的源码,我们没法修改,所有只能不要再使用 getOutputFile() 方法了

那如果要更改输出路径的时候该怎么办呢?不是建议让我们用 getPackageApplicationProvider() 方法么,我们来看看这个方法得到的是啥?

1
2
3
4
5
6
7
8
/**
* Returns the packaging task
*
* <p>Prefer this to {@link #getPackageApplication()} as it triggers eager configuration of the
* task.
*/
@Nullable
TaskProvider<PackageAndroidArtifact> getPackageApplicationProvider();

一个任务提供者,实际起作用的是 PackageAndroidArtifact ,所有直接看其源码,路径

里边东西不少,我们只取所需

1
protected File outputDirectory;

就它了,现在我要把 apk 输出到项目根目录下,就可以这样写了

1
2
3
4
5
6
7
8
9
10
11
12
13
applicationVariants.all { variant ->
variant.outputs.each { output ->
variant.packageApplicationProvider.get().outputDirectory = new File(project.rootDir.absolutePath + "/apk")

def fileName
if (output.name == 'debug') {
fileName = "AppName-v${defaultConfig.versionName}-c${defaultConfig.versionCode}-test.apk"
} else {
fileName = "AppName-v${defaultConfig.versionName}-c${defaultConfig.versionCode}.apk"
}
output.outputFileName = fileName
}
}

Perfect!烦人的警告也没有了