關於 Android Obb 檔案的掛載, 筆者提供兩個方式 (2 擇 1, alternative) 對於 Obb 檔案進行掛載 . 讀者有興趣的話可以挑一個方式實驗. 這第2個方法是筆者所發現 (發明???) 的. 原理也很簡單, 就是開一個新的執行緒觀察 (Monitoring) "Obb" 這個資料夾. 當 onObb() 被呼叫時即是 Obb 檔案掛載成功. 值得注意的是: Obb 檔案掛載成功後, 即使是 App 退出, 也千萬不要將其卸載.
方法1:
方法1 使用 OnObbStateChangeListener, 因此我們 new 一個 OnObbStateChangeListener(), 但我們 overload 它的 onObbStateChange() 方法. 在 onObbStateChange() 過濾錯誤的 state . MountObb1() 最後呼叫 StorageManager 之 mountObb() .
...
DATA_PATH = SM.getMountedObbPath(Path);
...
DATA_PATH 就是我們所要的. Example 如下:
...
private Handler mObbHandler = null;
private OnObbStateChangeListener mObbStateHandler = null;
private ProgressDialog mLoadingDlg = null;
...
private void MountObb1(final String OBBPath) {
final StorageManager SM = (StorageManager) getSystemService(STORAGE_SERVICE);
mLoadingDlg = new ProgressDialog(this);
mLoadingDlg.setCancelable(true);
mLoadingDlg.setMessage(getString(R.string.LoadingData));
mLoadingDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mLoadingDlg.show();
if (null == mObbStateHandler)
mObbStateHandler = new OnObbStateChangeListener() {
public void onObbStateChange(String Path, int State) {
if (State == ERROR_INTERNAL) {
finish();
return;
}
if (State == ERROR_COULD_NOT_UNMOUNT) {
finish();
return;
}
if (State != MOUNTED && State != ERROR_ALREADY_MOUNTED) {
finish();
return;
}
DATA_PATH = SM.getMountedObbPath(Path);
...
if(!InitMenu()) {
finish();
return;
}
mLoadingDlg.dismiss();
onObb(OBBPath);
}
};
if (!SM.mountObb(OBBPath, null, mObbStateHandler)) finish();
}
...
方法2:
方法2 的原理很簡單, 就是於程式中 呼叫 StorageManager 之 mountObb() 後, 檢查 /mnt/obb 底下有無已經掛載的資料夾, 如下圖所示 (9e2fef3de9fbf43b3eb499af60893a00):
我在這兒使用 new Thread(new Runnable() 來檢查 /mnt/obb 底下有無已經掛載的資料夾 ( /mnt/obb/9e2fef3de9fbf43b3eb499af60893a00/ ) ; 約 15 秒後 timeout . 因為是 new Thread, 所以在處理 UI Dialog 時, Thread 要使用 mObbHandler 物件 sendMessage; mObbHandler 物件 以 overload 它的 handleMessage() 處理 Thread 傳來的 messages.
...
private Handler mObbHandler = null;
private OnObbStateChangeListener mObbStateHandler = null;
private ProgressDialog mLoadingDlg = null;
...
private void MountObb2(final String OBBPath) {
final StorageManager SM = (StorageManager) getSystemService(STORAGE_SERVICE);
///////////////////////////////////////////////////////////
// show dialog
mLoadingDlg = new ProgressDialog(this);
mLoadingDlg.setCancelable(true);
mLoadingDlg.setMessage(getString(R.string.LoadingData));
mLoadingDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mLoadingDlg.show();
///////////////////////////////////////////////////////////
// send mountObb command
if (null == mObbStateHandler)
mObbStateHandler = new OnObbStateChangeListener() {
public void onObbStateChange(String Path, int State) {
if (State == ERROR_INTERNAL) {
finish();
return;
}
if (State == ERROR_COULD_NOT_UNMOUNT) {
finish();
return;
}
if (State != MOUNTED && State != ERROR_ALREADY_MOUNTED) {
finish();
return;
}
}
};
///////////////////////////////////////////////////////////
// new thread check if Obb mounted
if (null == mObbHandler)
mObbHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
finish();
break;
case 1:
...
break;
case 2:
if(!InitMenu()) {
finish();
return;
}
break;
case 3:
mLoadingDlg.dismiss();
break;
case 4:
onObb(DATA_PATH);
break;
default:
break;
}
}
};
if (null != mObbHandler)
new Thread(new Runnable() {
public void run() {
Message msg;
int found = 0;
for (int x=0; x < 15; x++) {
try { Thread.sleep(1000); } catch(Exception e) { }
File f = new File("/mnt/obb");
File[] files = f.listFiles();
for (File inFile : files) {
if (inFile.isDirectory()) {
DATA_PATH = "/mnt/obb/" + inFile.getName();
found = 1;
msg = new Message(); msg.what = 1;
if (!mObbHandler.sendMessage(msg)) finish();
break;
}
}
if (1 == found) break;
}
if (0 == found) {
finish();
//msg = new Message(); msg.what = 0;
//if (!mObbHandler.sendMessage(msg)) finish();
return;
}
...
msg = new Message(); msg.what = 2;
if (!mObbHandler.sendMessage(msg)) finish();
msg = new Message(); msg.what = 3;
if (!mObbHandler.sendMessage(msg)) finish();
msg = new Message(); msg.what = 4;
if (!mObbHandler.sendMessage(msg)) finish();
return;
}
}).start();
if (!SM.mountObb(OBBPath, null, mObbStateHandler)) finish();
}
...
onDestroy():
如果你的 App 是 Launcher 記得 onDestroy() 不要卸載 obb 了.
...
@Override
public void onDestroy() {
super.onDestroy();
...
//((StorageManager) getSystemService(STORAGE_SERVICE)).unmountObb(OBB_PATH, true, OBBHandler);
}
...
製作 Obb 檔案
https://developer.android.com/studio/command-line/jobb.html
jobb [-d <directory>][-o <filename>][-pn <package>][-pv <version>] \ [-k <key>][-ov][-dump <filename>][-v][-about]
@rem makeObb.bat jobb -d %CWD%/obb/ -o %CWD%/com.example.menu -pn com.examole.menu -pv 1 -v
執行上述 "jobb -d .... " 應該就可以產生 com.examole.menu.obb 這個檔案了.
習慣上 Obb 檔會置於 /storage/sdcard0/Android/obb/ 這個資料夾下. 以 com.examole.menu 為例:
Obb 檔案應當位於:
/storage/sdcard0/Android/obb/com.examole.menu/com.examole.menu.obb
