A community maintained version of the Android APK Expansion library, which was first published by Google.
We believe that the original APK Expansion Library was slightly over-engineered. We propose a new simplified API for the Downloader Library.
The new API does not require the user to implement any additional services or broadcast receivers.
It is still based on the original DownloaderService
, however the communication with the
service made more transparent.
- Service is started directly through the static methods.
- The service now signals download events through the local
broadcast, and does not require service binding for the most use cases. To receive
download events user just have to extend
BroadcastDownloaderClient
. - User does not have to implement an alarm receiver for signals from the service watch dogs.
In your module's build.gradle
dependencies:
compile 'com.github.bolein.better-apk-expansion:downloader_library:5.0.3'
In your root project's build.gradle
:
allprojects {
// ...
repositories {
// ...
maven { url "https://jitpack.io" } // add this line
}
}
The main activity in your application (the one started by your launcher icon) is responsible for verifying whether the expansion files are already on the device and initiating the download if they are not.
Starting the download using the Downloader Library requires the following procedures:
-
Check whether the files have been downloaded.
The Downloader Library includes some APIs in the Helper class to help with this process:
getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
doesFileExist(Context c, String fileName, long fileSize)
-
Start the download by calling the static method
DownloaderService.startDownloadServiceIfRequired(Context context, String channelId, PendingIntent pendingIntent, byte[] salt, String publicKey)
.The method takes the following parameters:
context
- Your application's Context.channelId
- The Channel ID to use on Android Oreo and newer. You must create the Channel yourself.pendingIntent
- A PendingIntent to start your main activity. This is used in the Notification that the DownloaderService creates to show the download progress. When the user selects the notification, the system invokes the PendingIntent you supply here and should open the activity that shows the download progress (usually the same activity that started the download).salt
- An array of random bytes that the licensing Policy uses to create an Obfuscator. The salt ensures that your obfuscatedSharedPreferences
file in which your licensing data is saved will be unique and non-discoverable.publicKey
- A string that is the Base64-encoded RSA public key for your publisher account, available from the profile page on the Play Console (see Setting Up for Licensing).
The method returns an integer that indicates whether or not the download is required. Possible values are:
NO_DOWNLOAD_REQUIRED
: Returned if the files already exist or a download is already in progress.LVL_CHECK_REQUIRED
: Returned if a license verification is required in order to acquire the expansion file URLs.DOWNLOAD_REQUIRED
: Returned if the expansion file URLs are already known, but have not been downloaded.The behavior for
LVL_CHECK_REQUIRED
andDOWNLOAD_REQUIRED
are essentially the same and you normally don't need to be concerned about them. In your main activity that callsstartDownloadServiceIfRequired()
, you can simply check whether or not the response isNO_DOWNLOAD_REQUIRED
. If the response is anything other thanNO_DOWNLOAD_REQUIRED
, the Downloader Library begins the download and you should update your activity UI to display the download progress (see the next step). If the response isNO_DOWNLOAD_REQUIRED
, then the files are available and your application can start.Notice that the method may return
NO_DOWNLOAD_REQUIRED
if your APK has no expansion files associated with it (see Linking OBB files).For example:
// You must use the public key belonging to your publisher account public static final String BASE64_PUBLIC_KEY = "YourLVLKey"; // You should also modify this salt. For security reasons, it has // to be truly random. public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 }; @Override public void onCreate(Bundle savedInstanceState) { // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { // Build an Intent to start this activity from the Notification Intent notifierIntent = new Intent(this, MainActivity.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); ... PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) // Don't forget to create the channel in advance! int startResult = DownloaderService.startDownloadServiceIfRequired(this, "downloader-channel", pendingIntent, SALT, BASE64_PUBLIC_KEY); // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return; } // If the download wasn't necessary, fall through to start the app } startApp(); // Expansion files are available, start the app }
To receive updates regarding the download progress, you must extend and register the BroadcastDownloaderClient
. It is
important to use the register()
and unregister()
methods provided by the client. Note that receiver will only receive
progress updates if it is registered in the same process as the DownloaderService
started.
public class SampleDownloaderActivity extends AppCompatActivity {
private final DownloaderClient mClient = new DownloaderClient(this);
// ...
@Override
protected void onStart() {
super.onStart();
mClient.register(this);
}
@Override
protected void onStop() {
mClient.unregister(this);
super.onStop();
}
// ...
class DownloaderClient extends BroadcastDownloaderClient {
@Override
public void onDownloadStateChanged(int newState) {
if (newState == STATE_COMPLETED) {
// downloaded successfully...
} else if (newState >= 15) {
// failed
int message = Helpers.getDownloaderStringResourceIDFromState(newState);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress) {
if (progress.mOverallTotal > 0) {
// receive the download progress
// you can then display the progress in your activity
String progress = Helpers.getDownloadProgressPercent(progress.mOverallProgress,
progress.mOverallTotal);
Log.i("SampleDownloaderActivity", "downloading progress: " + progress);
}
}
}
}
With the IDownloaderService
interface, you can send commands to the downloader service, such as to pause and resume the download (requestPauseDownload()
and requestContinueDownload()
).
To access an instance of the IDownloaderService
, you can use the DownloaderProxy
class.
To use the proxy, you should first establish a binding connection with the service by calling connect()
method. This
method will bind the proxy to the service and send further commands through the Messenger
.
For example:
// in your activity class
private final DownloaderProxy mDownloaderProxy = new DownloaderProxy(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
// Establish a connection to the service
mDownloaderProxy.connect();
// You can now use the proxy to issue commands to the
// DownloaderService such as `requestPauseDownload()`
// and `requestContinueDownload()`
// Request current download status for the downloader client
mDownloaderProxy.requestDownloadStatus();
}
@Override
protected void onDestroy() {
super.onDestroy();
// Don't forget to unbind the proxy when you don't
// need it anymore
mDownloaderProxy.disconnect();
}
In order to test the download, your application must be published in Google Play (alpha or beta release works). Before starting to test the download, make sure that you have associated at least one OBB file with your current application's versionCode
. Versions that have no associated expansion files can't start the download. You can associate the OBB files when you upload the apk file to the Play Console.
Check the APK Expansion Files documentation for more information about OBB files.
The first step in the download process is to obtain a valid licensing response. Make sure that you have correctly set your publisher key. For more info about setting up publisher account check the documentation.
Note that you can also set up your testing environment to always emit a valid licensing response (see the licensing documentation for more info).
The original zip_file
module is included.
In your module's build.gradle
dependencies:
compile 'com.github.bolein.better-apk-expansion:zip_file:5.0.2'
You may also use the old API, which is maintained only in bug-fix mode. For complete docs on the old Downloader Library refer to APK Expansion Files.
You must compile the 4.X.X version of the library for the old API.
In your module's build.gradle
dependencies:
compile 'com.github.bolein:better-apk-expansion:4.0.1'
In your root project's build.gradle
:
allprojects {
// ...
repositories {
// ...
maven { url "https://jitpack.io" } // add this line
}
}
The complete documentation on the old Downloader Library is available at the Android Developers website.
A library that works with the google_market_licensing library to download APK Expansion files in a background service. This library depends on the licensing library, and must be built as a library project.
A library that uses multiple zip files as a virtual read-only filesystem with hooks to support APK Expansion files. This also must be built as an Android library project.
A sample application that assumes that zip format files have been uploaded as the main/patch file(s) on Android Market. It downloads these files and then validates that the CRC's for every entry in the zip match. This application depends on the downloader_library and the zip_file library. Because of dependency issues involving multiple libraries, you may have to do a clean build after creating each project.
This library depends on the Android License Verification Library.
- Do not plan to extract the contents of an APK Expansion file. They are intended to be used in-place. By not compressing audio and video files and storing them in a Zip file they can be played from within an expansion file.
- See com.google.android.vending.expansion.downloader/Constants.java to turn on verbose logging in the library. Please turn it off when you publish
- You must add your SALT and Public key to the SampleDownloaderService and update the xAPKS structure in SampleDownloaderActivity in order to test with the sample code
- There is no strong need to include the validator with your Android applications. It is included as a demonstration, and can be used if you wish.
For more information, see the documentation at http://developer.android.com/guide/market/expansion-files.html
See the licensing documentation at http://developer.android.com/guide/publishing/licensing.html