私のアプリには、新しいコンテンツで更新する必要がある静的 DB があります。スキーマは変更されません。DB を入れて、アプリが起動すると、(コンストラクター内の) DB をアプリケーションのディレクトリ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()
、既存の DB を上書きするかどうかを示すフラグです。これはtrue
、 で適切な条件が一致した場合にのみ に設定されSQLiteOpenHelper.onUpgrade()
ます。その場合、FileOutputStream
は既存のファイルを切り捨て、 からの新しいファイルで上書きしますassets/
。
私がこれを行っている理由は、Java ソースにハードコーディングしたくないレコードがたくさんあるからです。ユーザーがアクセスできるように、そこにDBをドロップしたいだけです。ユーザーは DB を変更できません。表示専用です。
メイン アクティビティは のサブクラスですListActivity
。DB からデータを読み取り、 と を使用してリストに表示し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()
ます。
アプリが新しくインストールされた場合、問題はありません。すべてが正常に動作します。DB がディレクトリからコピーさassets/
れdatabases/
、アプリが実行されます。アップグレード時に問題が発生。テーブルから にデータをロードするポイントに到達するCursor
と、最初の行の最初の列が であることが判明しnull
、アプリはNullPointerException
.
Cursor
デバッグを通じて、最初の行の から出てくるのはnull
であり、他のすべての行は問題ないことを確認しました。また、データベースに問題がなく、すべてのデータがそこにあることを確認しました。これを行うには、コードを追加してimportAssets()
、DB の別のコピーを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
ですか? DB のコピー/上書きに問題はないと思います。新規インストールでは問題なく動作し、アップグレード時にコピーされた 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" />
編集: デバッグ プリントをループに入れる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());
}
}
}
編集(発見されたクラッジ):クラッシュを防ぎ、DBのアップグレードを可能にする回避策を発見しました。実際に問題が解決するわけではありませんが、少なくともユーザーにとって見苦しいものではありません。呼び出し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
たときにのみ true になるので、一度だけ発生します。既存の DB を削除し、新しい DB を からコピーします。私のテストに基づいて、オブジェクトが破棄されるまで実際には有効にならないため、コードで示唆されているようにイベントは実際には発生していません。最初に呼び出されたときは古いデータが表示されますが、次の呼び出しでは新しいデータが表示されます。importAssets()
onUpgrade()
assets/
File.delete()
TableOfContents.onResume()
ArrayAdapter
とにかく、実際にはデータがあるのに行1の値をCursor
取得する理由が説明されていないように見えるので、それは私の不注意です。null