Unity使用Obb擴(kuò)展包的正確姿勢

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

  • 如何生成Obb擴(kuò)展資源文件

Unity可以自動為你進(jìn)行分包操作,只需要你在發(fā)布安卓版本的時候進(jìn)行簡單的設(shè)置,當(dāng)然也可以自己根據(jù)需求通過以下命令進(jìn)行分包

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

obb擴(kuò)展文件的命名規(guī)則為:
main文件:main.<expansion-version>.<package-name>.obb
patch文件:patch.<expansion-version>.<package-name>.obb

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

  • 如何使用Obb擴(kuò)展資源文件

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

  • 如何手動下載Obb資源擴(kuò)展文件

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

  • 手動校驗Obb是否已經(jīng)下載完成

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

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:應(yīng)該是定義啟動模式

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

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

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

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

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

  • 獲取Obb文件
    /**
     * 獲取應(yīng)用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;
    }

這里主要是根據(jù)文件的長度生成的一個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中的數(shù)據(jù)讀取到一個Bundle中進(jìn)行保存,Bundle內(nèi)部實現(xiàn)是Map。最后我們可以將生成的校驗碼與setting.xml中獲取的校驗碼進(jìn)行對比,如果校驗失敗就可以啟動下載流程了,下載完成后重啟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);
    }
  • 關(guān)于使用obb所涉及到的權(quán)限問題
    最近需要把應(yīng)用所用的權(quán)限最小化,那么獲取obb是否需要權(quán)限,這是一個非常坑的東西,先看看官方的文檔。
    image.png

    從文檔上可以看出來,是android 6.0需要權(quán)限,除了6.0都無需權(quán)限,但是使用我們手里的6.0設(shè)備去嘗試沒有權(quán)限都可以下載obb正常進(jìn)行游戲,但是使用google play下載之后部分6.0機(jī)型讀取bugly上報訪問obb路徑被拒絕了,使用測試機(jī)也發(fā)現(xiàn)是偶發(fā)現(xiàn)象,下載了多次游戲,第一次的時候出現(xiàn)了訪問路徑拒絕,這就非常的蛋疼了。加上權(quán)限是肯定不會出問題的,我們剔除權(quán)限前游戲從未上報過這個問題,目前我們是增加了用戶讀取內(nèi)存權(quán)限解決問題。
    關(guān)于權(quán)限可以參考一下我另一篇文章
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容