You need to enable JavaScript to run this app. Kotlin多Activity复用同一SQLiteOpenHelper实例:传参报NPE问题
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin多Activity复用同一SQLiteOpenHelper实例:传参报NPE问题

崩溃根因
  • 你的实现存在两个硬伤,直接触发空指针崩溃:
    1. 传值逻辑缺关键步骤:创建了Bundle塞了对象,但是根本没调用intent.putExtras(args)把Bundle附加到Intent上,新Activity拿到的intent.extras本身就是null,强转的时候直接崩溃。
    2. 方案从根上就不可行:SQLiteOpenHelper内部持有系统级的数据库连接、上下文引用等不可序列化的对象,就算你给类加了Serializable接口,序列化过程也会直接失败,根本没法把对象通过Intent传递。就算侥幸传成功,新Activity拿到的也是反序列化出来的全新对象,和你在Activity1创建的实例没有任何关系,根本达不到“保持数据一致”的目的。
可行实现方案
  • 不需要跨Activity传DatabaseHelper实例,直接把它改造成全局单例,整个应用生命周期内只存在一个DatabaseHelper实例,所有Activity拿到的都是同一个对象,天然保证数据库访问的一致性。
    改造后的核心代码:
class DatabaseHelper private constructor(context: Context): SQLiteOpenHelper(context.applicationContext, DB_NAME, null, DB_VERSION) {
    companion object {
        private const val DB_NAME = "database"
        private const val DB_VERSION = 1

        // 全局唯一单例实例
        @Volatile
        private var instance: DatabaseHelper? = null

        fun getInstance(context: Context): DatabaseHelper {
            // 双重校验锁保证线程安全,不会创建多个实例
            return instance ?: synchronized(this) {
                instance ?: DatabaseHelper(context).also { instance = it }
            }
        }
    }

    override fun onCreate(db: SQLiteDatabase?) {
        db?.execSQL("CREATE TABLE users(email VARCHAR(30) PRIMARY KEY,password VARCHAR(10))")
        db?.execSQL("CREATE TABLE machines(name VARCHAR(30) PRIMARY KEY, programmes VARCHAR(10), available CHAR(1))")
        // 修正原建表bug:原语句引用了machines表不存在的id字段,会直接建表失败
        db?.execSQL("CREATE TABLE isUsing(machineID REFERENCES machines(name),userID REFERENCES users(email), PRIMARY KEY (machineID,userID))")
    }

    // 你原来写的userPresent、insertMachine、insertUserData方法直接保留即可
    // 注意:单例模式下不要在写完操作后随意close数据库连接,频繁开关连接会有性能损耗,甚至触发数据库锁异常
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        TODO("Not yet implemented")
    }
}
  • 调用方式:
    • 不管是Activity1还是Activity2,需要数据库实例的时候直接调用DatabaseHelper.getInstance(this)就行,不需要通过Intent、Bundle传递任何对象。
    • 跳转Activity的时候恢复最简洁的Intent创建逻辑,不需要塞任何和dbHelper相关的参数:
    val intent = Intent(this, AdminMainActivity::class.java)
    startActivity(intent)
    
  • 这种实现的优势:
    • 全局只有一个DatabaseHelper实例,所有地方访问的都是同一个数据库连接,完全满足你要的“跨Activity获取一致用户数据、根据当前登录用户展示差异化界面”的需求。
    • 用applicationContext初始化单例,不会持有Activity上下文导致内存泄漏。
    • 彻底避开Intent序列化传递不可序列化对象的崩溃问题。
代码附带的其他问题
  • 原登录查询逻辑是直接拼接SQL字符串,存在SQL注入风险,课程项目可以先运行,但最好替换成参数化查询的写法。
  • 原isUsing表的外键引用了machines表不存在的id字段,会触发建表失败,已经在上面的单例代码里修正。

内容的提问来源于stack exchange,提问作者Pers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.08 11:36:09