使用格式化参数包装调用 Context.getString 会导致 IllegalFormatConversionException

Posted

技术标签:

【中文标题】使用格式化参数包装调用 Context.getString 会导致 IllegalFormatConversionException【英文标题】:Wrapping the call Context.getString with formatting arguments results in a IllegalFormatConversionException 【发布时间】:2020-04-17 11:40:28 【问题描述】:

我正在尝试为 Context#getString(id, args) 方法创建一个包装器,因此我编写的代码更少:

fun Context.string(@StringRes strId: Int, vararg fmtArgs: Any?) = getString(strId, fmtArgs)

调用函数时会产生以下堆栈跟踪:

2020-04-17 13:26:20.778 24143-24143/mypackage E/ERROR:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:267)
            ... 45 more
     Caused by: java.util.IllegalFormatConversionException: d != [Ljava.lang.Object;
        at java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4403)
        at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2885)
        at java.util.Formatter$FormatSpecifier.print(Formatter.java:2839)
        at java.util.Formatter.format(Formatter.java:2524)
        at java.util.Formatter.format(Formatter.java:2459)
        at java.lang.String.format(String.java:2911)
        at android.content.res.Resources.getString(Resources.java:485)
        at android.content.Context.getString(Context.java:655)
        at mypackage.ktx.ContextKt.string(Context.kt:28)

看到问题,我查看了kotlin.text,看看他们如何使用参数执行字符串格式化...

/**
 * Uses this string as a format string and returns a string obtained by substituting the specified arguments,
 * using the default locale.
 */
@kotlin.internal.InlineOnly
public inline fun String.format(vararg args: Any?): String = java.lang.String.format(this, *args)

我适应了新的通话风格

fun Context.string(@StringRes strId: Int, vararg fmtArgs: Any?) = getString(strId, *fmtArgs)

……但它仍然会因相同的堆栈跟踪而崩溃。

我决定只获取字符串,然后使用 JetBrains 实现手动格式化它,看看是否有一些我没有想到的内部结构:

fun Context.string(@StringRes strId: Int, vararg fmtArgs: Any?) = getString(strId).format(fmtArgs)

我发现问题是 %d 不对应于一个对象,但如果 Android 能够通过类似的调用设法让这种工作和 kotlin 格式......

这种情况有什么解决办法?

【问题讨论】:

您是否尝试重写此方法,以便可以少写 3 个字符? 我不是压倒一切,我是在包装。是的,在 DSL 中写 string(id) 看起来比 getString(id) 更干净 【参考方案1】:

我适应了新的通话方式

fun Context.string(@StringRes strId: Int, vararg fmtArgs: Any?) = getString(strId, *fmtArgs)

这行得通。内联和取消内联后,gradle build-cache 刷新,这开始工作了。

【讨论】:

对我不起作用,内联和反内联 gradle build-cache 是什么意思? 在尝试随机的东西时,gradle构建缓存被刷新(我尝试的是创建一个内联函数,编译它并且不再将它变成一个内联函数并重新编译它)并开始接受这个调用风格。我有一个个人库和两个使用这个函数的完整项目,所以你只需要让 gradle 服从你的命令:)!【参考方案2】:

第一个和最后一个示例将数组作为第一个参数传递,因为缺少 * 扩展运算符,并且编译器可以使用它,因为 vararg fmtArgs: Any?Array<Any?>,但也是 Any。@ 987654325@ 应该可以工作。

【讨论】:

以上是关于使用格式化参数包装调用 Context.getString 会导致 IllegalFormatConversionException的主要内容,如果未能解决你的问题,请参考以下文章

在 Node.js 和 v8 中调用使用包装对象作为参数的函数

自动生成函数的类型安全包装,然后仅使用 `__typename` 作为参数动态调用。打字稿

包装在静态函数中时,Alamofire 4.0“调用中的额外参数'方法'”

使用 HTTPServletRequestWrapper 包装请求参数

使用委托包装异常处理时,Web-API 调用不起作用

使用 SWIG 包装对象从 C++ 调用 Python 函数的最简洁方法是啥