Android上的Kotlin Closable和SQLiteDatabase
我在我的项目中使用这个代码
fun getAllData(): List<Data> = writableDatabase.use { db -> db.query(...) }
它在棒棒糖设备上成功执行,但是在棒棒糖之前,它抛出一个ClassCastException异常
FATAL EXCEPTION: main java.lang.ClassCastException: android.database.sqlite.SQLiteDatabase cannot be cast to java.io.Closeable
可能是因为它是Java 1.6,并没有“试用资源”功能,但我不确定。
那么,为什么我有这个例外,如何解决呢?
这个问题与try-with-resources无关,只是编译代码和运行时环境之间不兼容的依赖问题。
问题出在编译时SQLiteDatabase
实现了Closeable
接口,后来换掉了另外一个没有的接口。 但是代码已经编译好了,不知道这个改变。
当你调用一个函数/方法时,所有的参数都被检查以确保它们是正确的类型,这通常由JVM来处理。 对于内联函数和扩展函数,Kotlin编译器插入一个类型检查以确保它在正确的类型上运行,对于这个特定的内联函数,函数的接收器被定义为Closeable
。 这里是方法签名:
inline fun <T : Closeable, R> T.use(block: (T) -> R): R { ... }
因此,编译器如下( 在字节码中 )对writableDatabase
进行类型检查:
CHECKCAST java/io/Closeable
所以在这一点上, SQLiteDatabase
的实现必须永远实现Closeable
接口,否则编译后的代码将会失败。 通过交换到旧版本的Android,这不再是真实的,你破坏合同,并导致异常。 编译器不能做任何不同的事情。 在我已经编译完代码之后,我将在任何JVM应用程序中将JAR换成完全不同的实现。 改变Android版本基本上交换所有的JAR。
如果所有版本都有一个close
方法( 从Kotlin stdlib use
函数复制并修改), 那么可以如何解决这个问题是专门为SQLiteDatabase
类编写自己的use
函数:
inline fun <T : SQLiteDatabase, R> T.use(block: (T) -> R): R { var closed = false try { return block(this) } catch (e: Exception) { closed = true try { close() } catch (closeException: Exception) { // eat the closeException as we are already throwing the original cause // and we don't want to mask the real exception } throw e } finally { if (!closed) { close() } } }
现在,内联类型检查将是CHECKCAST android/database/sqlite/SQLiteDatabase
,它将始终成功。