APK Expansion Files Example Code: A Comprehensive Guide to Understanding and Implementing Expansion Files

by

in

APK Expansion Files are a powerful tool for developers to distribute large-sized game assets and data to users. They allow you to separate your game’s core APK from its larger assets, resulting in faster downloads and a more streamlined user experience. This guide provides a comprehensive overview of APK Expansion Files, their functionalities, and a practical example code to get you started.

What Are APK Expansion Files?

APK Expansion Files are supplemental files that hold additional data for your Android application. They are primarily used for storing large assets like high-resolution graphics, audio files, or game levels that would significantly increase the size of your core APK. Expansion files are independent of your application’s APK and can be downloaded separately.

Why Use APK Expansion Files?

There are numerous benefits to using APK Expansion Files:

  • Reduced APK Size: Smaller core APKs lead to faster download times for users, improving the overall app installation experience.
  • Flexible Asset Management: Expansion files allow you to easily update or add new assets without requiring users to download a completely new APK.
  • Optimized Storage Usage: Expansion files can be downloaded and stored on external storage, freeing up space on the device’s internal storage.
  • Improved User Experience: Users can start playing your game even before all assets are downloaded, thanks to the progressive download feature of Expansion Files.

Understanding Expansion File Types

There are two primary types of Expansion Files:

  • Main Expansion File: This is the primary expansion file for your application and usually contains most of your game’s assets. It’s typically used for content that is essential for the game to function.
  • Patch Expansion File: This type of expansion file is used for updating the content of your main expansion file. They are smaller in size and contain only the changes or updates to the existing assets.

Implementing APK Expansion Files in Your Android App

Here’s a step-by-step guide to incorporating APK Expansion Files into your Android application:

1. Declaring Expansion Files in Your Manifest

In your AndroidManifest.xml, you need to declare your expansion files using the <meta-data> tag within the <application> element:

<application ...>
    <meta-data
        android:name="com.android.vending.expansion.DELIVERY_MODE"
        android:value="0"/>
    <meta-data
        android:name="com.android.vending.expansion.MAIN_FILE_SIZE"
        android:value="1000000000"/>
    <meta-data
        android:name="com.android.vending.expansion.PATCH_FILE_SIZE"
        android:value="100000000"/>
    </application>
  • DELIVERY_MODE: This attribute determines how the expansion files are delivered.
    • 0: Expansion files are delivered along with the APK. This is the default value.
    • 1: Expansion files are delivered separately and downloaded on demand.
  • MAIN_FILE_SIZE: Defines the expected size of your main expansion file in bytes.
  • PATCH_FILE_SIZE: Defines the expected size of your patch expansion file in bytes.

2. Downloading Expansion Files

Once your app is launched, you need to download the expansion files. The Android SDK provides the DownloaderService class to handle the download process.

public class MyDownloaderService extends DownloaderService {
    @Override
    protected int getMainFileID() {
        return 0; // ID for the main expansion file
    }

    @Override
    protected int getPatchFileID() {
        return 1; // ID for the patch expansion file
    }

    @Override
    protected long getMainFileSize() {
        return 1000000000; // Size of the main file in bytes
    }

    @Override
    protected long getPatchFileSize() {
        return 100000000; // Size of the patch file in bytes
    }
}

3. Extracting Expansion Files

After downloading, you need to extract the expansion files into a location accessible to your app. The ExpansionFile class provides methods for extracting files.

public class MyGameActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_game);

        // Extract the expansion files
        try {
            ExpansionFile mainFile = new ExpansionFile(this, 0);
            ExpansionFile patchFile = new ExpansionFile(this, 1);

            String mainFileLocation = mainFile.getFullPath();
            String patchFileLocation = patchFile.getFullPath();

            // Use mainFileLocation and patchFileLocation to access the files
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. Accessing Expansion File Data

Once the files are extracted, you can access their content just like any other file on your device. You can use Java’s standard file handling mechanisms to read and process the data.

// Example: Reading data from the main expansion file
try (BufferedReader reader = new BufferedReader(new FileReader(mainFileLocation))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // Process the data from the file
    }
} catch (IOException e) {
    e.printStackTrace();
}

Example Code for Implementing APK Expansion Files

Here’s a complete example code demonstrating the implementation of APK Expansion Files in your Android application:

// MainActivity.java

package com.example.apk_expansion_files;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.content.Intent;
import com.google.android.vending.expansion.downloader.DownloaderService;
import com.google.android.vending.expansion.downloader.ExpansionFile;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Start the DownloaderService to download expansion files
        Intent serviceIntent = new Intent(this, MyDownloaderService.class);
        startService(serviceIntent);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("MainActivity", "onResume called");

        // Check if expansion files are available
        try {
            ExpansionFile mainFile = new ExpansionFile(this, 0);
            ExpansionFile patchFile = new ExpansionFile(this, 1);

            if (mainFile.isFileExist() && patchFile.isFileExist()) {
                Log.d("MainActivity", "Expansion files are available");

                // Extract and access the expansion files
                // ... your code here ...
            } else {
                Log.d("MainActivity", "Expansion files are not available");
            }
        } catch (Exception e) {
            Log.e("MainActivity", "Error checking expansion files", e);
        }
    }
}

// MyDownloaderService.java

package com.example.apk_expansion_files;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import com.google.android.vending.expansion.downloader.DownloaderService;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IStub;
import com.google.android.vending.expansion.downloader.DownloadState;

public class MyDownloaderService extends DownloaderService {

    private static final String TAG = "MyDownloaderService";

    private static final int MAIN_FILE_ID = 0;
    private static final int PATCH_FILE_ID = 1;
    private static final long MAIN_FILE_SIZE = 1000000000; // 1 GB
    private static final long PATCH_FILE_SIZE = 100000000; // 100 MB

    private IStub mClientStub = new IStub() {
        @Override
        public void onServiceConnected(Messenger m) {
            Log.d(TAG, "onServiceConnected");
        }

        @Override
        public void onDownloadStateChanged(int newState) {
            Log.d(TAG, "onDownloadStateChanged: " + newState);
            // Handle download state changes here (e.g., show progress, notify user)
            if (newState == DownloadState.STATE_COMPLETED) {
                // Notify user that the download is complete
            }
        }

        @Override
        public void onDownloadProgress(DownloadProgressInfo progress) {
            Log.d(TAG, "onDownloadProgress: " + progress.mOverallProgress);
            // Update download progress UI
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
    }

    @Override
    protected IDownloaderClient getDownloaderClient() {
        return mClientStub;
    }

    @Override
    protected int getMainFileID() {
        return MAIN_FILE_ID;
    }

    @Override
    protected int getPatchFileID() {
        return PATCH_FILE_ID;
    }

    @Override
    protected long getMainFileSize() {
        return MAIN_FILE_SIZE;
    }

    @Override
    protected long getPatchFileSize() {
        return PATCH_FILE_SIZE;
    }

    @Override
    public void onDownloadStateChanged(int newState) {
        Log.d(TAG, "onDownloadStateChanged: " + newState);
        // Handle download state changes here (e.g., show progress, notify user)
    }

    @Override
    public void onDownloadProgress(DownloadProgressInfo progress) {
        Log.d(TAG, "onDownloadProgress: " + progress.mOverallProgress);
        // Update download progress UI
    }

    @Override
    public void onServiceStart(Intent intent, int startId) {
        super.onServiceStart(intent, startId);
        Log.d(TAG, "onServiceStart");

        // Check if expansion files are already downloaded
        if (Helpers.isExpansionFileDownloaded(this, MAIN_FILE_ID)
                && Helpers.isExpansionFileDownloaded(this, PATCH_FILE_ID)) {
            // Files are downloaded, stop the service
            stopSelf();
        } else {
            // Start the download process
            startDownload();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    private void startDownload() {
        // Create a notification for the download
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification = new Notification.Builder(this)
                .setContentTitle("Downloading Expansion Files")
                .setContentText("Downloading...")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(contentIntent)
                .build();

        // Start the download
        startDownloadService(notification);
    }
}

Considerations for Using APK Expansion Files

  • Storage Space: Expansion files can consume significant storage space, so ensure users have adequate storage available.
  • Network Usage: Downloading expansion files can consume a significant amount of network bandwidth.
  • User Experience: Provide clear feedback and progress updates to users during download and extraction.
  • Security: Protect your expansion files using proper encryption and security measures.

Example: Implementing APK Expansion Files in a Game Development Context

Scenario: You’re developing a mobile game with stunning graphics and a lot of levels. Using APK Expansion Files will allow you to:

  • Deliver High-Quality Graphics: Store high-resolution textures and models in expansion files, making your game visually impressive without impacting the APK size.
  • Efficient Level Loading: Store individual game levels as separate files within the expansion file. This enables you to only load the necessary level assets, improving loading times.

Code Example:

// In your game's level loading logic:

public class LevelManager {
    // ...

    public void loadLevel(int levelId) {
        // Extract the level assets from the expansion file
        ExpansionFile expansionFile = new ExpansionFile(context, 0); // Access the main expansion file
        String levelDirectory = expansionFile.getFullPath() + "/levels/" + levelId;

        // Load level-specific assets (e.g., textures, models, sound effects)
        // ...
    }
}

Conclusion

APK Expansion Files are an indispensable tool for developers creating large-scale Android applications, especially games. By effectively utilizing expansion files, you can create a more streamlined user experience, improve download times, and deliver high-quality content to your players.