Android 解决 API 'variantOutput.getPackageApplication()' is obsolete

Gradle升级3.2.1以后打包提示一个警告:

根据字面意思variantOutput.getPackageApplication() 这个方法废弃了, 但是项目中全局搜索这个关键词没有找到调用这个方法, 最后是因为下面这段代码:

1
2
3
4
5
6
7
8
9
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = "app-${defaultConfig.versionName}-release.apk"
outputFileName = fileName
}
}
}

这是写在 build.gradle 中用于自定义生成 apk 名的一段代码.

经过排查这个问题是由 output.outputFile 引起的,说明在调用 getOutputFile() 这个方法时,方法里调用了 getPackageApplication() 这个方法,既然是暗里调用,那只能去看源码了

首先进入 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 ,路径

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

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
applicationVariants.all { variant ->
variant.outputs.each { output ->
variant.packageApplicationProvider.get().outputDirectory = new File(project.rootDir.absolutePath + "/apk")
def fileName = "app-v${defaultConfig.versionName}-release.apk"
output.outputFileName = fileName
}
}