0

私のアプリには、新しいコンテンツで更新する必要がある静的 DB があります。スキーマは変更されません。DB を入れて、アプリが起動すると、(コンストラクター内の) DB をアプリケーションのディレクトリassets/にコピーします。コピーコードは次のとおりです。SQLiteOpenHelperdatabases/

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

}

変数overWriteimportAssets()、既存の 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);
}

mRowsmDBおよび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()と、データが から出てくることが示されますCursornull、最初の行のみ:

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

4

1 に答える 1

0

上記の出力で null を示している行には、データがあります。幻の行ではありません。「Import Notice...」として表示される値

null次に、最も簡単な解決策は、タイトルのある行をスキップすることです:

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

これにより、アプリがクラッシュせず、通知を保持できます。

于 2012-11-17T22:08:44.780 に答える