You may have used expansion files on Android. They are very handy when your app goes over 50Mb which, for games, happens pretty soon. In my case I have KendamApp – The Kendama App which has an extensive library of videos included.
The expansion files are organized on a main and a patch files. Each of them can be up to 2Gb. Since uploading them can be a pain, you can reuse them from one version to the next.
There are 2 libraries provided with android SDK to help managing expansion files:
- downloader_library: Which purpose should be obvious.
- zip_file: Which is a nice utility to manage expansion files via a content provider. You should be using it if you are using videos.
Also, if you are going to use videos, do not compress them when zipping the obb file.
Updating only with a patch file
Essentially, if you are going to add a few assets, it is better to use a patch file and reuse the main file. It will save lots of bandwidth and time.
You don’t have to re-upload the main file and users do not need to re-download it. Everyone wins.
Except that it may not be too straight forward when using the zip_file library.
The first pitfall (a.k.a. the undocumented feature)
First thing first, you should know that if you do not add some meta-data to the AndroidManifest, the library is going to look for the versions that match with the version number of the app. I don’t recall reading this in the documentation. I actually figured it out by reading the code.
So, the content provider on your AndroidManifest should look like this:
<provider android:authorities="com.plattysoft.zipprovider" android:name="com.plattysoft.provider.ZipFileContentProvider" > <meta-data android:name="mainVersion" android:value="17"/> <meta-data android:name="patchVersion" android:value="18"/> </provider>
The second pitfall (a.k.a. the terrible bug)
Then, I realized that every time I tried to watch one of the new videos the app was crashing. I checked for the main and patch files and they were properly downloaded and inside the obb directory.
Something weird was going on.
Digging with the debugger, I ended up in a function that is supposed to return an string array with the name of the available expansion files based on the version numbers and after checking that the files do exist.
static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret = new Vector<String>(); if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath = new File(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists if (expPath.exists()) { if ( mainVersion > 0 ) { String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb"; File main = new File(strMainPath); if ( main.isFile() ) { ret.add(strMainPath); } } if ( patchVersion > 0 ) { String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb"; File main = new File(strPatchPath); if ( main.isFile() ) { ret.add(strPatchPath); } } } } String[] retArray = new String[ret.size()]; ret.toArray(retArray); return retArray; }
Except that it was returning a single string and not two. So, paying extra attention I noticed this specific piece of code.
if ( patchVersion > 0 ) { String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb"; File main = new File(strPatchPath); if ( main.isFile() ) { ret.add(strPatchPath); } }
Of course it couldn’t find the patch expansion file, it is constructing the name using the mainVersion number instead of the patchVersion. Event the File variable is called main!
Copy and paste is an anti-pattern.
I have checked the latest version of this library that comes with the android SDK and the bug is still there. I’d check where the code is and send a push request. In the meantime, you know what you have to change to fix it.
Safe scenarios
You would not encounter this bug if:
- You are not using the zip_file library.
- You are not using a patch file.
- Your main and patch files have the same version number (you update both).