我的应用有一个静态数据库,我需要使用新内容进行更新。架构不会改变。我已将数据库放入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()
是否覆盖现有数据库的标志。如果在true
中匹配了相应的条件,则此设置仅设为SQLiteOpenHelper.onUpgrade()
。在这种情况下,FileOutputStream
会截断现有文件并使用assets/
中的新文件覆盖它。
我这样做的原因是我有很多记录,我宁愿不硬编码到Java源代码中。我只想将数据库放在那里供用户访问。用户无法修改数据库,仅供查看。
主要活动是ListActivity
的子类。它从数据库中读取数据,并使用Cursor
和ListArray
将其显示在列表中。这在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);
}
mRows
,mDB
和mContent
都已在onPause()
中关闭。
当新安装应用程序时,没有问题。一切都运行良好。数据库从assets/
复制到databases/
目录,应用程序运行。升级时会出现此问题。当它将表格中的数据加载到Cursor
时,第一行的第一列结果为null
,应用程序崩溃为NullPointerException
。< / p>
通过调试,我已经确认第一行Cursor
中出现的内容是null
,而所有其他行都没问题。我还确认数据库完好无损,所有数据都在那里。我这样做是通过向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());
}
}
}
编辑(发现了一个kludge):我发现了一种可以防止崩溃并允许数据库升级的解决方法。它实际上并没有解决问题,但至少它对用户来说并不难看。我通过添加importAssets()
电话修改了File.delete()
:
if (!overWrite && db.exists())
{
Log.i(TAG, DB_DIR + "/" + DB_NAME + " exists, do nothing");
in.close();
return;
}
else if (overWrite)
{
db.delete();
}
由于overWrite
仅在从importAssets()
调用onUpgrade()
时才会生效,因此这只会发生一次。它会删除现有数据库并从assets/
复制新数据库。根据我的测试,File.delete()
实际上不会在对象被销毁之前生效,因此事件实际上并不像代码所建议的那样发生。第一次调用TableOfContents.onResume()
时,ArrayAdapter
将显示旧数据,但在下一次调用时,它将显示新数据。
无论如何,这是我的kludge,因为似乎没有解释为什么Cursor
获得第1行的null
值,而实际上有数据存在。
答案 0 :(得分:0)
上面输出中显示null的行确实有数据。这不是一个幻影行。应显示为“导入通知......”的值
然后最简单的解决方案是跳过null
个标题的行:
mRows = mDB.query("book_content",
new String[] { "id", "title" },
"WHERE title IS NOT NULL",
null,
null,
null,
"id",
null);
这样一来,它不会让你的应用程序崩溃,你可以保持通知。