As Android projects scale into multi-module architectures or shared SDKs, isolating reusable features—such as custom UI components, networking engines, or analytics clients—into modular Android Archive (`.aar`) libraries becomes essential.
Unlike standard Java Archive (.jar) files that contain only compiled .class bytecode, an Android .aar package bundles compiled bytecode along with Android manifests, layouts, drawable resources, and native .so libraries. In this article, we demonstrate how to construct an Android Library module, expose public API contracts, and invoke library functions from a host application.
Quick Syntax Reference: Module Dependency Setup
Configure Gradle settings and build files to link a local library module (:mylibrary) to your main application module (:app):
// Include both application and library modules in project build
include(":app")
include(":mylibrary")plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
dependencies {
// Link local library module dependency
implementation(project(":mylibrary"))
}1. Defining Public API Contracts in the Library Module
Inside the :mylibrary module, create a public singleton SDK entry point to handle context initialization and expose public helper methods:
package com.example.mylibrary
import android.content.Context
import android.widget.Toast
object PrinterSDK {
private var isInitialized = false
fun initialize(context: Context, apiKey: String) {
// Validate SDK setup
require(apiKey.isNotBlank()) { "API Key cannot be empty" }
isInitialized = true
Toast.makeText(context.applicationContext, "PrinterSDK Initialized", Toast.LENGTH_SHORT).show()
}
fun printMessage(context: Context, message: String): String {
check(isInitialized) { "PrinterSDK must be initialized before calling printMessage()" }
val formatted = "[SDK-Output] $message"
Toast.makeText(context.applicationContext, formatted, Toast.LENGTH_LONG).show()
return formatted
}
}2. Configuring build.gradle.kts for Android Library Export
The library module Gradle configuration uses the com.android.library plugin and enforces a unique namespace to prevent resource collisions:
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.mylibrary"
compileSdk = 34
defaultConfig {
minSdk = 24
// Prevent resource name collisions with host applications
resourcePrefix("lib_")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}3. Invoking Library Functions from the Main Application
Inside the :app module, import the library SDK package and invoke exposed public functions directly:
package com.example.myapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.mylibrary.PrinterSDK // Import library SDK class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize library SDK with application context
PrinterSDK.initialize(this, apiKey = "SDK_SECRET_KEY_12345")
// Call library function
val result = PrinterSDK.printMessage(this, "Hello from Application Module!")
println("Returned Result: $result")
}
}AAR Archive Internal File Layout
`classes.jar`: Compiled Java/Kotlin bytecode for the library module.
`AndroidManifest.xml`: Library manifest merged into the main application manifest during build.
`res/`: Layouts, drawables, and XML resources provided by the library.
`R.txt` & `public.txt`: Symbol table listing exported public resource identifiers.
`jni/`: Native
.soshared libraries compiled for target CPU architectures (arm64-v8a, x86_64).`proguard.txt`: Consumer ProGuard keep rules automatically applied when consumer apps obfuscate code.
Comments and corrections