apkに1MB以上のデータベースファイルを含める

かなぶんの辞書DBのように、アプリに初期値としてDBを含めたい場合、assets内に保存しておき、初回起動時にdatabasesにコピーする、という方法があります。

基本的なやり方はこちらのサイトで紹介されている通りなのですが、この方法は小さいファイルなら問題ないのですが、1MB以上のファイルだとAssetManagerでExceptionが発生してしまいます。

AndroidのAssetManagerの内部では UNCOMPRESS_DATA_MAX という値が定義されていて、機種依存にはなると思いますが、現状出回っている端末では1024*1024 = 1MBに設定されています。

1MB以上のDBを扱うためには

  1. ファイルを分割する
  2. ファイルを圧縮する

の方法があります。

方法1.ファイルを分割する

最大1MBでファイルをを分割し、初回起動時に分割されたファイルを結合し、databasesに保存します。

分割は結合と逆の処理を書くか、専用ツールを使う。Linuxなら

split -b 1m

でできます。

Android側では初回起動時に下記のように結合、保存をします。

	public void extractZipFiles(String dest, String zipName) {
		try {
			AssetManager assetManager = context.getAssets();			
			InputStream inputStream = assetManager.open(zipName, AssetManager.ACCESS_STREAMING);

			ZipInputStream zipInputStream = new ZipInputStream(inputStream);
			ZipEntry zipEntry = zipInputStream.getNextEntry();
			
			while (zipEntry != null) { 
				String entryName = zipEntry.getName();
				int n;
				FileOutputStream fileOutputStream;
				fileOutputStream = new FileOutputStream(dest + "/" + entryName);             

				byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
				while ((n = zipInputStream.read(buf, 0, DEFAULT_BUFFER_SIZE)) > -1) {
					fileOutputStream.write(buf, 0, n);
				}

				fileOutputStream.close(); 
				zipInputStream.closeEntry();
				zipEntry = zipInputStream.getNextEntry();

			}
			zipInputStream.close();
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
		}
	}

方法2.ファイルを圧縮する

DBをzipで圧縮しておき、初回起動時にzipを解凍し、databases内に内容を保存します。
こうすることで、UNCOMPRESS_DATA_MAX の制限を受けずに読み込むことができます。
ただし、DBファイルを分割にしても圧縮にしても、apkはzipで圧縮されるのでapk自体のサイズ減少にはつながりません。

zipの圧縮は様々なツールが出回っているので、そのどれかを使えばいいでしょう。解凍のスニペットは以下の通りです。

	public void extractZipFiles(String dest, String zipName) {
		try {
			AssetManager assetManager = context.getAssets();			
			InputStream inputStream = assetManager.open(zipName, AssetManager.ACCESS_STREAMING);

			ZipInputStream zipInputStream = new ZipInputStream(inputStream);
			ZipEntry zipEntry = zipInputStream.getNextEntry();
			
			while (zipEntry != null) { 
				String entryName = zipEntry.getName();
				int n;
				FileOutputStream fileOutputStream;
				fileOutputStream = new FileOutputStream(dest + "/" + entryName);             

				byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
				while ((n = zipInputStream.read(buf, 0, DEFAULT_BUFFER_SIZE)) > -1) {
					fileOutputStream.write(buf, 0, n);
				}

				fileOutputStream.close(); 
				zipInputStream.closeEntry();
				zipEntry = zipInputStream.getNextEntry();

			}
			zipInputStream.close();
		} catch (Exception e) {
			Log.e(TAG, e.getMessage());
		}
	}

databases以下にコピーしたあとは通常のDBと同様に使えます。
ファイルサイズを小さくできでばそれに越したことはないですが、1MBより大きくなってしまう場合の参考にどうぞ。