I used sqlite3 on my Mac to create a small database file (surnamed ".db") containing one table, plus the android_metadata table containing "en_US". I pasted the file into the assets folder of my Android Studio project. At runtime, the onCreate method of my SQLiteOpenHelper copies the file into /data/data/projectname/databases/filename.db on my Genymotion emulator (a Samsung Galaxy S5). But when I try to read the table in filename.db with the Cursor provided by the getReadableDatabase method of the SQLiteOpenHelper, I get the LogCat error "E/SQLiteLog﹕ (1) no such table: tablename". The same thing happens on my el-cheapo Azpen A727 Android tablet.
The filename.db file copied into the /data/data/projectname/databases folder on the emulator contains the same number of bytes (5120) as the original filename.db I created on my Mac with sqlite3. When I compare the two .db files with the Unix octal dump command "od -c", I found that they are identical for the first 16 bytes, and differ for the first time at the 17th byte. (The original file has octal 004 as its seventeenth byte, and the copy has 020.) Should the two files have had identical contents? When I try to examine /data/data/projectname/databases/filename.db with sqlite3 .dump, I get "ERROR: (11) database disk image is malformed".
The getReadableDatabase method of my SQLiteOpenHelper triggers a call to the onCreate method of the helper. In onCreate, I copy filename.db from the assets folder to the /data/data/projectname/databases directory. At the end of onCreate, I verified that the 17th byte in both copies of filename.db is 004.
I added an onOpen method to my SQLiteOpenHelper. It is called automatically by getReadableDatabase after onCreate, and does nothing except calling super.onOpen. After calling super.onOpen, my onOpen prints the 17th byte of both copies of filename.db. The byte in assets remains 004, but the byte in /data/data/projectname/databases has become 020. (The damage also occurs if onOpen does not call super.onOpen.)
What sinister force might be damaging /data/data/projectname/databases/filename.db between the end of the call to onCreate and the start of the call to onOpen? Or is the problem something else entirely? Thank you very much.
package edu.nyu.scps.offer;
import android.content.Context;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Helper extends SQLiteOpenHelper {
private static final String DB_NAME = "offers.db";
private Context context;
public Helper(Context context) {
super(context, context.getDatabasePath(DB_NAME).getPath(), null, 1);
this.context = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
try {
AssetManager assetManager = context.getAssets();
InputStream inputStream = assetManager.open(DB_NAME); //in assets folder
OutputStream outputStream = new FileOutputStream(getDatabaseName());
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (IOException exception) {
Log.e("myTag", "exception " + exception);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
};
Here is a .dump of the file I created by hand using sqlite3:
sqlite3 offers.db .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE android_metadata (
"locale" text default "en_US"
);
INSERT INTO "android_metadata" VALUES('en_US');
CREATE TABLE offers (
_id integer primary key autoincrement,
title text,
description text
);
INSERT INTO "offers" VALUES(1,'Prewar Castle','It reminds me of the broken battlements of my own castle in Transylvania.');
INSERT INTO "offers" VALUES(2,'Shelter Island','heated saltwater pool, tennis, professional landscaping, finished lower level.');
INSERT INTO "offers" VALUES(3,'Amagansett','Heated pool, room for tennis, adjacent to nature conservancy, provate beach and pier rights.');
INSERT INTO "offers" VALUES(4,'Sagaponack Village','Insulate yourself from the barbarians at the hedgerow.');
INSERT INTO "offers" VALUES(5,'Amagansett Dunes','Close proximity to ocean beaches, oversized hot tub, 2 fireplaces. 4 bedrooms, 2.5 baths, .17 acres.');
INSERT INTO "offers" VALUES(6,'East Hampton Village','Outdoor shower, 2 car garage, fireplace, close to village and ocean beaches.');
INSERT INTO "offers" VALUES(7,'Yonkers','Batcave with stunning views of ice drifting down the Hudson. Hiking on Old Croton Aqueduct. Floods when Saw Mill River rises.');
INSERT INTO "offers" VALUES(8,'Tarrytown','Listen to the music of the New York State Thruway.');
INSERT INTO "offers" VALUES(9,'East Village','Pay extortion for a shoebox!');
INSERT INTO "offers" VALUES(10,'Poughkeepsie','Ranch-style living in chip plant recently vacated by a shrinking IBM.');
DELETE FROM sqlite_sequence;
INSERT INTO "sqlite_sequence" VALUES('offers',10);
COMMIT;
April 15, 2015: I discovered why my .db file was corrupted. When the getReadableDatabase method of class SQLiteOpenHelper called onCreate, the .db file had already been created and was even in the middle of a database transaction. The code in my onCreate overwrote this file completely. At some later time the SQLiteOpenHelper attempted to complete the transaction, but the .db file with which it started the transaction has been destroyed. Solution: the .db file must be copied from the project's assets before getReadableDatabase is called. The most straightforward approach would be to copy it in the constructor for my subclass of SQLiteOpenHelper. But Android's documentation says that SQLiteOpenHelper's constructor must "return very quickly" and defer the creation of the .db file to getReadableDatabase. So I ended up overriding getReadableDatabase with a new method that copies the .db file and then calls the getReadableDatabase of the superclass SQLiteOpenHelper. super.getReadableDatabase will not attempt to create a new .db file if it finds the file already in existence. Details omitted. I debugged my original code only because I was curious to know why it corrupted the .db file. In real life I'm going to use SQLiteAssetHelper, and I am grateful for the suggestion.