Skip to content

Split APKs

ackpine-splits artifact contains utilities for working with split APK files.

Working with zipped splits

ZippedApkSplits class contains factory methods for sequences of APK splits which are contained inside of a zipped file such as ZIP, APKS, APKM and XAPK.

val splits: Sequence<Apk> = ZippedApkSplits.getApksForUri(zippedFileUri, context)
val splitsList = splits.toList()
Sequence<Apk> splits = ZippedApkSplits.getApksForUri(zippedFileUri, context);
List<Apk> splitsList = new ArrayList<>();
for (var iterator = splits.iterator(); iterator.hasNext(); ) {
    var apk = iterator.next();
    splitsList.add(apk);
}

Attention

Iteration of these sequences is blocking due to I/O operations. Don't iterate them on UI thread!

Apk has the following properties:

val uri: Uri
val name: String
val size: Long
val packageName: String
val versionCode: Long
val description: String

Note

If your application doesn't have direct access to files (via READ_EXTERNAL_STORAGE permission), parsing and iteration of the sequences may be much slower or even fail on Android versions lower than 8.0 Oreo, because Ackpine may fall back to using ZipInputStream for these operations.

Apk has the following types: Base for base APK, Feature for a feature split, Libs for an APK split containing native libraries, ScreenDensity for an APK split containing graphic resources tailored to specific screen density, Localization for an APK split containing localized resources and Other for an unknown APK split. They also have their specific properties. Refer to API documentation for details.

ApkSplits class contains utilities for transforming Apk sequences.

val sortedSplitsList = mutableListOf<ApkCompatibility>()
// Building a one-time pipeline
val splits = ZippedApkSplits.getApksForUri(zippedFileUri, context)
    .sortedByCompatibility(context)
    .addAllTo(sortedSplitsList)
    .filterCompatible()
    .throwOnInvalidSplitPackage()
val splitsList = try {
    splits.toList()
} catch (exception: SplitPackageException) {
    println(exception)
    emptyList()
}
sortedSplitsList.filterNot { it.isPreferred }
    .map { it.apk }
    .forEach(::println) // prints incompatible APKs
List<ApkCompatibility> sortedSplitsList = new ArrayList<>();
// Building a one-time pipeline
Sequence<Apk> zippedApkSplits = ZippedApkSplits.getApksForUri(uri, context);
Sequence<ApkCompatibility> sortedSplits = ApkSplits.sortedByCompatibility(zippedApkSplits, context);
Sequence<ApkCompatibility> addingToListSplits = ApkSplits.addAllTo(sortedSplits, sortedSplitsList);
Sequence<Apk> filteredSplits = ApkSplits.filterCompatible(addingToListSplits);
Sequence<Apk> splits = ApkSplits.throwOnInvalidSplitPackage(filteredSplits);
List<Apk> splitsList = new ArrayList<>();
try {
    for (var iterator = splits.iterator(); iterator.hasNext(); ) {
        var apk = iterator.next();
        splitsList.add(apk);
    }
} catch (SplitPackageException exception) {
    System.out.println(exception);
    splitsList = Collections.emptyList();
}
for (var apkCompatibility : sortedSplitsList) {
    if (!apkCompatibility.isPreferred()) {
        System.out.println(apkCompatibility.getApk()); // prints incompatible APKs
    }
}

Creating APK splits from separate files

You can parse an APK file from a File or Uri using static Apk factories:

val apkFromFile: Apk? = Apk.fromFile(file, context)
val apkFromUri: Apk? = Apk.fromUri(uri, context)
Apk apkFromFile = Apk.fromFile(file, context);
Apk apkFromUri = Apk.fromUri(uri, context);