web-dev-qa-db-ja.com

Android P-アセットからデータベースをコピーした後の「SQLite:No Such Table Error」

アプリのアセットフォルダーにデータベースを保存し、アプリを最初に開いたときに次のコードを使用してデータベースをコピーします。

inputStream = mContext.getAssets().open(Utils.getDatabaseName());

        if(inputStream != null) {

            int mFileLength = inputStream.available();

            String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

            // Save the downloaded file
            output = new FileOutputStream(filePath);

            byte data[] = new byte[1024];
            long total = 0;
            int count;
            while ((count = inputStream.read(data)) != -1) {
                total += count;
                if(mFileLength != -1) {
                    // Publish the progress
                    publishProgress((int) (total * 100 / mFileLength));
                }
                output.write(data, 0, count);
            }
            return true;
        }

上記のコードは問題なく実行されますが、データベースを照会しようとすると、SQLite:No such table exceptionが発生します。

この問題はAndroid Pでのみ発生し、Androidの以前のバージョンはすべて正常に動作します。

これはAndroid Pの既知の問題ですか、何か変更がありますか?

40
Michael J

この問題は、以前のバージョンよりもAndroid Pで頻繁にクラッシュするようですが、Android P自体のバグではありません。

問題は、String filePathに値を割り当てる行が、アセットからファイルをコピーするときに開いたままのデータベースへの接続を開くことです。

問題を修正するには、次の行を置き換えます

String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

ファイルパス値を取得してデータベースを閉じるコードを使用します。

MySQLiteOpenHelper helper = new MySQLiteOpenHelper();
SQLiteDatabase database = helper.getReadableDatabase();
String filePath = database.getPath();
database.close();

また、内部ヘルパークラスも追加します。

class MySQLiteOpenHelper extends SQLiteOpenHelper {

    MySQLiteOpenHelper(Context context, String databaseName) {
        super(context, databaseName, null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
25
rmtheis

同様の問題を抱えていたため、これを解決してSQLiteOpenHelperに追加しました

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        db.disableWriteAheadLogging();
    }

どうやらAndroid PはPRAGMA Logを別のものに設定します。副作用があるかどうかはまだわかりませんが、機能しているようです!

49
Ramon Canales

Android Pに関する私の問題は、以下のようにcreateDataBase()メソッドのthis.getReadableDatabase()の後に「this.close()」を追加することで解決しました。

private void createDataBase() throws IOException {
    this.getReadableDatabase();
    this.close(); 
    try {           
        copyDataBase();            
    } catch (IOException e) {           
        throw new RuntimeException(e);
    }
}
27
aaru

私は同様の問題に遭遇しました。データベースをコピーしていましたが、アセットからはコピーしていませんでした。私が見つけたのは、問題がデータベースファイルのコードのコピーとはまったく関係がないということです。また、ファイルを開いたままにし、閉じず、フラッシュまたは同期する必要もありませんでした。私のコードは通常、既存の開かれていないデータベースを上書きします。 Android Pieと以前のAndroidのリリースとは異なると思われるのは、Android PieがSQLiteデータベースを作成するとき、journal_modeをWAL(先読みロギング)に設定することです)、デフォルトで。私はWALモードを使用したことがないので、SQLiteのドキュメントでは、journal_modeはデフォルトでDELETEであると書かれています。問題は、既存のデータベースファイルを上書きする場合、my.dbと呼びましょう。先書きログmy.db-walはまだ存在し、新しくコピーされたmy.dbファイルの内容を効果的に「上書き」します。データベースを開いたとき、sqlite_masterテーブルには通常、Android_metadataの行のみが含まれていました。私が期待していたすべてのテーブルが欠落していました。私の解決策は、特にAndroid Pieで新しいデータベースを作成する場合、データベースを開いた後にjournal_modeを単にDELETEに戻すことです。

PRAGMA journal_mode = DELETE;

おそらく、WALの方が優れており、おそらくデータベースを閉じる方法があるので、先行書き込みログが邪魔にならないようにしていますが、実際にはWALは必要なく、Androidのすべての以前のバージョンには必要ありません。

6
KGBird

残念ながら、受け入れられた答えは非常に具体的な場合に「うまくいく」だけですが、Android 9でのこのようなエラーを回避するための一貫した作業アドバイスを与えるものではありません。

ここにあります:

  1. データベースにアクセスするには、アプリケーションにSQLiteOpenHelperクラスの単一インスタンスを用意します。
  2. データベースの書き換え/コピーが必要な場合は、このインスタンスのSQLiteOpenHelper.close()メソッドを使用してデータベースを閉じ(およびこのデータベースへのすべての接続を閉じ)、このSQLiteOpenHelperインスタンスを使用しないでください。

Close()を呼び出した後、データベースへのすべての接続が閉じられるだけでなく、追加のデータベースログファイルがメインの.sqliteファイルにフラッシュされ、削除されます。したがって、リライトまたはコピーの準備ができているdatabase.sqliteファイルは1つだけです。

  1. コピー/書き換えなどの後、SQLiteOpenHelperの新しいシングルトンを作成します。getWritableDatabase()メソッドは、SQLiteデータベースの新しいインスタンスを返します!そして、次回データベースのコピー/書き換えが必要になるまで使用してください...

この答えは、私がそれを理解するのに役立ちました: https://stackoverflow.com/a/35648781/29771

AndStatusアプリケーションのAndroid 9でこの問題が発生しました https://github.com/andstatus/andstatus これには、「SQLiteException:noこのコミットの前のAndroid 9エミュレーターのそのようなテーブル」: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538 本当に興味があれば、実行できますこのコミットの前後のすべてのテストは、違いを確認するためにコミットします。

2
yvolk

WALを無効にしないソリューション

Android 9と呼ばれるSQLiteDatabaseの特別なモードを導入しますCompatibility WAL(先書きロギン)は、データベースが「journal_mode = WAL」を使用できるようにし、最大値を維持する動作を保持しますデータベースごとに1つの接続。

詳細はこちら:
https://source.Android.com/devices/tech/perf/compatibility-wal

SQLite WALモードの詳細は次のとおりです。
https://www.sqlite.org/wal.html

公式ドキュメントの時点で、WALモードは2番目のデータベースファイルを追加しますdatabasenameおよび "-wal"。したがって、データベースの名前が「data.db」の場合、同じディレクトリ内の「data-wal.db」と呼ばれます。

現在の解決策は、Android 9上のBOTHファイル(data.dbおよびdata-wal.db)を保存および復元することです。

その後、以前のバージョンと同様に機能します。

2
chrisonline

同様の問題、Android Pデバイスのみが影響を受けます。以前のバージョンはすべて問題ありません。

Android 9デバイスの自動復元をオフにしました。

トラブルシューティングのためにこれを行いました。生産ケースにはお勧めしません。

自動復元では、データベースヘルパーでデータベースのコピー機能が呼び出される前に、データベースファイルのコピーがデータディレクトリに配置されていました。したがって、a file.exists()はtrueを返しました。

開発デバイスからバックアップされたデータベースにテーブルがありませんでした。したがって、「テーブルが見つかりません」は実際には正しいものでした。

1
Berry Wing

まず、この質問を投稿していただきありがとうございます。同じことが起こった。すべて正常に機能していましたが、Android P Previewをテストするとクラッシュしました。このコードで見つけたバグは次のとおりです。

private void copyDatabase(File dbFile, String db_name) throws IOException{
    InputStream is = null;
    OutputStream os = null;

    SQLiteDatabase db = context.openOrCreateDatabase(db_name, Context.MODE_PRIVATE, null);
    db.close();
    try {
        is = context.getAssets().open(db_name);
        os = new FileOutputStream(dbFile);

        byte[] buffer = new byte[1024];
        while (is.read(buffer) > 0) {
            os.write(buffer);
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw(e);
    } finally {
        try {
            if (os != null) os.close();
            if (is != null) is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

私が遭遇した問題は、このコードが正常に機能することでしたが、SDK 28以降ではopenOrCreateDatabaseは自動的にAndroid_metadataテーブルを作成しません。したがって、「select * from TABLE」のクエリを実行すると、そのTABLEは見つかりません。これは、クエリがメタデータテーブルである「最初の」テーブルの監視を開始するためです。 Android_metadataテーブルを手動で追加することでこれを修正しましたが、すべてうまくいきました。他の誰かがこれを役に立つと思うことを願っています。特定のクエリが引き続き正常に機能するため、把握に時間がかかりました。

1
lil_matthew

出力ストリームを閉じないようです。 Android PがマルチMBバッファーを追加した場合を除き、dbが実際に作成されない理由はおそらく説明されませんが、try-with-resourceを使用することをお勧めします。

// garantees that the data are flushed and the resources freed
try (FileOutputStream output = new FileOutputStream(filePath)) {
    byte data[] = new byte[1024];
    long total = 0;
    int count;
    while ((count = inputStream.read(data)) != -1) {
        total += count;
        if (mFileLength != -1) {
            // Publish the progress
            publishProgress((int) (total * 100 / mFileLength));
        }
        output.write(data, 0, count);
    }

    // maybe a bit overkill
    output.getFD().sync();
}
0
bwt

Android PIE以上のデータベースファイルパスに次の行を使用する最も簡単な回答:

DB_NAME="xyz.db";
DB_Path = "/data/data/" + BuildConfig.APPLICATION_ID + "/databases/"+DB_NAME;
0
N_J

私は受け入れられた答えにコメントできないので、新しい答えを開かなければなりません。

mContext.getDatabasePath()はデータベース接続を開かず、成功するために既存のファイル名さえ必要としません(sources/Android-28/Android/app/ContextImpl.Javaを参照):

@Override
public File getDatabasePath(String name) {
    File dir;
    File f;

    if (name.charAt(0) == File.separatorChar) {
        // snip
    } else {
        dir = getDatabasesDir();
        f = makeFilename(dir, name);
    }

    return f;
}

private File makeFilename(File base, String name) {
    if (name.indexOf(File.separatorChar) < 0) {
        return new File(base, name);
    }
    throw new IllegalArgumentException(
            "File " + name + " contains a path separator");
}
0
Smellyfish

この問題の完璧な解決策は次のとおりです。

SQLiteOpenHelperクラスでこのメソッドをオーバーライドするだけです:

@Override
public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        db.disableWriteAheadLogging();
    }
}
0

バージョンPでの主な変更は、WAL(Write Ahead Log)です。次の2つの手順が必要です。

  1. リソースの下のvaluesフォルダーにあるconfig.xmlの次の行で同じを無効にします。

false

  1. CreateDatabaseメソッドのDBAdapterクラスで次の変更を行います。そうしないと、以前のAndroidバージョンの電話がクラッシュします。

    private void createDataBase()throws IOException {if(Android.os.Build.VERSION.SDK_INT <Android.os.Build.VERSION_CODES.P)this.getWritableDatabase();

    try {           
        copyDataBase();            
    } catch (IOException e) {           
        throw new RuntimeException(e);
    }
    

    }

0
K R Jawaharlal