Android SQLite
在 Android 上使用 SQLite
Android 应用使用 KPojo 模型,并通过应用内的 KronosDataSourceWrapper 访问 SQLiteDatabase。
添加 Kronos
配置标准的 google()、mavenCentral() 和 gradlePluginPortal() 仓库后,将 Kronos 插件和 core 库加入 Android 项目。
plugins {
id("com.android.application") version "8.13.2" apply false
id("org.jetbrains.kotlin.android") version "2.4.21" apply false
id("com.kotlinorm.kronos-gradle-plugin") version "0.4.0" apply false
}本文示例使用 Android Gradle Plugin
8.13.2和 Kotlin2.4.21。参考应用使用minSdk 26和 JDK 17。
在每个声明 KPojo 的 Android application 或 library 模块中应用该插件。
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.kotlinorm.kronos-gradle-plugin")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xcollection-literals", "-Xcontext-sensitive-resolution")
}
}
dependencies {
implementation("com.kotlinorm:kronos-core:0.4.0")
}定义模型
Android 表模型使用与其他 Kotlin 应用相同的模型注解。
import com.kotlinorm.annotations.PrimaryKey
import com.kotlinorm.annotations.Table
import com.kotlinorm.interfaces.KPojo
@Table("notes")
data class Note(
@PrimaryKey(identity = true)
var id: Long? = null,
var title: String? = null,
var content: String? = null,
var favorite: Boolean? = false,
) : KPojo连接 SQLiteDatabase
在应用中添加一个 KronosDataSourceWrapper 实现。Android 参考 wrapper 是应用源码,可以复制到项目中,再按应用的数据库名称和版本进行调整。
将该源码作为 AndroidSQLiteDataSourceWrapper 加入项目后,在应用的 onCreate 中创建一次并设置为 Kronos 数据源。
import android.app.Application
import com.kotlinorm.Kronos
import com.kotlinorm.orm.ddl.table
import java.util.concurrent.Executors
import java.util.concurrent.Future
class NotesApplication : Application() {
lateinit var database: AndroidSQLiteDataSourceWrapper
private set
private val schemaExecutor = Executors.newSingleThreadExecutor()
private lateinit var schemaReady: Future<*>
override fun onCreate() {
super.onCreate()
database = AndroidSQLiteDataSourceWrapper(this)
Kronos.dataSource = { database }
schemaReady = schemaExecutor.submit {
database.table.syncTable<Note>()
}
}
fun awaitSchemaReady() {
schemaReady.get()
}
}在 AndroidManifest.xml 中注册这个 Application 类。
<application
android:name=".NotesApplication"
android:label="@string/app_name"
android:theme="@style/Theme.App">
...
</application>已有 Application 类的应用可以把数据源设置放入现有类中。
将 SQL 日志写入 Logcat
应用需要将 Kronos SQL 日志写入 Logcat 时,加入 kronos-logging。
dependencies {
implementation("com.kotlinorm:kronos-logging:0.4.0")
}在 NotesApplication.onCreate 中调用初始化函数。
import com.kotlinorm.KronosLoggerApp
KronosLoggerApp.detectLoggerImplementation()在表结构就绪后执行 CRUD
NotesApplication 会启动一次表结构准备。数据库操作在 executor 中执行,并在每次操作前调用 awaitSchemaReady();结果和错误会投递回主线程。
import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.kotlinorm.Kronos
import com.kotlinorm.orm.ddl.table
import com.kotlinorm.orm.insert.insert
import com.kotlinorm.orm.select.select
import java.util.concurrent.Executors
class NotesActivity : Activity() {
private val databaseExecutor = Executors.newSingleThreadExecutor()
private val mainHandler = Handler(Looper.getMainLooper())
private fun <T> runDatabase(
action: () -> T,
onSuccess: (T) -> Unit,
onError: (Throwable) -> Unit,
) {
databaseExecutor.execute {
runCatching {
(application as NotesApplication).awaitSchemaReady()
action()
}.onSuccess { result ->
mainHandler.post { onSuccess(result) }
}.onFailure { error ->
mainHandler.post { onError(error) }
}
}
}
fun createNote() {
runDatabase(
action = {
val id = Note(title = "First note", content = "Hello Android")
.insert()
.withId()
.execute()
.lastInsertId
?: error("SQLite did not return the generated note id")
Note()
.select()
.where { it.id == id }
.first()
},
onSuccess = { note ->
// 在界面中展示 note。
},
onError = ::reportDatabaseError,
)
}
private fun reportDatabaseError(error: Throwable) {
Log.e("Notes", "Database operation failed", error)
}
override fun onDestroy() {
databaseExecutor.shutdownNow()
super.onDestroy()
}
}在事务中执行关联写入
相关写操作使用 Kronos.transact。Android SQLite 使用默认事务参数。
在同一个 Activity 中调用上一节的 runDatabase 辅助函数。
import com.kotlinorm.Kronos
import com.kotlinorm.orm.insert.insert
import com.kotlinorm.orm.update.update
fun createFavoriteNote() {
runDatabase(
action = {
Kronos.transact {
val id = Note(title = "Plan", content = "Review Android support")
.insert()
.withId()
.execute()
.lastInsertId
?: error("SQLite did not return the generated note id")
Note(id = id)
.update()
.set { it.favorite = true }
.by { it.id }
.execute()
}
},
onSuccess = {},
onError = ::reportDatabaseError,
)
}参考应用
kronos-example-android 提供完整的 Markdown 笔记应用,包含 wrapper 源码、模型、仓储、界面和 instrumentation test。