In the first part of this post I described a way to build release packages of KDE apps for Android using Craft on KDE’s Binary Factory infrastructure. In this part we are now going to look at how to review and optimize the package content, and where to get the metadata for the app stores from.

Inspecting APKs

Before starting this work KDE Itinerary’s nightly build APK for 32bit ARM was about 36MB, without translation catalogs. The first working and complete release APK built with Craft came in at over 140MB, obviously not the direction I was going for. Fortunately there’s a number of ways to optimize this, currently we are approaching about 22MB, without loss of functionality and full translations.

Like with any other optimization task, we need ways to measure and compare results. The Android SDK provides two useful tools for this:

  • apkanalyzer as a command-line tool, which allows list APK content including their sizes, and to compare two versions of an APK.

  • Android Studio has a graphical UI for this, under Build > Analyze APK. Once you have an APK loaded, there’s also a way to compare that with a previous version via the button in the top right.

Screenshot of Android Studio showing a size analysis of KDE Itinerary.
Android Studio APK analyzer.

Also, APKs are just ZIP files, so Ark or a similar tool is also useful to have around.

Optimizing APKs

In case of KDE Itinerary, the vast majority of optimizations were simply dropping things that aren’t needed at all, and that ended up in there due to too aggressive inclusion rules for androiddeployqt. Depending on where a file is coming from, there’s one or more options to get them out of the package again:

  • Do not install files that are not needed at runtime. That’s often just adding an Android conditional in a CMake file, common examples are AppStream or .desktop files (although those usually only contribute a few kB in size). For external dependencies build options to build a specific plugin or to disable the creation of e.g. man pages or API docs can be helpful.

  • Exclude implicitly included plugins. androiddeployqt by itself includes all plugins of a specific type, if a library you use asks for that. Via that mechanism we get a bunch of Qt image format or QML tooling plugins included for example. If we can’t avoid those plugins being built and installed in the first place, there is a way to explicitly exclude library files (and thus plugins) in the build.gradle file of the application:

android {
    packagingOptions {
        exclude 'lib/*/*_Controls.2_Imagine_*'
        exclude 'lib/*/*_qmltooling_*'
  • Exclude implicitly included assets. We unfortunately need to treat assets differently from libraries and plugins, the above Gradle options don’t apply to them. Instead there is a much more limited and unusual exclusion syntax for this, which also cannot be repeated but needs to be squeezed into a single line. This is particularly useful for filtering things remaining in assets/share. Ordering content by size is usually a good idea for prioritization, but it’s worth paying special attention to unneeded translation catalogs when doing that, those are typically small by themselves, but multiply by the number of languages.
android {
    aaptOptions {
        ignoreAssetsPattern '!<dir>ECM:<dir>qlogging-categories5:<file>!<file>'
  • Do not link against unneeded libraries. Unlike plugins, androiddeployqt will not include libraries in the package that aren’t used. Usage here however means another library links to it, not that it’s practically really needed. QtWidgets is such an example in case of KDE Itinerary, costing a few megabyte. Just adding that to the exclusion list will break the application though, we actually need to remove the linking altogether. In this case QtWidgets was pulled in unconditionally by KNotifications, which is fixed in KDE Frameworks 5.82.

Once there is not much more to gain with simply excluding content this way, more elaborate techniques like static linking or looking into what actually takes up the space in libraries with tools like ELF Dissector might be worth exploring, effort vs gain tends to be harder to estimate then though.

Package Metadata

The application package alone isn’t enough for distributing it via any of the APK stores, we also need the associated metadata describing the application, contact and licensing information, icons, screenshots, etc, and ideally all translated as well.

This is however information that largely already exists, in form of the AppStream metadata for use on other platforms. The nightly F-Droid builds used to generate the basic metadata from those files as part of the publication process. With KDE Frameworks 5.78 we moved this to the build process, as part of ECM’s Android support.

Besides covering a lot more information (in particular, screenshots), this can now also be augmented by data that is not modelled in AppStream, such as F-Droid store header pictures. This is done by merging metadata found in the Fastlane format used by F-Droid with the data derived from other sources, to give us the features and flexibility while keeping additional maintenance and translation work as minimal as possible.

Screenshot of the F-Droid store showing KDE Itinerary's information.
KDE Itinerary's F-Droid store presence, fed automatically from existing AppStream metadata.


While I’m quite happy with the recent progress, there is more to do of course.

It would be nice to see more apps to go through this process. It was certainly the goal to come up with something that is widely applicable and more sustainable than ad hoc alternatives, but that yet has to be validated.

And of course there is still one final bit missing, automating the publication in proprietary APK stores, most notably Google Play.