Kotlin多Activity复用同一SQLiteOpenHelper实例:传参报NPE问题
崩溃根因
- 你的实现存在两个硬伤,直接触发空指针崩溃:
- 传值逻辑缺关键步骤:创建了Bundle塞了对象,但是根本没调用
intent.putExtras(args)把Bundle附加到Intent上,新Activity拿到的intent.extras本身就是null,强转的时候直接崩溃。 - 方案从根上就不可行:
SQLiteOpenHelper内部持有系统级的数据库连接、上下文引用等不可序列化的对象,就算你给类加了Serializable接口,序列化过程也会直接失败,根本没法把对象通过Intent传递。就算侥幸传成功,新Activity拿到的也是反序列化出来的全新对象,和你在Activity1创建的实例没有任何关系,根本达不到“保持数据一致”的目的。
- 传值逻辑缺关键步骤:创建了Bundle塞了对象,但是根本没调用
可行实现方案
- 不需要跨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) - 不管是Activity1还是Activity2,需要数据库实例的时候直接调用
- 这种实现的优势:
- 全局只有一个
DatabaseHelper实例,所有地方访问的都是同一个数据库连接,完全满足你要的“跨Activity获取一致用户数据、根据当前登录用户展示差异化界面”的需求。 - 用
applicationContext初始化单例,不会持有Activity上下文导致内存泄漏。 - 彻底避开Intent序列化传递不可序列化对象的崩溃问题。
- 全局只有一个
代码附带的其他问题
- 原登录查询逻辑是直接拼接SQL字符串,存在SQL注入风险,课程项目可以先运行,但最好替换成参数化查询的写法。
- 原
isUsing表的外键引用了machines表不存在的id字段,会触发建表失败,已经在上面的单例代码里修正。
内容的提问来源于stack exchange,提问作者Pers
相关产品推荐
相关产品推荐

