由于Google Play上不能上傳大于100M的包,所以需要將應用進行Obb分包,資源文件打包到Obb中,在Apk啟動的時候再從Obb擴展文件中加載資源。
如何生成Obb擴展資源文件
Unity可以自動為你進行分包操作,只需要你在發布安卓版本的時候進行簡單的設置,當然也可以自己根據需求通過以下命令進行分包。
// jobb 命令在sdk\tools目錄下jobb -pn-pv-d \資源 -o G:\輸出包名(如main.1.com.google.obb)
obb擴展文件的命名規則為:
main文件:main...obb
patch文件:patch...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();
VectorlocalVector = new Vector(); 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是否需要權限,這是一個非常坑的東西,先看看官方的文檔。
從文檔上可以看出來,是android 6.0需要權限,除了6.0都無需權限,但是使用我們手里的6.0設備去嘗試沒有權限都可以下載obb正常進行游戲,但是使用google play下載之后部分6.0機型讀取bugly上報訪問obb路徑被拒絕了,使用測試機也發現是偶發現象,下載了多次游戲,第一次的時候出現了訪問路徑拒絕,這就非常的蛋疼了。加上權限是肯定不會出問題的,我們剔除權限前游戲從未上報過這個問題,目前我們是增加了用戶讀取內存權限解決問題。
更多關于unity培訓的問題,歡迎咨詢千鋒教育在線名師。千鋒教育擁有多年IT培訓服務經驗,采用全程面授高品質、高體驗培養模式,擁有國內一體化教學管理及學員服務,助力更多學員實現高薪夢想。