Android在ksp中简单使用Room
最近下载了最新版Studio,在github上看到很多比较好的开源项目,于是下载下来研究了一下,好多依赖和配置都需要升级,之前使用过room封装数据库工具类,最近在整理ksp相关,周末心血来潮把room也升级了,简单记录一下升级过程,直接上代码。
1. 简介
1.1 什么是 KSP?
KSP 是一个用于处理 Kotlin 代码的编译时工具,它允许开发者在编译期间分析和生成代码。与 KAPT 相比,KSP 直接处理 Kotlin 代码,避免了将 Kotlin 代码转换为 Java 代码的开销,因此性能更高。
2 .KSP 的优势:
- 更快的编译速度:KSP 直接处理 Kotlin 代码,避免了 KAPT 的额外转换步骤。
- 更好的 Kotlin 支持 :KSP 完全支持 Kotlin 的特性,如扩展函数、数据类等。
- 简洁的 API:KSP 提供了更易用的 API,简化了注解处理器的开发。
3. KSP 的核心概念
3.1 符号(Symbol)
KSP 的核心是符号(Symbol),它代表了 Kotlin 代码中的各种元素,如类、函数、属性等。KSP 提供了丰富的 API 来访问这些符号。
3.2 处理器(Processor)
KSP 处理器是一个实现了 SymbolProcessor
接口的类,用于处理 Kotlin 代码中的符号。每个处理器可以注册对特定注解的兴趣,并在编译时处理这些注解。
3.3 代码生成
KSP 处理器可以生成新的 Kotlin 或 Java 代码。生成的代码会被编译器编译,并与原始代码一起打包。
4.添加ksp依赖配置:
ini
#KSP
ksp = "2.1.10-1.0.29"
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
5.项目的依赖配置:
scss
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library).apply(false)
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp).apply(false)
}
6.app的build.gradle配置:
scss
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
}
7.添加room配置:
ini
#Room
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
util-codex = { group = "com.blankj", name= "utilcodex", version.ref = "utilcodex" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
[bundles]
room = [
"androidx-room-ktx",
"androidx-room-runtime",
]
8.在build.gradle添加room:
8.1 以往的room配置:
bash
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.room:room-rxjava2:$room_version"
8.2 现在的room配置:
scss
api(libs.bundles.room)
ksp(libs.androidx.room.compiler)
9.room数据库生成文件路径:
9.1 以前的路径配置:
ini
//指定room.schemaLocation生成的文件路径
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
9.2 ksp中的路径配置:
ini
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
defaultConfig {
applicationId = "com.example.ksproomdemo"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
9.3 room对应的jvm版本:
ini
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
ksp {
arg("jvmTarget", "17")
}
9.4 生成的数据库文件如下:
bash
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "1599160ffa834f06d3bdbe0680959e41",
"entities": [
{
"tableName": "User",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` TEXT NOT NULL, `name` TEXT NOT NULL, `sex` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "userId",
"columnName": "userId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sex",
"columnName": "sex",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "email",
"columnName": "email",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1599160ffa834f06d3bdbe0680959e41')"
]
}
}
10.布局文件:
ini
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_add"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="添加数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
<TextView
android:id="@+id/tv_update"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="修改数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_add"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
<TextView
android:id="@+id/tv_query"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="查询数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_update"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
<TextView
android:id="@+id/tv_delete"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="删除数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_query"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
<TextView
android:id="@+id/tv_delete_users"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="删除多个数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_delete"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
<TextView
android:id="@+id/tv_delete_all"
android:layout_width="200dp"
android:layout_height="40dp"
android:text="删除所有数据"
android:textColor="@color/white"
android:textSize="18sp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_delete_users"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/design_default_color_primary"/>
</androidx.constraintlayout.widget.ConstraintLayout>
11.数据库工具类RoomUtils:
kotlin
package com.example.db
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.app.RoomApp
import com.example.ksproomdemo.bean.User
import com.example.ksproomdemo.dao.UserDao
/**
* @author: njb
* @date: 2025/3/30 1:25
* @desc: 描述
*/
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
class RoomUtils private constructor(){
private val database: AppDatabase by lazy {
Room.databaseBuilder(
RoomApp.instance.applicationContext,
AppDatabase::class.java, "app-database"
).build()
}
companion object {
@Volatile
private var instance: RoomUtils? = null
fun getInstance(): RoomUtils {
return instance ?: synchronized(this) {
instance ?: RoomUtils().also { instance = it }
}
}
}
private val userRepository: UserRepository by lazy {
UserRepository(database.userDao())
}
suspend fun insertUser(user: User) {
userRepository.insertUser(user)
}
suspend fun updateUser(user: User) {
userRepository.updateUser(user)
}
suspend fun deleteUser(user: User) {
userRepository.deleteUser(user)
}
suspend fun getAllUsers(): List<User> {
return userRepository.getAllUsers()
}
suspend fun deleteUsers(user: List<User>) {
return userRepository.deleteUsers(user)
}
suspend fun deleteAllUser() {
userRepository.deleteAllUser()
}
}
12.UserRepository:
kotlin
package com.example.db
import com.example.ksproomdemo.bean.User
import com.example.ksproomdemo.dao.UserDao
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* @author: njb
* @date: 2025/3/30 1:26
* @desc: 描述
*/
class UserRepository (private val userDao: UserDao){
suspend fun insertUser(user: User) {
withContext(Dispatchers.IO) {
userDao.insertAll(user)
}
}
suspend fun updateUser(user: User) {
withContext(Dispatchers.IO) {
userDao.updateUser(user)
}
}
suspend fun deleteUser(user: User) {
withContext(Dispatchers.IO) {
userDao.delete(user)
}
}
suspend fun getAllUsers(): List<User> {
return withContext(Dispatchers.IO) {
userDao.getAll()
}
}
suspend fun deleteUsers(user: List<User>) {
withContext(Dispatchers.IO) {
userDao.deleteUsers(user)
}
}
suspend fun deleteAllUser() {
withContext(Dispatchers.IO) {
userDao.deleteAllUserInfo()
}
}
}
13.Dao和实体类:
less
package com.example.ksproomdemo.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.ksproomdemo.bean.User
/**
* @author: njb
* @date: 2025/3/30 1:06
* @desc: 描述
*/
@Dao
interface UserDao {
@Query("SELECT * FROM user")
suspend fun getAll(): List<User>
@Query("SELECT * FROM user WHERE id IN (:userIds)")
suspend fun loadAllByIds(userIds: IntArray): List<User>
@Query("SELECT * FROM user WHERE name LIKE :name")
suspend fun findByName(name: String): User?
@Query("SELECT *FROM user WHERE id LIKE:userId")
suspend fun findById(userId: Int): User?
@Update
suspend fun updateUser(user: User)
@Insert
suspend fun insertAll(users: User)
@Delete
suspend fun delete(user: User)
@Delete
suspend fun deleteUsers(user: List<User>)
@Query("DELETE FROM User")
suspend fun deleteAllUserInfo()
}
less
package com.example.ksproomdemo.bean
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
/**
* @author: njb
* @date: 2025/3/30 1:24
* @desc: 描述
*/
@Entity(tableName = "User")
data class User(
var userId: String = "",
@ColumnInfo(name = "name") var name: String = "",
@ColumnInfo(name = "sex") var sex: String = "",
@ColumnInfo(name = "email") var email: String = "",
){
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
14.测试代码:
kotlin
package com.example.ksproomdemo
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.SnackbarUtils
import com.blankj.utilcode.util.ToastUtils
import com.example.db.RoomUtils
import com.example.ksproomdemo.bean.User
import com.example.ksproomdemo.databinding.ActivityMainBinding
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() {
private lateinit var roomUtils: RoomUtils
private lateinit var binding:ActivityMainBinding
private val TAG = "Mainivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initListener()
}
private fun initView() {
roomUtils = RoomUtils.getInstance()
}
private fun initListener() {
binding.tvAdd.setOnClickListener {
lifecycleScope.launch {
val user = User("18","张三","男","[email protected]")
withContext(Dispatchers.IO) {
roomUtils.insertUser(user)
LogUtils.d(TAG, "----添加用户数据----$user")
}
Snackbar.make(binding.tvAdd,user.toString(),Snackbar.LENGTH_LONG).show()
}
}
binding.tvUpdate.setOnClickListener {
lifecycleScope.launch {
val user = User("12","Tom","女","[email protected]")
withContext(Dispatchers.IO){
roomUtils.updateUser(user)
LogUtils.d(TAG, "----更新用户数据----$user")
}
Snackbar.make(binding.tvUpdate,user.toString(),Snackbar.LENGTH_LONG).show()
}
}
binding.tvQuery.setOnClickListener {
lifecycleScope.launch {
val user = withContext(Dispatchers.IO){
RoomUtils.getInstance().getAllUsers()
}
LogUtils.d(TAG, "----查询用户数据----$user")
Snackbar.make(binding.tvQuery,user.toString(),Snackbar.LENGTH_LONG).show()
}
}
binding.tvDelete.setOnClickListener {
lifecycleScope.launch {
val user = User("18","张三","男","[email protected]")
withContext(Dispatchers.IO){
RoomUtils.getInstance().deleteUser(user)
}
LogUtils.d(TAG, "----删除一条用户数据----$user")
Snackbar.make(binding.tvDelete,user.toString(),Snackbar.LENGTH_LONG).show()
}
}
binding.tvDeleteUsers.setOnClickListener {
lifecycleScope.launch {
val user = roomUtils.getAllUsers()
val user1 : List<User>
// 删除所有用户
withContext(Dispatchers.IO) {
roomUtils.deleteUsers(user)
user1 = roomUtils.getAllUsers()
}
LogUtils.d(TAG, "===删除多个用户数据===$user1")
Snackbar.make(binding.tvDelete,user1.toString(),Snackbar.LENGTH_LONG).show()
}
}
binding.tvDeleteAll.setOnClickListener {
lifecycleScope.launch {
val user : List<User>
// 删除所有用户
withContext(Dispatchers.IO) {
roomUtils.deleteAllUser()
user = roomUtils.getAllUsers()
}
LogUtils.d(TAG, "===删除所有用户数据===$user")
Snackbar.make(binding.tvDelete,user.toString(),Snackbar.LENGTH_LONG).show()
}
}
}
}
15.日志打印:
less
2025-03-30 22:53:13.383 3712-3760 Mainivity com.example.ksproomdemo D ----添加用户数据----User(userId=18, name=张三, sex=男, [email protected])
2025-03-30 22:53:26.322 3712-3760 Mainivity com.example.ksproomdemo D ----更新用户数据----User(userId=12, name=Tom, sex=女, [email protected])
2025-03-30 22:53:37.349 3712-3712 Mainivity com.example.ksproomdemo D ----查询用户数据----[User(userId=18, name=张三, sex=男, [email protected]), User(userId=18, name=张三, sex=男, [email protected]), User(userId=18, name=张三, sex=男, [email protected])]
2025-03-30 22:53:49.061 3712-3712 Mainivity com.example.ksproomdemo D ----删除一条用户数据----User(userId=18, name=张三, sex=男, [email protected])
2025-03-30 22:53:58.775 3712-3712 Mainivity com.example.ksproomdemo D ===删除多个用户数据===[]
Mainivity com.example.ksproomdemo D ===删除所有用户数据===[]
16.实现效果如下:
17.总结:
- KSP 是一个强大的 Kotlin 编译时工具,适用于代码生成、代码分析等场景。与 KAPT 相比,KSP 具有更高的性能和更好的 Kotlin 支持。
- ksp要添加相应插件和依赖,配置方式有所改变.
- ksp中room的路径要配置正确,ksp中jvm版本要对应.
- ksp的编译速度和依赖方式简直不要太安逸.