首先, 官方google play对APK大小有限制: 50M.( https://support.google.com/googleplay/android-developer/answer/113469?hl=en )
所以想通过google play发布大数据的应用的话, 得通过扩展包, 一个叫做OBB(Opaque Binary Blob)的东西, 最大可以存储4G的数据 (国内的奇葩山寨文化就不要管提了).
其实OBB就是一个(压缩的,可加密的)FAT32磁盘镜像, 在运行时将OBB挂载到/mnt/XXXX/ 的位置 (XXXX是系统自动生成的字符串路径).
挂载完成以后, 记录下来挂载的位置, 就可以使用 native API 来读取文件了, 这样就可以完全脱离Java的AssetManager或者ZLib.
如何生成OBB文件呢? ADT工具下的jobb就可以.
adt-x86\sdk\tools>jobb -d E:\bin\data -o E:\bin\main.1.com.games.xxx.obb -pv 1 -pn com.games.xxx -k pswd
可以看出可以对OBB加密, 密码为"pswd"
不过这里Jobb有几个bug: 指定的文件夹太小, jobb直接崩溃. 当然它是用于大数据包的, 但是小文件测试也不行?
如果指定的文件夹是10M, 那么可以生成OBB, 但是在android上加载不了, 设备重启也加载不了, 错误代码为无法加载.
将OBB填到20M,
最后可以了.
然后就是code了, AStorageManager就是来管理obb的.
挂载OBB是异步的, 而且需要提供callback function和callback data(可选).callback data是用户定义的,可以是任何数据, 比如我这里是一个app结构指针:
static void Android_ObbCallbackFunc(const char* filename, const int32_t state, void* data) { Android_App* app = (Android_App*)data; if( state == AOBB_STATE_MOUNTED ) { int isMounted = ::AStorageManager_isObbMounted(app->storage, filename); assert( isMounted != 0 ); const char* mntPath = ::AStorageManager_getMountedObbPath(app->storage, filename); //save persistent path data - current NDK returns tmp string that may even corrupted right after AStorageManager_getMountedObbPath() return //https://code.google.com/p/android/issues/detail?id=41983 static char mountPath[PATH_MAX]; app->storageRoot = strcpy(mountPath, mntPath); LOGI("OBB mounted: %s", filename); } else if( state == AOBB_STATE_UNMOUNTED ) LOGI("OBB unmounted: %s", filename); else if( state != AOBB_STATE_ERROR_NOT_MOUNTED ) LOGI("Android_ObbCallbackFunc: %d", state); }
从上面的回调函数可以看到, 如果挂载成功, 就将挂载后的路径保存到app->storageRoot里, 后面的文件读取就可以使用这个路径了.
不过AStorageManager_getMountedObbPath有bug, 好像是r8的bug了, 现在已经r9了, 但我这儿有时候偶尔还是会返回乱码或者空字符串.
下面是OBB初始化的代码, 指定密码和回调函数, 以及回调数据:
static void Android_InitStorage(Android_App* app) { ANativeActivity* activity = app->activity; assert(activity != NULL && activity->obbPath != NULL); assert(app->storage == NULL); app->storage = AStorageManager_new(); const char* obbPath = activity->obbPath; ::AStorageManager_unmountObb(app->storage, obbPath, 1, Android_ObbCallbackFunc, NULL); //it‘s a async call: handle final mount path in callbacks ::AStorageManager_mountObb(app->storage, obbPath, "pswd", Android_ObbCallbackFunc, app); }
记得好像看过断点时的栈, 回调是在线程里调用的, 所以需要注意线程安全.
另外, 如果挂载速度不可控, 那么主线程可能需要挂起等待, 否则如果主线程跑的足够快已经开始读取, 而mount还没有完成的话, 可能会导致IO失败. 目前没有等待,也暂时没有遇到问题, 后面会继续完善.