なかなか気づきにくい現象ですが、SDL2のAndroidアプリケーションで特定の拡張子以外のファイルを開いた場合に、seek処理が遅くなります。
30MByte程度のファイルポインタを末尾に移動するのに数秒掛かります。
これはAssetManagerの仕様に伴うものです。
回避方法を記載します。
Table of Contents
1 SDL2のI/O処理
SDL_RWops構造体経由で実行します。以下のコードはfoo.barというファイルを開いて、ファイルポインタを末尾に移動します。
SDL_RWops *rwops; static char buffer[1024]; rwops = SDL_RWFromFile("foo.bar", "rt"); if (rwops == nullptr) return 1; SDL_RWseek(rwops, 0, RW_SEEK_END); SDL_RWclose(rwops); return 0;
SDL_RWops構造体でI/O処理の実装は隠蔽されています。
2 AndroidのSDL_RWops構造体のメンバ
Androidの場合、SDL_RWops構造体はAssetManagerを使おうと試みます。assetFileDescriptorRefにAssetManager.openFdのJNIグローバル参照を設定します。
Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence) { if (ctx->hidden.androidio.assetFileDescriptorRef) { /** AssetManager. */ } else { /** Not AssetManager. */ } return ctx->hidden.androidio.position; }
AssetManager.openFdは独自形式のファイルの場合にnullを返します(1MByteを超える場合?)。
AssetManager.openFdが成功した場合、Android_JNI_FileSeekのif文が真となり、seek関数を呼び出します。
失敗した場合、else文が実行されます。
else文ではseek関数の代わりにread関数を何度も呼び、ファイルポインタを進めることで代用します。
read関数はコピーが発生する為、seek関数に比べて相当に重くなります。
これがseek処理が重い原因です。
3 aapt
assetsディレクトリのファイルをパッケージングするツールです。
jpgやmp3等の登録された拡張子はそのままパッケージングされます。
登録されていない拡張子の場合は圧縮されます。
1MByteを超えるような大きいファイルの場合は展開されないようです。
4 custom_rules.xml
aaptはAndroid SDKのtools/ant/build.xmlのタスクとして定義されています。
各プロジェクトのcustom_rules.xmlでaaptのタスクを上書きし、登録されていない拡張子を非圧縮の対象として登録することで、1MByte以上の登録されていなかった拡張子のファイルをAssetManager.openFdで開くことができるようになります。
以下は-pre-buildでjniディレクトリをndk-buildでビルドし、-package-resourcesでaaptを実行するcustom_rules.xmlです。
<?xml version="1.0" encoding="UTF-8"?> <project> <target name="-pre-build"> <exec executable="ndk-build" failonerror="true"/> </target> <target name="-package-resources" depends="-crunch"> <!-- only package resources if *not* a library project --> <do-only-if-not-library elseText="Library project: do not package resources..." > <aapt executable="${aapt}" command="package" versioncode="${version.code}" versionname="${version.name}" debug="${build.is.packaging.debug}" manifest="${out.manifest.abs.file}" assets="${asset.absolute.dir}" androidjar="${project.target.android.jar}" apkfolder="${out.absolute.dir}" nocrunch="${build.packaging.nocrunch}" resourcefilename="${resource.package.file.name}" resourcefilter="${aapt.resource.filter}" libraryResFolderPathRefid="project.library.res.folder.path" libraryPackagesRefid="project.library.packages" libraryRFileRefid="project.library.bin.r.file.path" previousBuildType="${build.last.target}" buildType="${build.target}" ignoreAssets="${aapt.ignore.assets}"> <res path="${out.res.absolute.dir}" /> <res path="${resource.absolute.dir}" /> <nocompress extension="bar" /> <!-- <nocompress /> forces no compression on any files in assets or res/raw --> <!-- <nocompress extension="xml" /> forces no compression on specific file extensions in assets and res/raw --> </aapt> </do-only-if-not-library> </target> </project>
上の例では<nocompress extension="bar" />で.barファイルを非圧縮にしています。
複数の拡張子を登録する場合はnocompress属性を複数記述します。