🦁

AndroidのTypedArrayでuse拡張関数を使うとクラッシュする

2022/01/30に公開

KotlinでAndroidのネイティブアプリを開発していたときに
通常のViewでは対応できないようなパターンが出てきたため自身で
Viewを継承して作成するカスタムViewを作成することになりました。
そこでKotlinのuseメソッドを利用したところ思わぬ不具合が発生したのでそれを共有します。

何が合ったのか

単体テストヨシ!実機Android 12(Pixel)ヨシ!ベータ版配信開始!!
しばらくするとクラッシュするとの連絡が・・

  1. 不具合の発生パターンとしては画面が表示される
  2. 今回作成したViewが含まれるViewが表示されようとする
  3. クラッシュ

だったため真っ先にカスタムViewを疑うことに

対象のソースコード

context.theme.obtainStyledAttributes(attrs, R.styleable.AutoExpandableLayout, 0, 0).use {
            it.apply {
                val buttonId = getResourceId(R.styleable.AutoExpandableLayout_expandableButton, -1)
                expandableButtonId = if (buttonId == -1) null else buttonId
            }
        }

エラー内容

Caused by: java.lang.ClassCastException: android.content.res.MiuiTypedArray cannot be cast to java.lang.AutoCloseable

原因

原因としてはTypedArrayのcloseメソッドがバージョン&機種依存だったことが原因のようです。
調べたところAPIレベルSから追加されたようでクラッシュした端末が12未満だったことと辻褄が合います。
https://developer.android.com/sdk/api_diff/s-beta1-incr/changes/android.content.res.TypedArray

実際にKotlinのuse拡張関数の中身を見てみると
closeメソッドを呼び出していることがわかります。

@SinceKotlin("1.2")
@kotlin.internal.InlineOnly
public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        this.closeFinally(exception)
    }
}
@PublishedApi
internal fun AutoCloseable?.closeFinally(cause: Throwable?) = when {
    this == null -> {}
    cause == null -> close()
    else ->
        try {
            close()
        } catch (closeException: Throwable) {
            cause.addSuppressed(closeException)
        }
}

最後に

ベテランのAndroidエンジニアの方なら
普通はrecycleを利用すると思うので今回のようなことはほぼないと思いますが
私のようななんとなくコードを書いている初心者にはかなりの盲点でした。

Discussion