Unity使用Obb擴展包的正確姿勢

由于Google Play上不能上傳大于100M的包,所以需要將應用進行Obb分包,資源文件打包到Obb中,在Apk啟動的時候再從Obb擴展文件中加載資源。

  • 如何生成Obb擴展資源文件

Unity可以自動為你進行分包操作,只需要你在發布安卓版本的時候進行簡單的設置,當然也可以自己根據需求通過以下命令進行分包

// jobb 命令在sdk\tools目錄下
jobb -pn <package> -pv <versioncode> -d \資源 -o G:\輸出包名(如main.1.com.google.obb)

obb擴展文件的命名規則為:
main文件:main.<expansion-version>.<package-name>.obb
patch文件:patch.<expansion-version>.<package-name>.obb

按照Unity分包的規則,主APK文件主要包括Java、Native代碼、游戲腳本、插件以及第一個場景包含的所有資源。Obb包主要是資源文件,在Unity打包Apk過程中,會把所有的資源文件(包括 streaming Assets)打包到Assets目錄下,而Obb分包后會將第一個場景以外的資源都打包到Obb目錄中,在Apk啟動后,會根據相應命名規則從Obb中加載資源文件。而在Unity里面為了安全性還封裝了一些校驗規則,下面會提取出相關的校驗規則供我們下載校驗(這只針對通過Unity直接打包會生成相關的校驗規則,如果你是導出工程然后再進行分包、打包那么Unity這套規則并不直接適用于你,為了安全性你可以自己實現一套類似的規則)。

  • 如何使用Obb擴展資源文件

大多數情況下,當用戶從Google Play上下載應用時,Google Play會自動將APK文件和擴展文件同時下載下來,至于具體是哪些cases下Google Play無法下載擴展文件并沒有說明,此外即使Google Play正確的下載了擴展文件,但是由于擴展文件存放的目錄是可以被用戶和其他應用訪問的。但是Google Play并不總是保證一定會下載擴展文件,一般情況下我們需要將生成的apk以及obb下載下來的擴展文件有可能會被用戶或其他應用刪除。
其次,我們的安裝包除了在Google Play平臺,也會在其他渠道上架,所以為了保證用戶下載簡潔可靠,我們需要在應用中自行實現擴展文件完整性檢查和下載的機制。

  • 如何手動下載Obb資源擴展文件

1.如果你的Obb擴展文件上傳到Google平臺,那么你可以使用Android中提供的APK擴展文件下載庫Downloader Library來簡化擴展文件檢查和下載的邏輯,具體可以參考以及Google Play APK擴展文件機制及開發流程詳解,然而這種方式限制多多,需要支持google框架,不能應用于其他渠道等等...
2.將擴展文件上傳到自己的服務器,原理上就可以適用于所有的渠道,需要的就是實現一個網絡下載器。

  • 手動校驗Obb是否已經下載完成

UnityPlayer是一個UI場景類,在UnityPlayerActivity會初始化該類,在進入游戲前,這個類里面會讀取本地Obb文件生成校驗碼并與打包Apk時,配置在setting.xml中的校驗表對比,如果校驗失敗,則不會進入游戲場景,配置表如下:

Assets/bin/Data/Setting.xml

<?xml version="1.0" encoding="UTF-8"?>
<settings>
  <integer name="splash_mode">0</integer>
  <bool name="useObb">True</bool>
  <bool name="9f6f9912e7e5c791037078042be85f73">True</bool>
</settings>

splash_mode:應該是定義啟動模式

useObb:是否使用Obb,如果沒有使用Unity進行Obb分包,那么該選項始終是False。

9f6f9912e7e5c791037078042be85f73:表示加密算法生成的校驗碼。

項目中需要做的是在進入游戲后去進行一次Obb校驗,防止用戶重復下載Obb,如果校驗失敗就需要我們在游戲中自動去下載Obb包,我們把Unity中校驗Obb的步驟拎出來,一共三部。

  • 檢測Obb文件是否存在
  • 根據Obb文件生成校驗碼
  • 讀取setting.xml文件,并與校驗碼做對比

下面的具體的一些代碼,主要規則來源于UnityPlayer。

  • 獲取Obb文件
    /**
     * 獲取應用obb位置
     * @param paramContext
     * @return
     */
    private static String[] getObbPath(Context paramContext) {
        String str1 = paramContext.getPackageName();
        Vector<String> localVector = new Vector<String>();
        try {
            int i1 = paramContext.getPackageManager().getPackageInfo(str1, 0).versionCode;
            if (Environment.getExternalStorageState().equals("mounted")) {
                File localFile1 = Environment.getExternalStorageDirectory();
                File localFile2 = new File(localFile1.toString()
                        + "/Android/obb/" + str1);
                if (localFile2.exists()) {
                    if (i1 > 0) {
                        String str3 = localFile2 + File.separator + "main."
                                + i1 + "." + str1 + ".obb";
                        if (new File(str3).isFile()) {
                            localVector.add(str3);
                        }
                    }
                    if (i1 > 0) {
                        String str2 = localFile2 + File.separator + "patch."
                                + i1 + "." + str1 + ".obb";
                        if (new File(str2).isFile()) {
                            localVector.add(str2);
                        }
                    }
                }
            }
            String[] arrayOfString = new String[localVector.size()];
            localVector.toArray(arrayOfString);
            return arrayOfString;
        } catch (PackageManager.NameNotFoundException localNameNotFoundException) {
        }
        return new String[0];
    }

  • 加密生成校驗碼算法:
    /**
     * 通過obb文件獲取加密MD5
     * @param paramString
     * @return
     */
    private static String getMd5(String paramString) {
        try {
            Log.d("WARX", "path = " + paramString);
            MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
            FileInputStream localFileInputStream = new FileInputStream(
                    paramString);
            long lenght = new File(paramString).length();
            localFileInputStream.skip(lenght - Math.min(lenght, 65558L));
            byte[] arrayOfByte = new byte[1024];
            for (int i2 = 0; i2 != -1; i2 = localFileInputStream
                    .read(arrayOfByte)) {
                localMessageDigest.update(arrayOfByte, 0, i2);
            }
            BigInteger bi = new BigInteger(1, localMessageDigest.digest());
            Log.d("WARX", "md5 = " + bi.toString(16));
            return bi.toString(16);
        } catch (FileNotFoundException localFileNotFoundException) {
        } catch (IOException localIOException) {
        } catch (NoSuchAlgorithmException localNoSuchAlgorithmException) {

        }
        return null;
    }

這里主要是根據文件的長度生成的一個md校驗碼。

  • 解析XML算法:
private static Bundle getXml(Context context) {
        Bundle bundle = new Bundle();
        XmlPullParser localXmlPullParser;
        // int i1;
        String str;
        try {
            File localFile = new File(context.getPackageCodePath(),
                    "assets/bin/Data/settings.xml");
            Object localObject1;
            if (localFile.exists())

                localObject1 = new FileInputStream(localFile);
            else
                localObject1 = context.getAssets()
                        .open("bin/Data/settings.xml");

            XmlPullParserFactory localXmlPullParserFactory = XmlPullParserFactory
                    .newInstance();
            localXmlPullParserFactory.setNamespaceAware(true);
            localXmlPullParser = localXmlPullParserFactory.newPullParser();
            localXmlPullParser.setInput((InputStream) localObject1,null);
            int type = localXmlPullParser.getEventType();
            Object localObject2 = null;
            str = null;
            while (type!=1) {
                switch (type) {
                case 2:
                    if (localXmlPullParser.getAttributeCount()==0) {
                        type = localXmlPullParser.next();
                        continue;
                    }
                    str = localXmlPullParser.getName();
                    localObject2 = localXmlPullParser.getAttributeName(0);
                    if (!localXmlPullParser.getAttributeName(0).equals("name")){
                        type = localXmlPullParser.next();
                        continue;
                        }
                    localObject2 = localXmlPullParser.getAttributeValue(0);
                    if (str.equalsIgnoreCase("integer")) {
                        bundle.putInt((String) localObject2,
                                Integer.parseInt(localXmlPullParser.nextText()));
                    } else if (str.equalsIgnoreCase("string")) {
                        bundle.putString((String) localObject2,
                                localXmlPullParser.nextText());
                    } else if (str.equalsIgnoreCase("bool")) {
                        bundle.putBoolean((String) localObject2, Boolean
                                .parseBoolean(localXmlPullParser.nextText()));
                    } else if (str.equalsIgnoreCase("float")) {
                        bundle.putFloat((String) localObject2,
                                Float.parseFloat(localXmlPullParser.nextText()));
                    }
                    break;
                default:
                    break;
                }
                type = localXmlPullParser.next();
            }

        } catch (Exception localException) {
            localException.printStackTrace();
        }
        return bundle;
    }

這里將xml中的數據讀取到一個Bundle中進行保存,Bundle內部實現是Map。最后我們可以將生成的校驗碼與setting.xml中獲取的校驗碼進行對比,如果校驗失敗就可以啟動下載流程了,下載完成后重啟Activity,重新讀取Obb文件并加載資源。

    /**
     * 重啟Activity
     * @param context
     */
    public static void restartApplication(Activity context) {
        PackageManager packageManager = context.getPackageManager();
        Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
        ComponentName componentName = intent.getComponent();
        Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
        mainIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
        context.startActivity(mainIntent);
        System.exit(0);
    }
  • 關于使用obb所涉及到的權限問題
    最近需要把應用所用的權限最小化,那么獲取obb是否需要權限,這是一個非??拥臇|西,先看看官方的文檔。
    image.png

    從文檔上可以看出來,是android 6.0需要權限,除了6.0都無需權限,但是使用我們手里的6.0設備去嘗試沒有權限都可以下載obb正常進行游戲,但是使用google play下載之后部分6.0機型讀取bugly上報訪問obb路徑被拒絕了,使用測試機也發現是偶發現象,下載了多次游戲,第一次的時候出現了訪問路徑拒絕,這就非常的蛋疼了。加上權限是肯定不會出問題的,我們剔除權限前游戲從未上報過這個問題,目前我們是增加了用戶讀取內存權限解決問題。
    關于權限可以參考一下我另一篇文章
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,702評論 25 708
  • 這部漫畫也算久負盛名了,但是本來以為是像《花降樓》這樣的妖嬈美男賣身的故事,而且不大喜歡那種色彩斑斕又飾物密集...
    lxt閱讀 7,279評論 2 1
  • 上周末的下午,接到一個借錢的電話。是一個只見過幾次面,吃過二次飯,陌生而熟悉的“朋友”打來的。之所以加雙引號,是我...
    樂學藝術中心閱讀 1,396評論 3 13
  • 2018年6月12日,第十六屆中國國際軟件和信息服務交易會在大連世界博覽廣場星海會展中心開幕。 遼寧省省長唐一軍、...
    群智合閱讀 188評論 0 0
  • 汽車的"三大件"是指——發動機、底盤以及變速箱,三大件的好與壞直接就決定了這輛車的性能好壞。同時也是最能體現汽車廠...
    iasa07閱讀 3,678評論 0 0