Guide to APK Expansion File for Google Play Store

DylanLiuDylanLiu USUniversity ✭✭
edited May 4 in Xamarin.Forms

Background:

"Google Play currently requires that your APK file be no more than 100MB (API level 9-10 and 14+) or 50 MB (API level 8 or lower)... Google Play allows you to attach two large expansion files that supplement your APK ... Each expansion file can be up to 2GB in size.". Your apps can easily exceed the limit if you have a lot of local content (images, videos, etc). This is most common among mobile games. Unfortunately, there seems to be lack of a detailed step-by-step tutorial on how to use expansion files in Xamarin. That is until now ...

Tutorial:

This tutorial assume that you have a lot of high resolution images in your app, and those images have previously been stored in the resource folder of your android project.

  1. Move all the images from the resource folder to somewhere outside of your project, for example a folder named "Images" on your Desktop. The following steps assume that you have all the images names stored in a local database. If you haven't done that yet, look into "https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/databases/" on how to add a database to your application.
  2. Zip the folder named "Images". Note that for videos, you have to use uncompressed format. You can either do it via command line ("zip -r0 Videos.zip Videos") or with using an application like WinZip.
  3. You need to rename the zip file "Main.VERSION_NUMBER.YOUR_BUNDLE_ID.obb". VERSION_NUMBER and BUNDLE_ID must match whatever that is in your AndroidManifest.xml. For example "Main.1.com.example.myapp.obb". Make sure that you have "hide file extension" checked off on your computer, otherwise your file name would still have a hidden ".zip" extension. Then your code won't be able to locate the expansion files.
  4. To test or debug with the expansion file, you need to copy the .obb file to your android testing device in "Android/obb/YOUR_BUNDLE_ID/". For example, it will look like "Android/obb/com.example.myapp/Main.1.com.example.myapp.obb". Note that if you want to test on a simulator, I'm sure there is place that you need to copy the file to. But I didn't really look into where it should be.
  5. Add "Android APK Expansion Downloader" package to your Xamarin.Android project.
  6. Create a new file named "ExpansionFile.cs". The following code basically copies all the images from your expansion file to the Personal folder on the device.

        public static class ExpansionFile
        {
            static string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    
            public static bool CopyFromExpansionFile()
            {
                var context = Forms.Context;
                var packageInfo = Forms.Context.PackageManager.GetPackageInfo(context.PackageName, 0);
                var expansionFile = ApkExpansionSupport.GetApkExpansionZipFile(context, packageInfo.VersionCode, 0);
    
                // assuming you have the names of all the images stored in a database 
                var imageNames = MyDataBase.GetAllImages();
                foreach (string imageName in imageNames)
                {
                    var imageLocalPath = Path.Combine(documentsPath, imageName);
                    var iconLocalPath = Path.Combine(documentsPath, iconName);
    
                    if (!File.Exists(imageLocalPath))
                    {
                        var imageFileEntry = expansionFile.GetEntry("Images/" + imageName);
                        if (imageFileEntry == null)
                            return false;
                        var success = CopyFile(imageFileEntry, imageLocalPath);
                        if (!success)
                            return false;
                    }
                }
                return true;
            }
    
            private static bool CopyFile(ZipFileEntry fileEntry, string path)
            {
                var zipFile = new ZipFile(fileEntry.ZipFileName);
                return zipFile.ExtractFile(fileEntry, path);
            }
        }
    
  7. In the MainActivity.cs of your Xamarin.Android project add:

            if (!ExpansionFile.CopyFromExpansionFile();)
                    Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
    
  8. In your cross platform (Xamarin.Forms) code where you reference the images:
    Instead of

        Image.Source = ImageSource.FromFile("Image0.jpg");
    

You need:

            if (Device.RuntimePlatform == Device.Android)
            {
        string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                    Image.Source = ImageSource.FromFile(Path.Combine(documentsPath, "Image0.jpg"));
            }
            else
                    Image.Source = ImageSource.FromFile("Image0.jpg");

The same goes if you are only doing (Xamarin.Android).

  1. Some sources say that the expansion file might not be successfully downloaded from the google play store when the apk file is downloaded. So you need to add some code to manually trigger the download. I didn't find this to be the case with my app. So I didn't add this part. But if you want to, here is where you can find that code: https://github.com/mattleibow/android.Play.ExpansionLibrary
  2. You need to have "ReadExternalStorage" checked in your AndroidManifest.xml
  3. In some cases (API 23 and above), the app will crash with an error "System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/Android/obb/com.example.myapp/Main.1.com.example.myapp.obb is denied" even when you have "ReadExternalStorage" checked from step 10. This issue is reported in the following posts:
    https://code.google.com/p/android/issues/detail?id=197287
    https://code.google.com/p/android/issues/detail?id=217899
    But it was never acknowledged or fixed by Google. It is reported to happen 100% of the time on Samsung S5 S6 S7 running android 6.0. That's big percentage of your user base there. So you have to pick up Google's slack and fix it yourself.

To fix this issue, you need to add code to explicitly ask for permission in runtime.

            const string storagePermission = Manifest.Permission.ReadExternalStorage;
            if ((int)Build.VERSION.SdkInt >= 23 && CheckSelfPermission(storagePermission) != (int)Permission.Granted)
            {
                string[] PermissionsStorage = { storagePermission };
                const int RequestLocationId = 0;
                // You have to explicityly ask for storage permission in runtime for API 23 and above
                RequestPermissions(PermissionsStorage, RequestLocationId);
            }

(note that CheckSelfPermission and RequestPermissions only works on API 23 and above, so make sure the conditional statement for checking the version goes before checking permission.)

then your code for copying the images over becomes:

        if ((int)Build.VERSION.SdkInt >= 23)
        {
            if (CheckSelfPermission(storagePermission) == (int)Permission.Granted)
            {
                // copy images from expansion file to personal folder
                if (!ExpansionFile.CopyFromExpansionFile();)
                    Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
            }
        }
        else
        {
            // The issue with storage permission doesn't exist on API 22 and below
            // copy images from expansion file to personal folder
            if (!ExpansionFile.CopyFromExpansionFile();)
                Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
        }
  1. When you upload the expansion file to Google Play Store, follow the instructions here: https://support.google.com/googleplay/android-developer/answer/2481797?hl=en

Afterthought:

It's been a struggle to get my app published in the google play store. I place partial blame on google to place this antiquated restriction on apk file size. We used HockeyApp for beta testing / crash reports. HockeyApp doesn't support Expansion files, because expansion files are proprietary to Google Play Store. So I can see in the future that it is going to cause me more pain to undo everything in this tutorial and return the images back to resources folder. So unless you have to have local content for some reason, I suggest you host all your high resolution images and videos online and make your .apk file super small.

References:

http://www.applandeo.com/en/5-steps-using-expansion-files-android-xamarin-forms/
https://dotnetdevaddict.co.za/2014/12/21/downloading-expansion-files-in-xamarin-android/

Credit goes to Matthew Leibowitz who did most of the heavy lifting. I just complied everything together.

Posts

Sign In or Register to comment.