assetsディレクトリのストレージ展開
リソースとして管理したくないデータ(常時メモリ展開したくないデータ)をassetsにディレクトリ構成で配置して、起動時にストレージに丸ごとディレクトリコピーを実装してみましたが、AssetManagerの制約、ファイルサイズ制限等で色々と難儀したのでメモします。
AssetManagerのディレクトリ判定
assetsフォルダ配置内は静的に配置を目的としているためか、抽象表現であるFileが扱えない仕様となっています。問題としてはlist()で取得したパスのディレクトリ・ファイルを判別する手段がありません。
AssetManagerの主要メソッド
String[] list(String path) | 引数のパスに含まれる全ての子のパスを取得する |
InputStream open(String filename, int accessMode) | 引数のパスのInputStreamを取得する |
AssetFileDescriptor openFD(String filename) | 引数のパスのFileDescriptorを取得する |
XmlResourceParser openXmlResourceParser(String filename) | 引数のパスのXmlParserを取得する |
代替案として、パスをlist()で取得可能またはそのパスをopen不可だった場合はディレクトリと判断するようにします。(※空ディレクトリのパスの場合、open()にてFileNotFoundExceptionが発生します)
private boolean isDirectory(final String path) { boolean isDirectory = false; try { if (assetManager.list(path).length > 0){ //子が含まれる場合はディレクトリ isDirectory = true; } else { // オープン可能かチェック assetManager.open(path); } } catch (FileNotFoundException fnfe) { isDirectory = true; } return isDirectory; }
assetsからStorageへのディレクトリコピー
ディレクトリの全コピーなのでcopyのロジックは再帰メソッドで実装しますが、AssetManagerは
Fileオブジェクトが扱えないのでassetsのpathで扱うことになります。
private void copyFiles(final String parentPath, final String filename, final File toDir) { String assetpath = (parentPath != null ? parentPath + File.separator + filename : filename); if (isDirectory(assetpath)) { //ディレクトリ判定 if (!toDir.exists()) { //出力先のディレクトリ作成 toDir.mkdirs(); } for (String child : assetManager.list(assetpath)) { //再帰呼出 copyFiles(assetpath, child, new File(toDir, child)); } } else { //バイナリコピー copyData(assetManager.open(assetpath), new FileOutputStream(new File(toDir.getParentFile(), filename))); } }
非圧縮ファイルサイズ制限
Android OSではUNCOMPRESS_DATA_MAX(約1MB)で指定された以上のファイルが扱えない仕様となっています。
http://pentan.info/android/app/assets_data_max.html
1MBを超える可能性のあるファイルについては圧縮してassetに配置する必要があります。
zip解凍にはZipInputStreamを利用して展開します。
private void unzip(InputStream is, File toDir) { ZipInputStream zis = null; try { zis = new ZipInputStream(is); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { String entryFilePath = entry.getName().replace('\\', File.separatorChar); File outFile = new File(toDir, entryFilePath); if (entry.isDirectory()) { outFile.mkdirs(); } else { BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(outFile)); byte[] buffer = new byte[1024]; int len = 0; while ( (len = is.read(buffer, 0, buffer.length)) > 0) { bos.write(buffer, 0, len); } bos.flush(); } finally { if (bos != null) { try { bos.close(); } catch (IOException ioe) {} } } zis.closeEntry(); } } } finally { if (zis != null) { try { zis.close(); } catch (IOException ioe) {} } } }
サンプルコード
今回使用したサンプルコードをgithubにアップしました。
https://github.com/hmori/AssetTest
「expand data」ボタンで "assets/data" を内部ストレージ"/data/data/{パッケージ名}/app_data"に展開します。展開中、zipファイルの場合はzip展開してコピーするようにしています。StorageManagerコンストラクタの第3引数をtrueにするとSDカード(/mnt/sdcard/{パッケージ名}/data)へ展開します。
サンプルではassetsにディレクトリ構成で配置して実装していますが、実際にはディレクトリ毎zip圧縮して展開した方がシンプルになると思います。