[原] Android上使用native IO


首先, 官方google play對APK大小有限制: 50M.( https://support.google.com/googleplay/android-developer/answer/113469?hl=en )

所以想通過google play發布大數據的應用的話, 得通過擴展包, 一個叫做OBB(Opaque Binary Blob)的東西, 最大可以存儲4G的數據 (國內的奇葩山寨文化就不要管提了).

OBB是app使用的數據文件, google play並不關心其內容, 下載到設備以后, 系統也不關心. 如何處理這個文件, 是由app決定的.比如app可以上傳一個mp4文件作為obb, 下載以后讀取該OBB文件,然后用mp4解碼器播放.

 

SDK預置的OBB格式, 是一個(壓縮的,可加密的)FAT磁盤鏡像, 在運行時將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失敗. 目前沒有等待,也暫時沒有遇到問題, 后面會繼續完善.

 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

更新:

理論上OBB只是APK expansion, 它可以是任何格式, app上傳和下載系統並不關心. ndk提供的mount obb(Fat32鏡像)在調試的時候問題比較多, 經常mount失敗(AOBB_STATE_ERROR_INTERNAL和AOBB_STATE_ERROR_COULD_NOT_MOUNT)

據說(google group上有人說)是版本不匹配的原因, 但是嘗試更新AndroidManifest.xml的包version和jobb -pv的版本, 比如both +1並使兩者匹配, 還是經常mount不上, 對於頻繁更新包文件用於測試的情況極為不利.

 

所以可以使用的另外一種方法是使用自定義的包格式(比如最簡單的-常用的zip格式, 貌似Android SDK在java層提供了這種格式)等, 這一點跟一般PC游戲的自定義文件包格式類似.比如暴雪的MPQ格式等等.

這樣就可以跳過mount,直接使用native IO來讀寫包文件, 因為這種方法是游戲開發中常用的方式, 所以移植起來問題不大. 只移植package系統的runtime就夠用了, 工具還是在host platform上運行打包.
目前zip壓縮格式已經經過測試可用.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM