5

We have a large ~600MB expansion file that we get using the Google Play Expansion APK mechanism. We also have to unzip this file for actual use. The entire app plus data thus requires about 1.4 GB of storage.

As far as I can tell Google Play insists of downloading the .obb to the "internal" SD and there doesn't appear to be any way to change this. We have many users with lots of free space on their "external" SD card but limited space on the internal one. They are screaming about the app taking so much space. Is there anything we can do about this?

We currently expand the .obb file to:

getExternalStorageDirectory()/Android/data

I suppose we could ask the user where they wanted it and they could choose the true external SD card. However that would still leave a (largely useless) .obb file on the internal SD card and Google Play says we cannot delete this.

Does anyone have any suggestions on how to handle this correctly?

btschumy
  • 1,435
  • 1
  • 18
  • 35
  • "We have many users with lots of free space on their "external" SD card but limited space on the internal one" -- from Android's standpoint, there is no such concept. There is only one external storage, represented by `getExternalStorageDirectory()`. Anything beyond that is due to device manufacturer extensions to Android, of which the Play Expansion APK stuff will know nothing. – CommonsWare Mar 09 '13 at 01:10

3 Answers3

2

Per the Expansion File Storage Location documentation, expansion files are stored in

<shared-storage>/Android/obb/<package-name>/

where shared-storage is what is returned by getExternalStorageDirectory(), which should be on the SD card for users that have an SD card.

Unfortunately, as stated on that same page:

To ensure proper behavior, you must not delete, move, or rename the expansion files.

I would take special attention to this paragraph:

If you must unpack the contents of your expansion files, do not delete the .obb expansion files afterwards and do not save the unpacked data in the same directory. You should save your unpacked files in the directory specified by getExternalFilesDir(). However, if possible, it's best if you use an expansion file format that allows you to read directly from the file instead of requiring you to unpack the data. For example, we've provided a library project called the APK Expansion Zip Library that reads your data directly from the ZIP file.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • Thanks for the response. The problem is that for many devices there are 2 SD cards. An internal one and an external (removable) one. The getExternalStorageDirectory() seems to always return the internal SD card which generally has more limited storage. We have to have very fast access to the data in the .obb file (and from C code) so we have to expand it. – btschumy Mar 09 '13 at 01:30
1

The problem with put OBB files in anywhere else is that they will not be removed when users uninstall your app, which tend to get them into a bad mood, to state it mildly. However if you believe the benefits outweigh the cost, there's nothing technical that stops you from changing the storage location, as the source code to perform OBB file(s) download & validation is provided, you can just modify its behavior to suite your needs.

For example, the path where OBB files are stored is provided by com.google.android.vending.expansion.downloader.Helpers:

static public String getSaveFilePath(Context c) {
    File root = Environment.getExternalStorageDirectory();
    String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
    return path;
}

(Also needs to modify getFilesystemRoot(String path) & isFilenameValid(String filename))

If you are using Google's expansion zipfile lib as well, then you also need to change 3 methods in com.android.vending.expansion.zipfile.APKExpansionSupport that specify Environment.getExternalStorageDirectory() as the root dir.

These changes should at least get you half way there.

Also note that the problem with external SD card is that their speed vary greatly, it is usually that the internal SD would have at least decent speed. Maybe you can partition the OBB into 2 files (assuming you don't need the update file feature), and move one of them to external SD.

Kai
  • 15,284
  • 6
  • 51
  • 82
  • I'm not interested in hacking the system to make this work. My real question was if I was missing some setting somewhere to allow this to all happen on the external SD card. From CommonWare's comments above it sounds like there isn't. – btschumy Mar 09 '13 at 15:56
  • There isn't. But it's not hacking the system because it's never part of the Android platform. It's just a set of library plus service provided by the Google Play app. – Kai Mar 09 '13 at 16:43
0

I had the same problem but I've found a way to solve it.

If you delete the OBB, then doesFileExist (in Helpers.java) will return false. In your ExpDownloaderActivity, where you check expansionFilesDelivered, you will use this information to re-download the OBB.

I've changed the doesFileExist and expansionFilesDelivered in a way not to return a boolean but an integer with the following meaning:

fileStatus == 0: OBB is missing (must be downloaded)

fileStatus == 1: OBB is available and can be unpacked to another place

fileStatus == 2: data in OBB is already stored to another place

Now te trick: After unpacking the data from OBB to my favorite place, I replace the original OBB with a file with the same name that contains only the filesize of the original OBB as a string. This frees up the occupied space on sdcard.

Further calls to doesFileExist and expansionFilesDelivered will return filestatus = 2 which means, there is no action required.

Here are my changes in Helpers.java:

static public int doesFileExist(Context c, String fileName, long fileSize,
        boolean deleteFileOnMismatch) {
    // the file may have been delivered by Market --- let's make sure
    // it's the size we expect


    File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
    if (fileForNewFile.exists()) {
        if (fileForNewFile.length() == fileSize) {
            return 1;
        } else if (fileForNewFile.length() < 100) {
            // Read the file and look for the file size inside
            String content = "";
            long isSize = 0;
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(fileForNewFile);
                char current;
                while (fis.available() > 0) {
                    current = (char) fis.read();
                    content = content + String.valueOf(current);
                }

            } catch (Exception e) {
                Log.d("ReadOBB", e.toString());
            } finally {
                if (fis != null)
                    try {
                        fis.close();
                    } catch (IOException ignored) {
                }
            }
            try {
                isSize = Long.parseLong(content);
            } catch(NumberFormatException nfe) {
                Log.d("ReadOBBtoInt", nfe.toString());
            } 
            if (isSize == fileSize) {
                return 2;
            }
        }
        if (deleteFileOnMismatch) {
            // delete the file --- we won't be able to resume
            // because we cannot confirm the integrity of the file
            fileForNewFile.delete();
        }
    }
    return 0;
}

If the file size of the OBB not match, I read the OBB and compare with the filesize stored inside (this is what the filesize shouldt be). If this give a match, I know that the file is already processed.

This are my changes in my ExpDownloaderActivity:

int expansionFilesDelivered() {
    int fileStatus = 0;

    for (XAPKFile xf : xAPKS) {
        if (xf.mFileSize > 0) {
            String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
            fileStatus = Helpers.doesFileExist(this, fileName, xf.mFileSize, false);
            if (fileStatus==0)
                return 0;
        }
    }
    return fileStatus;
}

In the onCreate of the ExpDownloaderActivity:

    initializeDownloadUI();

    int fileStatus = expansionFilesDelivered();
    if (fileStatus==0) {        // OBB is missing
            // ... Download the OBB file, same as on Downloader example
    } else if (fileStatus==1) {
        validateXAPKZipFiles(); // and, if OBB has no errors, unpack it to my favorite place
                                // if done, create a new OBB file with the original name
                                // and store a string with the original filesize in it.
    } else {
        finish();               // No action required        }

So I have the data unpacked wherever I want and - as the OP mentioned - no need the space on sdcard for the full OBB and the unpacked data.

thpitsch
  • 2,016
  • 2
  • 21
  • 27