Android pre-populated Database

5 minute read

Today I am going to show you how to create an android application which comes with a pre-populated database. The database file has to be stored in assets directory.

Every file that is stored in Assets directory is compressed. Prior to Android 2.3, any compressed asset file with an uncompressed size of over 1 MB cannot be read from the APK. The limit on the uncompressed size of compressed assets was removed in Android 2.3. So someday in the future, when you don’t have to worry about Android versions lower than 2.3, you can avoid this heartache. (source: Brian Hardy-Dealing with Asset Compression in Android Apps

So if you have a database file over 1 mb and you write an application that supports Android versions prior to 2.3 you have 2 options

  1. Split your database file in chunks which are smaller than 1Mb.
  2. Ship your file with an extension from the following and it will not be compressed. (This will increase your .apk file size by the size of database file):
     //extract from Package.cpp in the aapt source code, on which types of files are not compressed by default:
    
     /* these formats are already compressed, or don't compress well */
     static const char* kNoCompressExt[] = {
         ".jpg", ".jpeg", ".png", ".gif",
         ".wav", ".mp2", ".mp3", ".ogg", ".aac",
         ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
         ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
         ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
         ".amr", ".awb", ".wma", ".wmv"
     };
    

    Then create your pre-populated database file which has android_metadata table in it and insert a record. (Android creates automatically this table when you are creating your database in a traditional way in SQLiteOpenHelper onCreate() method.) : ```sql – create table CREATE TABLE android_metadata (locale TEXT );

– insert record INSERT INTO android_metadata VALUES(‘en_US’);

Then create your class which extends SQLiteOpenHelper like this:

```java
public class DatabaseHelper extends SQLiteOpenHelper {

private static final String TAG = "DatabaseHelper";
private static final int DATABASE_VERSION = 1;

// database file name.
private static final String DB_NAME = "mp.jet";

private final Context myContext;

private static SQLiteDatabase myWritableDb;

/**
 * Constructor takes and keeps a reference of the passed context in order to
 * access to the application assets and resources.
 * 
 * @param context
 *            the application context
 */
public DatabaseHelper(Context context) {

	super(context, DB_NAME, null, 1);
	this.myContext = context;
}

@Override
public void onCreate(SQLiteDatabase db) {
}

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

/**
 * Returns a writable database instance in order not to open and close many
 * SQLiteDatabase objects simultaneously
 * 
 * @return a writable instance to SQLiteDatabase
 */
public SQLiteDatabase getMyWritableDatabase() {
	if (myWritableDb == null || (!myWritableDb.isOpen())) {
		myWritableDb = this.getWritableDatabase();
	}

	return myWritableDb;
}

@Override
public void close() {
	super.close();
	if (myWritableDb != null) {
		myWritableDb.close();
		myWritableDb = null;
	}
}

/**
 * Creates an empty database on the system and rewrites it with your own
 * database.
 * */
public void createDataBase() throws IOException {

	boolean dbExist = databaseExists();

	if (dbExist) {
		// do nothing , database already exists
	} else {

		/**
		 * By calling this method and empty database will be created into
		 * the default system path of your application so we are gonna be
		 * able to overwrite that database with our database.
		 */
		this.getReadableDatabase();

		// release all opened database objects
		// if you omit this line you will get a "no such table exception"
		// and
		// the application will crash ONLY in the first run.
		this.close();

		try {

			copyDataBase();
		} catch (IOException e) {

			throw new Error("Error copying database");

		}
	}

}

/**
 * Check if the database already exist to avoid re-copying the file each
 * time you open the application.
 * 
 * @return true if it exists, false if it doesn't
 */
private boolean databaseExists() {

	SQLiteDatabase checkDB = null;

	try {
		String myPath = getDatabasePath();
		checkDB = SQLiteDatabase.openDatabase(myPath, null,
				SQLiteDatabase.OPEN_READONLY);

	} catch (SQLiteException e) {
		// database does not exist yet.
	}

	if (checkDB != null) {
		checkDB.close();
	}

	return checkDB != null ? true : false;
}

/**
 * Copies your database from your local assets-folder to the just created
 * empty database in the system folder, from where it can be accessed and
 * handled. This is done by transferring bytestream.
 * */
private void copyDataBase() throws IOException {
	Log.d(TAG, "copyDatabase()");

	// Open your local db as the input stream
	InputStream myInput = myContext.getAssets().open(DB_NAME);

	// Path to the just created empty db
	String outFileName = getDatabasePath();

	// Open the empty db as the output stream
	OutputStream myOutput = new FileOutputStream(outFileName);

	// transfer bytes from the inputfile to the outputfile
	byte[] buffer = new byte[1024];
	int length;
	while ((length = myInput.read(buffer)) > 0) {
		myOutput.write(buffer, 0, length);
	}

	SQLiteDatabase checkDB = null; // get a reference to the db.

	try {

		checkDB = SQLiteDatabase.openDatabase(getDatabasePath(), null,
				SQLiteDatabase.OPEN_READWRITE);

		// once the db has been copied, set the new version..
		checkDB.setVersion(DATABASE_VERSION);
		checkDB.close();
	} catch (SQLiteException e) {
		// database does?t exist yet.
	}

	// Close the streams
	myOutput.flush();
	myOutput.close();
	myInput.close();

}

/**
 * Get absolute path to database file. The Android's default system path of
 * your application database is /data/data/&ltpackage
 * name&gt/databases/&ltdatabase name&gt
 * 
 * @return path to database file
 */
private String getDatabasePath() {
	// The Android's default system path of your application database.
	// /data/data/<package name>/databases/<databasename>
	return myContext.getFilesDir().getParentFile().getAbsolutePath()
			+ "/databases/" + DB_NAME;
}

Comments