如何在 Kotlin 中访问 variant.outputFileName

Posted

技术标签:

【中文标题】如何在 Kotlin 中访问 variant.outputFileName【英文标题】:How to access variant.outputFileName in Kotlin 【发布时间】:2018-11-20 09:23:11 【问题描述】:

我们一直在使用类似这样的 sn-p 来重命名 Gradle 构建生成的 APK 文件:

android.applicationVariants.all  variant ->
    variant.outputs.all 
        outputFileName = "$variant.name-$variant.versionName.apk"
    

来源:https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration#variant_output

我现在正在将我的build.gradle 转换为build.gradle.kts,即。 e.到 Gradle Kotlin DSL。这是最后丢失的部分之一:我不知道如何访问outputFileName

根据 API 文档,它甚至似乎都不存在:

BaseVariant.getOutputs() 返回一个 DomainObjectCollection<BaseVariantOutput>,它提供了在 sn-p 中使用的 all 方法。 BaseVariantOutput 扩展 OutputFile 扩展 VariantOutput 但这些都没有 outputFileName 或任何匹配名称的 getter 或 setter。

所以,我怀疑有一些高级的 Groovy 魔法在起作用,但我如何在 Kotlin 中做到这一点?

【问题讨论】:

致反对者:想解释一下吗? 【参考方案1】:

@david.mihola 答案的一点简化版本:

android 

    /**
    * Notes Impl: Use DomainObjectCollection#all
    */
    applicationVariants.all 
        val variant = this
        variant.outputs
            .map  it as com.android.build.gradle.internal.api.BaseVariantOutputImpl 
            .forEach  output ->
                val outputFileName = "YourAppName - $variant.baseName - $variant.versionName $variant.versionCode.apk"
                println("OutputFileName: $outputFileName")
                output.outputFileName = outputFileName
            
    


【讨论】:

能否请您解释一下这个合成器/样本以及为什么我们仍然需要使用 BaseVariantOutputImpl ? 因为我们需要转换它,所以我们可以访问 setOuputFileName @Zingam cmiiw @mochdawi 那么这是由于 Kotlin DSL 的一些严重缺陷造成的吗? 根据我的经验,applicationVariants.forEach 不起作用(集合为空),您必须使用 applicationVariants.all。但不是 Kotlin 的扩展 Iterable.all(),而是 DomainObjectCollection.all()。【参考方案2】:

浏览了Android Gradle插件的源码,我想我找到了答案——开始吧:

我们实际上是在处理BaseVariantOutputImpl 类型的对象,而这个类确实有这两种方法:

public String getOutputFileName() 
    return apkData.getOutputFileName();


public void setOutputFileName(String outputFileName) 
    if (new File(outputFileName).isAbsolute()) 
        throw new GradleException("Absolute path are not supported when setting " +
                    "an output file name");
    
    apkData.setOutputFileName(outputFileName);

利用这些知识,我们现在可以:

import com.android.build.gradle.internal.api.BaseVariantOutputImpl

然后像这样投射我们的目标对象:

applicationVariants.all(object : Action<ApplicationVariant> 
    override fun execute(variant: ApplicationVariant) 
        println("variant: $variant")
        variant.outputs.all(object : Action<BaseVariantOutput> 
            override fun execute(output: BaseVariantOutput) 

                val outputImpl = output as BaseVariantOutputImpl
                val fileName = output.outputFileName
                        .replace("-release", "-release-v$defaultConfig.versionName-vc$defaultConfig.versionCode-$gitHash")
                        .replace("-debug", "-debug-v$defaultConfig.versionName-vc$defaultConfig.versionCode-$gitHash")
                println("output file name: $fileName")
                outputImpl.outputFileName = fileName
            
        )
    
)

所以,我猜:是的,有一些 Groovy 魔法在起作用,即 Groovy 的动态类型系统允许您只访问 getOutputFileNamesetOutputFileName(通过缩写 outputImpl.outputFileName 语法,如 Kotlin ) 从您的代码中提取,希望它们在运行时存在,即使您知道的编译时接口没有它们。

【讨论】:

非常感谢您提供详细的解释+解决方案:) @david.mihola 注意:您可以调用更简单的 applicationVariants.all(无需创建显式 Action 对象),您的 ApplicationVariant 将作为接收器参数传递。确保不要定义一些 lambda 参数:“.all variant->”,否则它将匹配 Kotlin 的扩展函数。只使用简单的“.all”,你会得到更短更好的代码。【参考方案3】:

使用 lambdas 的较短版本:

    applicationVariants.all
        outputs.all 
            if(name.contains("release"))
                (this as BaseVariantOutputImpl).outputFileName = "../../apk/$name-$versionName.apk"
        
    

这会将 APK 放入 app/apk 文件夹,名称由变体名称和版本代码组成。 您可以根据需要更改文件名的格式。 重要提示:必须仅在发布版本上执行,因为路径中的“..”会破坏调试构建过程并出现奇怪的错误。

【讨论】:

以上是关于如何在 Kotlin 中访问 variant.outputFileName的主要内容,如果未能解决你的问题,请参考以下文章

如何在 kotlin 中“静态”访问类的成员?

如何在 Kotlin 中访问 variant.outputFileName

如何在 kotlin android 的活动中访问应用程序类变量

如何在 Kotlin 的 Ktor 中提取访问权限验证

在 kotlin 中,如何从子类访问父类中受保护的静态成员

如何通过 Kotlin 中的 gson 访问嵌套 JSON 中的值?