Split APKs¶
ackpine-splits
artifact contains utilities for working with split APK files.
Reading 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.
Working with splits¶
ApkSplits
class contains utilities for transforming Apk
sequences. In Kotlin they appear as extensions of Sequence<Apk>
and Sequence<ApkCompatibility>
.
val sortedSplitsList = mutableListOf<ApkCompatibility>()
// Building a one-time pipeline
val splits = ZippedApkSplits.getApksForUri(zippedFileUri, context)
.throwOnInvalidSplitPackage()
.sortedByCompatibility(context)
.addAllTo(sortedSplitsList)
.filterCompatible()
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<Apk> verifiedSplits = ApkSplits.throwOnInvalidSplitPackage(zippedApkSplits);
Sequence<ApkCompatibility> sortedSplits = ApkSplits.sortedByCompatibility(verifiedSplits, context);
Sequence<ApkCompatibility> addingToListSplits = ApkSplits.addAllTo(sortedSplits, sortedSplitsList);
Sequence<Apk> filteredSplits = ApkSplits.filterCompatible(addingToListSplits);
List<Apk> splitsList = new ArrayList<>();
try {
for (var iterator = filteredSplits.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
}
}
-
throwOnInvalidSplitPackage()
operation validates a split package and throwsSplitPackageException
if it's not valid when the sequence is iterated. -
sortedByCompatibility(Context)
operation returns a sequence that contains APK splits sorted according to their compatibility with the device. The most preferred APK splits will appear first. If exact device's screen density, ABI or locale doesn't appear in the splits, nearest matching split is chosen as a preferred one. The returned sequence containsApkCompatibility
objects, which are pairs of anisPreferred
flag and anApk
object being checked. -
filterCompatible()
operation filters out the splits which are not the most preferred for the device. AfilterCompatible(Context)
overload is the same as writingsortedByCompatibility(context).filterCompatible()
.
Note
To correctly close I/O resources and skip unnecessary I/O operations, it's best to apply throwOnInvalidSplitPackage()
immediately after creating the sequence with ZippedApkSplits
factories.
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);