【问题标题】:Cursor Returning null After DB Overwritten数据库覆盖后光标返回 null
【发布时间】:2012-11-06 06:31:19
【问题描述】:

我的应用有一个静态数据库,我需要使用新内容对其进行更新。架构不会改变。我已将数据库放入assets/,当应用程序启动它时,它会将数据库(在SQLiteOpenHelper 构造函数中)复制到应用程序的databases/ 目录中。复制代码如下:

private void importAssets(boolean overWrite) throws IOException
{
    AssetManager am = mCtx.getAssets();
    InputStream in = am.open(DB_NAME);
    FileOutputStream out;
    byte[] buffer = new byte[1024];
    int len;
    File dir, db;

    dir = new File(DB_DIR);
    dir.mkdirs();
    db = new File(dir, DB_NAME);

    // if not first run return
    if (!overWrite && db.exists())
    {
        Log.i(TAG, DB_DIR + "/" + DB_NAME + " exists, do nothing");
        in.close();
        return;
    }

    // copy the DB from assets to standard DB dir created above
    out = new FileOutputStream(db);
    while ((len = in.read(buffer)) > 0)
    {
        out.write(buffer, 0, len);
    }

    out.flush();
    out.close();
    in.close();

    if (!db.exists())
    {
        Log.e(TAG, DB_DIR + "/" + DB_NAME + " does not exist");
    }

}

变量overWrite 是告诉importAssets() 是否覆盖现有数据库的标志。如果在SQLiteOpenHelper.onUpgrade() 中匹配适当的条件,则仅将其设置为true。在这种情况下,FileOutputStream 会截断现有文件并用来自assets/ 的新文件覆盖它。

我这样做的原因是我有很多记录,我不想将它们硬编码到 Java 源代码中。我只想将数据库放在那里供用户访问。用户不能修改数据库,仅供查看。

主要活动是ListActivity 的子类。它从数据库中读取数据并使用CursorListArray 将其显示在列表中。这是在onResume() 中完成的,如下所示:

protected void onResume()
{
    super.onResume();

    mContent = new PocketbookContent(this);
    mDB = mContent.getReadableDatabase();

    mRows = mDB.query("book_content",
        new String[] { "id", "title" },
        null,
        null,
        null,
        null,
        "id", 
        null);

    String[] chapters = new String[mRows.getCount()];
    mRows.moveToFirst();
    for (int i = 0; i < mRows.getCount(); i++)
    {
        // assume title is in column 1
        chapters[i] = mRows.getString(1);
        mRows.moveToNext();
    }

    // set the adapter & insert data into view
    mAdapter = new ArrayAdapter<String>(this,
        R.layout.toc_row, chapters);
    setListAdapter(mAdapter);
}

mRowsmDBmContent都在onPause()关闭。

新安装应用程序时,没有问题。一切运行良好。数据库从assets/ 复制到databases/ 目录并且应用程序运行。升级时出现问题。当它将表中的数据加载到Cursor 时,第一行的第一列原来是null,应用程序崩溃并显示NullPointerException

通过调试,我已经确认第一行中的Cursornull,而所有其他行都很好。我还确认数据库完好无损,所有数据都在那里。我通过将代码添加到importAssets() 以从databases/ 目录(不是直接从assets/)复制数据库到我可以检查它的SD 卡来做到这一点。我的结论是null 行被传递给ArrayAdapter 构造函数,然后它崩溃了。这是堆栈跟踪的顶部:

D/AndroidRuntime( 6058): Shutting down VM
W/dalvikvm( 6058): threadid=1: thread exiting with uncaught exception (group=0x40015560)
E/AndroidRuntime( 6058): FATAL EXCEPTION: main
E/AndroidRuntime( 6058): java.lang.NullPointerException
E/AndroidRuntime( 6058):        at android.widget.ArrayAdapter.createViewFromResource(ArrayAdapter.java:355)
E/AndroidRuntime( 6058):        at android.widget.ArrayAdapter.getView(ArrayAdapter.java:323)
E/AndroidRuntime( 6058):        at android.widget.AbsListView.obtainView(AbsListView.java:1430)
E/AndroidRuntime( 6058):        at android.widget.ListView.makeAndAddView(ListView.java:1745)
E/AndroidRuntime( 6058):        at android.widget.ListView.fillDown(ListView.java:670)
E/AndroidRuntime( 6058):        at android.widget.ListView.fillFromTop(ListView.java:727)
E/AndroidRuntime( 6058):        at android.widget.ListView.layoutChildren(ListView.java:1598)
E/AndroidRuntime( 6058):        at android.widget.AbsListView.onLayout(AbsListView.java:1260)
E/AndroidRuntime( 6058):        at android.view.View.layout(View.java:7175)

为什么我的Cursor null 是第一行?我认为数据库复制/覆盖没有问题,因为它在全新安装时工作正常,并且在升级时复制的数据库在使用sqlite3 检查时完好无损。

目标 API 级别是10

编辑:这里是toc_row.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#000000"
    android:padding="3px"
    android:textSize="27dp"
    android:textColor="#ffffff" />

编辑:当我将调试打印放在while() 循环中时,它显示来自Cursor 的数据是null,但仅限于第一行:

V/RenderScript_jni(  195): surfaceDestroyed
I/TableOfContents( 6058): NUMROWS: 5
I/TableOfContents( 6058): 0: null
I/TableOfContents( 6058): 1: Introduction
I/TableOfContents( 6058): 2: The Police and Arrest
I/TableOfContents( 6058): 3: Being Detained
I/TableOfContents( 6058): 4: When You are Arrested
D/AndroidRuntime( 6058): Shutting down VM

注意,TableOfContents 是主要活动,是 ListActivity 的子类。

编辑onUpgrade() 方法。它调用importAsssets() 并将overWrite 设置为true

public void onUpgrade(SQLiteDatabase db, int oldVer, int newVer)
{  
    if (ORIG_REL_SCHEMA_VER == oldVer &&
        DB_SCHEMA_VER == newVer)
    {  
        try
        {  
            importAssets(true);
        }
        catch (IOException ioe)
        {  
            Log.e(TAG, "importAssets() exception" + ioe.getMessage());
        }
    }
}

编辑(发现一个杂物):我发现了一种解决方法,可以防止崩溃并允许数据库升级。它实际上并没有解决问题,但至少对用户来说并不难看。我通过添加File.delete() 调用修改了importAssets()

if (!overWrite && db.exists())
{
    Log.i(TAG, DB_DIR + "/" + DB_NAME + " exists, do nothing");
    in.close();
    return;
}
else if (overWrite)
{
    db.delete();
}

由于overWrite 仅在从onUpgrade() 调用importAssets() 时才为真,这只会发生一次。它删除现有数据库并从assets/ 复制新数据库。根据我的测试,File.delete() 在对象被销毁之前实际上不会生效,因此事件并没有按照代码的建议实际发生。第一次调用TableOfContents.onResume()ArrayAdapter 会显示旧数据,但下次调用时,会显示新数据。

不管怎样,这是我的难题,因为似乎无法解释为什么 Cursor 在第 1 行得到 null 值,而实际上那里有数据。

【问题讨论】:

  • "升级时出现问题。"请发布您的onUpgrade() 方法。
  • 所以...封装在/assets 中的数据库not 在第一行有任何null 值,但如果onUpgrade() 被称为null值出现在第一行并且现有行仍然存在但向上推了一个索引?
  • assets/ 中的 DB 没有 null 值,这是正确的。无论onUpgrade() 是否被调用,数据库本身都没有null 值。只是当Cursor 尝试从数据库中读取数据时(在调用onUpgrade() 之后),它会为第一行获取null 值(从哪里来?)。 Cursor 中的所有剩余行都是它们应该在的位置和位置。
  • 光标不返回任何不存在的行。如果它存在于游标中,则它存在于数据库中...您可以使用WHERE title IS NULL 删除此行。您还可以更改查询或使用 CursorAdapter 跳过任何 null 值。
  • 上面输出中显示null 的行确实有数据。这不是幻影行。应该显示为“进口通知......”的值所以我不能把它扔掉。虽然我可能会对此进行调查,看看我是否发现任何有用的东西。

标签: android android-listview listadapter android-sqlite android-cursor


【解决方案1】:

上面输出中显示为 null 的行确实有数据。这不是幻影行。应显示为“进口通知...”的值

那么最简单的解决方案是跳过带有null 标题的行:

mRows = mDB.query("book_content",
    new String[] { "id", "title" },
    "WHERE title IS NOT NULL",
    null,
    null,
    null,
    "id", 
    null);

这样它就不会让您的应用崩溃,并且您可以保留通知。

【讨论】:

  • 当我这样做时,它不会崩溃,但数据库中的第一行丢失了。这当然是有道理的,因为无论出于何种原因,Cursor 都无法读取第一行。
猜你喜欢
  • 1970-01-01
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
相关资源
最近更新 更多