遇到 NullPointerException 在 SQLiteDatabase.getWritableDatabase() 是 Android 開發常見痛點。本文針對您的堆疊追蹤深度分析根因,提供徹底解決方案與最佳實踐。
at com.manager.boot.r1223.VSDataSource.open(VSDataSource.java:34)
at com.manager.boot.r1223.VSDataSource.ScanVirtualSystems(VSDataSource.java:52)
at com.manager.boot.r1223.OSListActivity.onCreate(OSListActivity.java:49)
關鍵點:
VSDataSource.open() 第 34 行 → dbHelper.getWritableDatabase()OSListActivity.onCreate → ScanVirtualSystems → open()NullPointerException 而非 SQLException您的代碼片段揭示關鍵問題:
public static void open() throws SQLException {
try {
database = dbHelper.getWritableDatabase(); // dbHelper 為 static?
} catch (Exception e) { ... }
}
static 方法/欄位在類別載入時初始化,早於 Context 可用。若 dbHelper 為 static,其建構式需 Context 參數,此時 context 可能為 null。Context 會導致記憶體洩漏(特別是 Activity Context)。public class VSDataSource {
private DatabaseHelper dbHelper;
private SQLiteDatabase database;
public VSDataSource(Context context) {
dbHelper = new DatabaseHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
// 使用範例
// VSDataSource dataSource = new VSDataSource(this);
// dataSource.open();
// ... 操作資料庫 ...
// dataSource.close();
}
class DatabaseManager private constructor(@Suppress("UNUSED_PARAMETER") context: Context) {
private val dbHelper = DatabaseHelper(context.applicationContext)
private val dbLock = ReentrantReadWriteLock()
companion object {
@Volatile private var INSTANCE: DatabaseManager? = null
fun getInstance(context: Context): DatabaseManager = INSTANCE ?: synchronized(this) {
INSTANCE ?: DatabaseManager(context.applicationContext).also { INSTANCE = it }
}
}
fun read(block: (SQLiteDatabase) -> T): T {
dbLock.readLock().lock()
return try { block(dbHelper.readableDatabase) } finally { dbLock.readLock().unlock() }
}
fun write(block: (SQLiteDatabase) -> T): T {
dbLock.writeLock().lock()
return try { block(dbHelper.writableDatabase) } finally { dbLock.writeLock().unlock() }
}
}
Google 官方推薦的 ORM 框架,編譯期驗證 SQL、自動處理執行緒、支援 Flow/LiveData/RxJava:
@Entity
data class MfsItem(
@PrimaryKey val id: Int,
val name: String,
val path: String,
val type: Int,
val desc: String
)
@Dao
interface MfsDao {
@Query("SELECT * FROM mfs")
fun getAll(): Flow>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(items: List)
}
@Database(entities = [MfsItem::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun mfsDao(): MfsDao
}
您的 onUpgrade 存在 SQL 語法錯誤:
// 錯誤:缺少空格
db.execSQL("DROP TABLE IF EXISTS" + VFS_DATABASE_TABLE)
// 正確
db.execSQL("DROP TABLE IF EXISTS " + VFS_DATABASE_TABLE)
建議完整修正版:
class DatabaseHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, DATABASE_VERSION
) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_TABLE_SQL)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
companion object {
private const val DATABASE_NAME = "vfs.db"
private const val DATABASE_VERSION = 1
private const val TABLE_NAME = "mfs"
private const val CREATE_TABLE_SQL = """
CREATE TABLE $TABLE_NAME (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
path TEXT NOT NULL,
type INTEGER NOT NULL,
desc TEXT
)
""".trimIndent()
}
}
Context 傳遞正確(使用 ApplicationContext 避免洩漏)static 修飾符try-catch-finally 確保關閉連線您的 NullPointerException 核心原因是 static 方法過早存取未初始化的 dbHelper。移除 static、正確傳遞 Context、採用實例化模式或升級至 Room 即可徹底解決。現代 Android 開發強烈建議使用 Room,從根本上避免此類底層錯誤。