Android DB覆盖后返回null的游标

Android DB覆盖后返回null的游标,android,android-listview,listadapter,android-sqlite,android-cursor,Android,Android Listview,Listadapter,Android Sqlite,Android Cursor,我的应用程序有一个静态数据库,我需要用新内容更新它。架构不会更改。我已将DB放入资产/中,当应用程序启动它时,它会将DB(在SQLiteOpenHelper构造函数中)复制到应用程序的数据库/目录中。副本代码如下: private void importAssets(boolean overWrite) throws IOException { AssetManager am = mCtx.getAssets(); InputStream in = am.open(DB_NAME

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

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");
    }

}
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);
}
变量
overWrite
是一个标志,告诉
importasets()
是否覆盖现有数据库。只有在
SQLiteOpenHelper.onUpgrade()
中匹配了适当的条件时,才会将其设置为
true
。在这种情况下,
FileOutputStream
会截断现有文件,并用
assets/
中的新文件覆盖它

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

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

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");
    }

}
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);
}
为什么我的
光标中的第一行为空?我认为DB copy/overwrite没有问题,因为它在新安装时工作正常,升级时复制的DB在使用
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" />
注意,
TableOfContents
是主要活动,从
ListActivity
子类

Edit:使用
onUpgrade()方法。它调用
importassets()
,并将
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());
        }
    }
}
编辑(发现了一个混乱):我发现了一个解决方法,可以防止崩溃并允许数据库升级。它实际上并没有解决这个问题,但至少对用户来说并不难看。我通过添加一个
文件修改了
importasets()
。delete()
调用:

if (!overWrite && db.exists())
{
    Log.i(TAG, DB_DIR + "/" + DB_NAME + " exists, do nothing");
    in.close();
    return;
}
else if (overWrite)
{
    db.delete();
}
由于只有在从
onUpgrade()
调用
importasets()
时,
overWrite
才为真,因此这只会发生一次。它删除现有数据库并从
资产/
复制新数据库。根据我的测试,
File.delete()
在对象被销毁之前实际上不会生效,因此事件不会按照代码的建议实际发生。第一次调用
TableOfContents.onResume()
时,
ArrayAdapter
将显示旧数据,但在下一次调用时,它将显示新数据

无论如何,这是我的难题,因为似乎没有解释为什么
Cursor
为第1行获取
null
值,而实际上那里有数据

上面输出中显示null的行没有数据。这不是幽灵般的争吵。应显示为“导入通知…”的值

然后,最简单的解决方案是跳过标题为
null
的行:

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

这样就不会使你的应用程序崩溃,你可以保留通知。

“问题发生在升级时。”请发布你的
onUpgrade()
方法。因此。。。打包在
/assets
中的数据库在第一行中没有任何
null
值,但是如果调用
onUpgrade()
,则
null
值出现在第一行,并且现有行仍然存在,但向上推一个索引。
assets/
中的DB没有
null
值,这是正确的。无论是否调用
onUpgrade()
,数据库本身都没有
null
值。只是当
光标
尝试从数据库读取数据时(在调用
onUpgrade()
之后),它会为第一行获得一个
null
值(从何处?)。
光标
中的所有剩余行都是其应有的状态和位置。光标不会返回任何不存在的行。如果它存在于游标中,则它存在于数据库中。。。您可以使用标题为NULL的
删除此行。您还可以更改查询或使用游标适配器跳过任何
null
值。上面输出中显示
null
的行中没有数据。这不是幽灵般的争吵。应该显示为“导入通知…”的值,所以我不能将其丢弃。尽管我可能会对此进行调查,看看是否发现了任何有用的东西。当我这样做时,它不会崩溃,但数据库的第一行丢失了。这当然是有道理的,因为无论出于何种原因,
光标
,都无法读取第一行。