This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/android/faq/framework-size.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Framework size

You might be wondering why the Nutrient .aar file is so large.

The Nutrient SDK covers most of the PDF specification, which contains thousands of pages, making it quite complex. Nutrient includes a complete PDF renderer, cryptography, and many UI components. Most of the AAR is the native PDF engine, which we compile separately for each supported ABI (arm64-v8a, armeabi-v7a, x86_64, and x86). A device only needs one of them, so with ABI splits or App Bundles, your users download much less than the full AAR. We’re working hard to ensure the framework size stays as low as possible.

Architectures

Nutrient includes its native libraries for the following Android ABIs:

  • arm64-v8a
  • armeabi-v7a
  • x86_64
  • x86

A device only loads the libraries for its own ABI. To avoid shipping all four to every user, use ABI splits or Android App Bundles.

Reducing the size of your app

Three techniques reduce the download size of your app: R8, ABI splits, and Android App Bundles.

R8

Enable R8(opens in a new tab) for your release builds to remove unused code and resources from your app and its dependencies. Nutrient ships consumer R8 rules in the AAR, so you don’t need to add any rules for it:

android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}

The consumer rules keep all of Nutrient’s classes, so R8 doesn’t shrink the SDK itself. Most of Nutrient’s size is its native libraries, which ABI splits and App Bundles reduce.

ABI split

Another technique to reduce the overall download size of your final APK is by using ABI splits. Each APK then only contains the native libraries for a single ABI. ABI splits require you to prepare both your build setup and your Google Play entry (if you are publishing via Google Play) by doing the following:

Android App Bundles

Android App Bundles(opens in a new tab) provide automated modularization of your distributed app. Google Play generates APKs that only contain the native libraries and resources each device needs, which can yield much smaller download and install sizes. Our own trials with PDF Viewer(opens in a new tab) showed an average download size reduction of 50 percent for most users. However, the final number depends upon the specific app and needs to be evaluated on a per case basis.

Moreover, App Bundles allow for dynamic distribution of features inside your app, which can be used to load “secondary features” on demand, rather than installing them with the initial APK. An in-depth guide about Android App Bundles can be found at the official Android App Bundle documentation(opens in a new tab).

Third-party libraries

Kotlin coroutines

Internally, we use Kotlin coroutines(opens in a new tab) for structuring our code, coordinating asynchronous operations, and performing secure and stable multithreading. Also, many of the public API methods of Nutrient are available in two flavors: a blocking call, directly returning the requested result; and an asynchronous version of the same method, which is either a suspend function or returns a Kotlin Flow(opens in a new tab). Most asynchronous versions move their work off the calling thread and return the result as soon as it’s available. A few, such as XfdfFormatter.parseXfdfAsync(), XfdfFormatter.writeXfdfAsync(), and DocumentJsonFormatter.exportDocumentJson(), run on the caller’s coroutine context, so call them from a background dispatcher like Dispatchers.IO.

Here’s an example snippet that searches a document in a non-blocking way. Search is performed on a background thread, while the results are collected on the main thread, allowing you to simply update the UI. From Java, call the blocking version on a worker thread instead:

// Perform an asynchronous search on a background thread and update the UI on the main thread.
val searchJob = lifecycleScope.launch {
textSearch.performSearchAsync(query).collect { nextResult ->
// This will be called once for every `SearchResult` object.
// Put your search result handling here.
}
}

Keep an eye open for *Async() methods, which are usually suspend functions or return a Flow(opens in a new tab). To call them from Java, see calling asynchronous APIs from Java.

Nutrient declares kotlinx-coroutines-core as an API dependency, so the Flow and Job types it returns are always available in your app. It doesn’t depend on RxJava.

Rendering PDF documents

Rendering PDF documents is rather complex. We have a separate guide explaining some of the challenges.