4

当我将值插入 Android 上的 SQLite 表时,我收到 NullPointerException,但我不明白为什么。我正在测试 ContentValues 和数据库实例是否为空。

这是插入代码:

public void insertOrIgnore(ContentValues values) {

    SQLiteDatabase db = this.dbHelper.getWritableDatabase();

    try {

        //I added these null value checks to stop NPE, but doesn't help.
        if (values != null && db != null) {
            db.insertWithOnConflict(TABLE, null, values, SQLiteDatabase.CONFLICT_IGNORE);
        }
    } catch (SQLiteException e) {

    } finally {
        if (db != null) {
            db.close();
        }
    }
} 

在哪里

public static final String TABLE = "albums";

大多数情况下,此代码可以按预期处理添加到数据库中的数据。但是,它有时而且很少会产生以下错误。堆栈跟踪来自 ACRA,我无法隔离在什么情况下会发生此错误。我正在寻找关于为什么会发生这种情况以及条件是什么的指针。我对 SQLite 的了解是初级水平。

java.lang.NullPointerException
    at android.database.sqlite.SQLiteStatement.releaseAndUnlock(SQLiteStatement.java:290)
    at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:96)
    at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:2025)
    at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1965)
    at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:690)
    at android.database.sqlite.SQLiteDatabase.beginTransactionNonExclusive(SQLiteDatabase.java:605)
    at android.database.sqlite.SQLiteStatement.acquireAndLock(SQLiteStatement.java:247)
    at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:112)
    at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1844)
    at com.mydomain.myapp.albums.AlbumsData.insertOrIgnore(AlbumsData.java:89)

第 89 行是上面显示的 db.insertWithOnConflict(...) 调用。

我不一定要寻找完整代码的答案,而是寻找问题所在的指针和解释,以便我自己开始修复它。

编辑:堆栈跟踪显示 NPE 源自SQLiteStatement (v 4.03) 的第 290 行:

 setNativeHandle(mDatabase.mNativeHandle);

所以看起来数据库实例为空。当我在交易开始时测试 null 时,它如何在交易期间变为 null?

4

2 回答 2

2

如此处所述SQLiteDatabase close() 函数在多个线程时导致 NullPointerException

您的错误的原因可能是您在某个时候关闭了数据库。可能在失败的任务尚未完成的同时。

我已经稍微关注了堆栈跟踪,这就是大致发生的情况:

  • AlbumsData.insertOrIgnore(AlbumsData.java:89)
    您调用insertWithOnConflict,它构建生成的 sql 字符串 ( "INSERT OR IGNORE INTO..."),然后将其与您的值一起包装ContentValuesSQLiteStatement.
  • SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1844)- 生成的语句现在要执行
  • SQLiteStatement.executeInsert(SQLiteStatement.java:112)- 在实际插入发生之前,数据库需要获取锁。
  • SQLiteStatement.acquireAndLock(SQLiteStatement.java:247)- 这里发生了一些检查,据我所知,数据库对象当时不为空。代码决定它必须启动一个事务。据我所知,数据库对象本身当时没有锁定。
  • SQLiteDatabase.beginTransactionNonExclusive(SQLiteDatabase.java:605)- 只是转发
  • SQLiteDatabase.beginTransaction(SQLiteDatabase.java:690)- 经过一些检查(不确定数据库是否必须存在于此),它将尝试执行execSQL("BEGIN IMMEDIATE;")
  • SQLiteDatabase.execSQL(SQLiteDatabase.java:1965)- 只是向前
  • SQLiteDatabase.executeSql(SQLiteDatabase.java:2025)-建立另一个SQLiteStatement"BEGIN IMMEDIATE;这个应该现在执行
  • SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:96)- 从检查数据库锁开始,这似乎没问题,这里的数据库不应该为空。然后执行该语句,最后再次解锁数据库。
  • SQLiteStatement.releaseAndUnlock(SQLiteStatement.java:290)- 清理一些东西,最后因为数据库是 NPE 而失败null

行号不匹配,因此该代码中可能存在供应商修改/添加。

如您所见,代码在实际使用您提供的数据之前崩溃。它即将做

BEGIN TRANSACTION IMMEDIATE; -- crash
INSERT INTO table (...) VALUES (...);
-- (end transaction)

在我看来,这使它成为一个框架错误。在那里内部处理的数据库对象不应该null位于某个地方,尤其是当它似乎在堆栈中更靠前时不为空时。

我还认为另一个隐藏的异常可能是其根本原因。代码中有很多try { /* do stuff */ } finally { /* clean up */ }块,finally即使该try部分抛出异常,该部分也会被执行。现在该finally块可能会导致另一个异常,结果是 AFAIK,原始异常被 finally 块中的新异常替换。

尤其executeUpdateDelete()是像

try {
    acquireAndLock(WRITE);
    // actual statement execution
} finally {
    releaseAndUnlock();
}

如果此时数据库已关闭,acquireAndLock或者该部分中的任何代码都可能失败,并且可能会留下导致再次失败try的数据库对象。你应该得到相同的堆栈跟踪。nullreleaseAndUnlock

除此之外,不要像catch (SQLiteException e) { /* empty */ }. 如果可能的话,用 ACRA 记录它们/你还没有这样做。

于 2012-11-26T16:53:25.903 回答
1

此 NPE 似乎来自自定义 ROM,因为Android 源代码指向的方法与您在 LogCat 中收到的方法不同。对于这种情况,我所做的是:如果这些异常的发生率非常罕见,我会忽略它们,因为很难知道手机上运行的是什么自定义 ROM,而且更难获得这个自定义 ROM 的源代码。问题出在哪里。

使用自定义 ROM 的用户并不多,因此,如果您在具有不同 SDK 的不同手机上广泛测试了您的应用程序,并且您获得的异常率并不那么重要,您可以忽略它们。否则,您可以在黑暗中拍摄并推测导致 NPE 的自定义 ROM 中可能存在什么(个人认为不值得这样做)。

于 2012-11-26T13:26:23.440 回答