Steps to create separate APK and APK expansion file when APK size is greater than 50MB

1.2k views Asked by At

i have an app size approximately < 200 MB size which contain assets folder with .jpg image files.

Step 1:

Manifist Permission Provided

<!-- Required to access Google Play Licensing -->
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

<!-- Required to download files from Google Play -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- Required to poll the state of the network connection and respond to changes -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Required to check whether Wi-Fi is enabled -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

<!-- Required to read and write the expansion files on shared storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Step 2:

I have used dependencies instead of import module

build.gradle; 

repositories {
    maven { url 'https://dl.bintray.com/alexeydanilov/apk-expansion' }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    // APK Expansion library
    compile 'com.google.android.gms:play-services:7.5.0'
    compile files('libs/commons-io-2.4.jar')

     // try using library too
//    compile project(':aPKExpansionZipLibrary')
//    compile project(':applicationLicensing')
//    compile project(':downloaderLibrary')

    compile 'com.danikula.expansion:expansion:1.1@aar'
    compile 'com.danikula.expansion:license:1.5@aar'
    compile 'com.danikula.expansion:zip:1.1@aar'


}

Step 3:

Downloader Service Class

public class ExampleDownloaderService extends DownloaderService {

// BASE64_PUBLIC_KEY from developer profile public static final String BASE64_PUBLIC_KEY = "..............oAtvtNLZ/tNm3okOpR7GsT58dMBsc.............";

// i put it random .... i am not clear till now why so random public static final byte[] SALT = new byte[] {1, 4, -1, -1, 14, 42, -79, -21, 13, 2, -8, -11, 62, 1, -10, -101, -19, 41, -12, 18};

@Override
public String getPublicKey() {
    return BASE64_PUBLIC_KEY;
}

@Override
public byte[] getSALT() {
    return SALT;
}

@Override
public String getAlarmReceiverClassName() {
    return ExampleAlarmReceiver.class.getName();
}

}

Step 4:

Alaram Receiver Class:

public class ExampleAlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ExampleDownloaderService.class);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }       
    }

}

Step 5:

I Have create two helper classes

Uitilities

public class Utilities {

    private static final String TAG = "Utilities";

    // The shared path to all app expansion files
    public final static String EXP_PATH = "/Android/obb/";
    public final static String EXP_DATA_PATH = "/Android/data/";
    public final static long EXP_FILE_SIZE = 211434558L; // 154577244L
    public final static int MAIN_VERSION = 2; // 1
    public final static int PATCH_VERSION = 0;

    public static final String PREFS_NAME = "FishGuidePrefsFile";


    public static InputStream getAssetsPathInputStream(Context context, String internalPath) throws IOException {
        // Get a ZipResourceFile representing a merger of both the main and
        // patch files
        ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, MAIN_VERSION, PATCH_VERSION);

        internalPath = "assets/" + internalPath;

        // Get an input stream for a known file inside the expansion file ZIPs
        return getAssetInputStream(context, internalPath);
    }

    public static AssetFileDescriptor getAssetsFileDescriptor(Context context, String internalPath) throws IOException {
        // Get a ZipResourceFile representing a merger of both the main and
        // patch files
        ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, MAIN_VERSION, PATCH_VERSION);

        internalPath = "assets/" + internalPath;

        // Get an input stream for a known file inside the expansion file ZIPs
        return expansionFile.getAssetFileDescriptor(internalPath);
    }

    public static ZipResourceFile getAssetsZipResourceFile(Context context)
            throws IOException {
        // Get a ZipResourceFile representing a merger of both the main and
        // patch files
        ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(context, MAIN_VERSION, PATCH_VERSION);
        return expansionFile;
    }

    public static String[] getAPKExpansionFiles(Context ctx) {
        return getAPKExpansionFiles(ctx, MAIN_VERSION, PATCH_VERSION);
    }

    public static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
        String packageName = ctx.getPackageName();
        Vector<String> ret = new Vector<String>();

        Log.w(TAG, "packageName: " + packageName);
        Log.w(TAG, "Environment.getExternalStorageState(): " + Environment.getExternalStorageState());
        Log.w(TAG, "Environment.getExternalStorageDirectory(): " + Environment.getExternalStorageDirectory());
        Log.w(TAG, "ctx.getFilesDir(): " + ctx.getFilesDir());

        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;
    }

    public static void unzipExpansionFile(String expPath, File outputDir) {
        ZipHelper zh = new ZipHelper();
        zh.unzip(expPath, outputDir);
    }

    public static File getExternalDataPath(Context ctx) {
        String packageName = ctx.getPackageName();
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            // Build the full path to the app's expansion files
            File root = Environment.getExternalStorageDirectory();
            File expDataPath = new File(root.toString() + EXP_DATA_PATH + packageName);

            // Check that expansion file path exists
            // if (!expDataPath.exists()) {
            // expDataPath.mkdirs();
            // }

            Log.d(TAG, "External Data Path: " + expDataPath.toString());

            return expDataPath;
        }

        return null;
    }

    public static String getFullExternalDataPath(Context ctx, String assetPath) {

        File baseDir = getExternalDataPath(ctx);
        if (!assetPath.startsWith("assets")) {
            assetPath = "assets/" + assetPath;
        }
        return baseDir + File.separator + assetPath;
    }

    public static InputStream getAssetInputStream(Context ctx, String path) {
        try {

            if (!path.startsWith("assets")) {
                path = "assets/" + path;
            }

            File baseDir = getExternalDataPath(ctx);
            String assetFilePath = baseDir + File.separator + path;

            File assetFile = new File(assetFilePath);

            Log.d(TAG, "Loading AssetInputStream for: " + assetFilePath
                    + " => Exists: " + assetFile.exists());

            return new FileInputStream(assetFilePath);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

    public static void cleanUpOldData(Context ctx) {
        try {
            String packageName = ctx.getPackageName();
            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);
                File expDataPath = new File(root.toString() + EXP_DATA_PATH + packageName);

                // clean up the expanded data under EXP_DATA_PATH
                boolean isDeleted = deleteDirectory(expDataPath);
                Log.d(TAG, "Expanded data path deleted: " + isDeleted);

                // clean up the obb file under EXP_PATH

                int currentMainVersion = MAIN_VERSION - 1;
                int currentPatchVersion = PATCH_VERSION - 1;


                // Check that expansion file path exists
                if (expPath.exists()) {
                    if (currentMainVersion > 0) {
                        String strMainPath = expPath + File.separator + "main." + currentMainVersion + "." + packageName + ".obb";
                        File main = new File(strMainPath);
                        if (main.exists()) {
                            isDeleted = main.delete();
                        }
                    }
                    Log.d(TAG, "Main OBB file deleted: " + isDeleted);
                    if (currentPatchVersion > 0) {
                        String strPatchPath = expPath + File.separator + "patch." + currentPatchVersion + "." + packageName + ".obb";
                        File patch = new File(strPatchPath);
                        if (patch.exists()) {
                            isDeleted = patch.delete();
                        }
                        Log.d(TAG, "Patch OBB file deleted: " + isDeleted);
                    }
                }
            }

        } catch (Exception e) {
            Log.e(TAG, "Unable to clean up old app data");
        }
    }

    public static boolean deleteDirectory(File path) {
        if (path.exists()) {
            File[] files = path.listFiles();
            if (files == null) {
                return true;
            }
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                } else {
                    files[i].delete();
                }
            }
        }
        return (path.delete());
    }

    // public static String getFullExternalDataPath(Context ctx, String
    // assetPath) {
    // return "file:///android_asset/" + assetPath;
    // }
    //
    // public static InputStream getAssetInputStream(Context ctx, String path) {
    // try {
    // AssetManager mgr = ctx.getAssets();
    // return mgr.open(path, AssetManager.ACCESS_BUFFER);
    // } catch (FileNotFoundException e) {
    // e.printStackTrace();
    // } catch (Exception e) {
    // // TODO: handle exception
    // }
    // return null;
    // }

}

andother Class ZipHelper

public class ZipHelper {

    private static final String TAG = "CoralSea.ZipHelper";

    boolean zipError=false;

    public boolean isZipError() {
        return zipError;
    }

    public void setZipError(boolean zipError) {
        this.zipError = zipError;
    }

    public void unzip(String archive, File outputDir) {
        try {
            Log.d(TAG,"ZipHelper.unzip() - File: " + archive);
            ZipFile zipfile = new ZipFile(archive);
            for (Enumeration e = zipfile.entries(); e.hasMoreElements(); ) {
                ZipEntry entry = (ZipEntry) e.nextElement();
                unzipEntry(zipfile, entry, outputDir);

            }
        }
        catch (Exception e) {
            Log.e(TAG,"ZipHelper.unzip() - Error extracting file " + archive+": "+ e);
            setZipError(true);
        }
    }

    private void unzipEntry(ZipFile zipfile, ZipEntry entry, File outputDir) throws IOException {
        if (entry.isDirectory()) {
            createDirectory(new File(outputDir, entry.getName()));
            return;
        }

        File outputFile = new File(outputDir, entry.getName());
        if (!outputFile.getParentFile().exists()){
            createDirectory(outputFile.getParentFile());
        }

        //Log.d("control","ZipHelper.unzipEntry() - Extracting: " + entry);
        BufferedInputStream inputStream = new BufferedInputStream(zipfile.getInputStream(entry));
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));

        try {
            copy(inputStream, outputStream);
        }
        catch (Exception e) {
            Log.e(TAG,"ZipHelper.unzipEntry() - Error: " + e);
            setZipError(true);
        }
        finally {
            outputStream.close();
            inputStream.close();
        }
    }

    private void createDirectory(File dir) {
        Log.d(TAG,"ZipHelper.createDir() - Creating directory: "+dir.getName());
        if (!dir.exists()){
            if(!dir.mkdirs()) throw new RuntimeException("Can't create directory "+dir);
        }
        else Log.d(TAG,"ZipHelper.createDir() - Exists directory: "+dir.getName());
    }

    // Copies src file to dst file.
    // If the dst file does not exist, it is created
    private void copy(File src, File dst) throws IOException {
        InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst);

        copy(in, out); 
    }

    // Copies src file to dst file.
    // If the dst file does not exist, it is created
    private void copy(InputStream in, OutputStream out) throws IOException {

        // Transfer bytes from in to out
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }
}

Step 6:

Spalsh Screen work

Finally: Process to create expansion file:

  1. Create New Folder and rename with main.2.com.example
  2. Put all the images inside the folder
  3. Compress this folder with 0 compression
  4. New generated File as main.2.com.example.zip & Rename with main.2.com.example.obb

So my Questions are:

1.How can i make the separate expansion file as the size of my apk is much greater than 50MB but less than 200MB?

a>Which classes are required to handle the expansion file zip and download?

b>Where should they have been called?

  1. My original source code took the assets folder directory to get image path, Should i update path from Android/OBB/ [package name]/ directory.

3.What are the memory optimization factor while use expansion file?

I have already checked other Questions bit similar to mine but none of it solve any of my purpose!!!

0

There are 0 answers