Перейти к содержимому

Эта страница ещё не переведена и показана на английском.

Manifest and resources

The android block of elide.pkl: namespace and API levels, the AndroidManifest.xml it generates, how XML merges over it, and the Android artifacts.

Читать офлайнelide help --lang ru android-manifest

На этой странице

A project is an Android project when its elide.pkl declares android. Only namespace and compileSdk are required; everything else has a default or is optional.

pkl
amends "elide:project.pkl"

import "elide:Android.pkl" as Android

name = "my-app"

android {
  namespace = "com.example.app"
  compileSdk = 36
}

artifacts {
  ["app"] = new Android.AndroidApk { release = false }
}
utf-8 · pkl · Lines: 14

Settings

PropertyTypeMeaning
namespacepackage nameThe package R lives in and relative class names resolve against; the default applicationId. Required.
compileSdkAPI levelThe platform the app compiles, links and dexes against. Required.
minSdkAPI levelThe oldest release the app runs on. Default compileSdk.
targetSdkAPI levelThe release the app declares it was tested against.
buildTools"36.0.0"The build-tools release; default resolved from the SDK (Android SDK).
sdkpathThe SDK root, "managed" or "system" (Android SDK).
applicationobjectThe generated AndroidManifest.xml (below).
resourcesobjectGenerated value resources (below).
deviceobjectThe project’s emulator (Run and debug).
devicesmappingNamed test devices (Android tests).
hostTestsartifact nameThe artifact host tests run against (Android tests).
hiltBooleanRun Hilt’s bytecode transformation (Android dependencies).

API levels

The build owns the API levels, and the merged manifest carries them:

LevelManifest omits itManifest declares another value
minSdkThe build’s is written.The build fails: the dex would not run where it installs.
targetSdktargetSdk, else compileSdk, is written.The build fails when targetSdk is declared; otherwise the manifest’s stands.

A library’s manifest records its minimum and never a target.

Namespace and application id

namespace is where R lives. applicationId, declared on an application artifact, is the package the app installs and publishes as, and defaults to the namespace. A package attribute in an AndroidManifest.xml must agree with the namespace, or the build fails.

Two artifacts built from the same sources with different applicationIds install side by side, so a debug build can be installed beside a release build:

pkl
artifacts {
  ["app"] = new Android.AndroidApk {
    versionCode = 3
    versionName = "1.2.0"
  }
  ["app-dev"] = new Android.AndroidApk {
    applicationId = "com.example.app.dev"
    release = false
    resources { "src/main/res"; "src/debug/res" }
  }
}
utf-8 · pkl · Lines: 11

Kotlin defaults

A project declaring android defaults kotlin.compilerOptions.jvmTarget to 17, since the app runs against android.jar on a device rather than on the JVM running the build. It also turns off the Kotlin features kotlinx, defaultPlugins and reflection, whose runtimes would otherwise be dexed into the app. Declaring a feature turns it back on:

pkl
kotlin {
  features {
    reflection = true
  }
}
utf-8 · pkl · Lines: 5

Generated manifest

android.application describes the application, and the build generates AndroidManifest.xml from it:

pkl
android {
  namespace = "com.example.app"
  compileSdk = 36
  minSdk = 30
  targetSdk = 36

  application {
    name = ".App"
    label = "@string/app_name"
    icon = "@mipmap/ic_launcher"
    theme = "@android:style/Theme.Material.Light.NoActionBar"
    allowBackup = false
    supportsRtl = true
    activities {
      ["MainActivity"] { launcher = true }
      [".settings.SettingsActivity"] { label = "@string/settings"; exported = false }
    }
    services {
      [".sync.SyncService"] { foregroundServiceType = "dataSync"; exported = false }
    }
    receivers {
      [".BootReceiver"] {
        exported = true
        actions { "android.intent.action.BOOT_COMPLETED" }
      }
    }
    providers {
      [".data.FilesProvider"] {
        authorities { "${applicationId}.files" }
        exported = false
        grantUriPermissions = true
      }
    }
    permissions {
      "android.permission.INTERNET"
      "android.permission.RECEIVE_BOOT_COMPLETED"
    }
    features {
      ["android.hardware.camera"] { required = false }
    }
  }
}
utf-8 · pkl · Lines: 42
PklGenerates
name, label, icon, roundIcon, theme, allowBackup, supportsRtl<application> attributes of the same name
activities<activity>; launcher = true adds the MAIN/LAUNCHER intent filter and exports it
services<service>, with foregroundServiceType
receivers<receiver>; actions become its intent filter
providers<provider>, with authorities and grantUriPermissions
permissions<uses-permission>
features<uses-feature>, required by default

Every component takes exported and permission; activities also take label and theme. Components are keyed by class name, fully qualified or relative to the namespace (.MainActivity and MainActivity are the same class). Android 12 and later refuse a component with an intent filter that does not declare exported; a launcher activity is exported unless it says otherwise.

As with any manifest, the build adds the values it owns to the generated one: uses-sdk, versionCode, versionName, applicationId, and android:debuggable for a debug build.

Merging with AndroidManifest.xml

src/main/AndroidManifest.xml is optional. When it exists it is merged over the generated document, so XML overrides and extends Pkl and never the reverse:

  • an attribute both declare takes the XML’s value, without a conflict;
  • an element only the XML declares is added;
  • tools:node="remove", tools:node="replace" and tools:remove in the XML act on what was generated.

Keep in XML what the model does not name: intent filters beyond the launcher, <meta-data>, <queries>, and attributes such as android:networkSecurityConfig.

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
  <application android:networkSecurityConfig="@xml/network_security_config">
    <activity android:name=".MainActivity" android:windowSoftInputMode="adjustResize" />
  </application>
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" tools:node="remove" />
</manifest>
utf-8 · xml · Lines: 7

Libraries’ manifests merge under the application’s with Android’s rules: the application wins, an earlier declaration beats a later one, and two different values for one attribute are an error naming both files unless tools:replace settles it. tools:node (merge, replace, remove, removeAll, strict, mergeOnlyAttributes), tools:remove and tools:overrideLibrary are honored, and no tools: attribute reaches the APK. A library asking for a higher minSdkVersion than the app is an error that tools:overrideLibrary waives.

Placeholders

${name} in any manifest is replaced from the artifact’s placeholders, plus applicationId. An unknown placeholder fails the build. A library substitutes only its own and leaves the rest for the application.

pkl
["app"] = new Android.AndroidApk {
  placeholders { ["appLabel"] = "My App" }
}
utf-8 · pkl · Lines: 3

String resources

android.resources.strings generates <string> resources of the default configuration:

pkl
android {
  namespace = "com.example.app"
  compileSdk = 36
  resources {
    strings {
      ["app_name"] = "My App"
      ["greeting"] = "Don't panic"
    }
  }
}
utf-8 · pkl · Lines: 10

Values are written as given; quotes, apostrophes, backslashes, newlines and a leading @ or ? are escaped for aapt2. The generated resources are linked after any library and before the project’s own resource directories, so a string the project’s res/values also declares resolves to the XML, and one a library declares resolves to the project. Translations and other configurations (values-fr/, values-night/) stay in res/.

Code reads them as any resource: R.string.greeting, stringResource(R.string.greeting).

Project layout

PathHolds
src/main/kotlin, src/main/javaSources
src/main/AndroidManifest.xmlOptional; merged over the generated manifest
src/main/resResources
src/main/assetsAssets, packaged under assets/
src/main/jniLibs/<abi>/lib<name>.soNative libraries
src/testHost tests (Android tests)
src/androidTestInstrumented tests (Android tests)

Declaring sources, resources, assets, jniLibs or manifestFile on an artifact replaces the convention for that field alone. A declared directory or source set that does not exist fails the build.

Artifacts

All Android artifacts take:

PropertyDefaultMeaning
nameartifact name plus .apk, .aab or .aarPublished file name.
sourcesevery non-test source setSource sets whose classes it carries.
manifestFilesrc/main/AndroidManifest.xmlManifest merged over the generated one.
resourcessrc/main/resResource directories.
assetssrc/main/assetsAsset directories.
jniLibssrc/main/jniLibsNative library directories.
placeholdersemptyManifest placeholder values.
linkArgsemptyExtra aapt2 link arguments.

Applications (AndroidApk, AndroidAab) add:

PropertyDefaultMeaning
applicationIdnamespaceInstalled and published package.
versionCode, versionNamenoneReplace any the manifest declares.
releasetruefalse is a debuggable debug build.
signingnoneKeystore (Signing and release).
minifyfalseShrink with R8 in place of d8.
proguardFilesemptyR8 rules, after the libraries’ own.
abiFiltersempty: every ABIABIs whose native libraries ship.
packaging.excludesemptyJava resources left out (below).
dexArgsemptyExtra d8 or R8 arguments.

AndroidApk adds instrumentation, which makes it a test APK (Android tests). AndroidAab adds bundleArgs (extra bundletool build-bundle arguments) and features. AndroidAar adds consumerProguardFiles.

Native libraries

Native libraries are laid out as <abi>/lib<name>.so under each jniLibs directory and packaged under lib/<abi>/, stored uncompressed and aligned to 16 KB pages, which Android 15 and later require on 16 KB devices. The .so files themselves must be linked for 16 KB pages (-z max-page-size=16384). Alignment needs build-tools 35.0.0-rc3 or later; with an older buildTools, the newest installed release that has it is used.

pkl
["app"] = new Android.AndroidApk {
  abiFilters { "arm64-v8a"; "x86_64" }
}
utf-8 · pkl · Lines: 3

Java resources

Non-class files in the jars an app depends on ship at the APK’s root, as the Android Gradle Plugin packages them. META-INF/services files are concatenated; any other duplicated path ships from the first jar carrying it. Signatures, jar manifests, META-INF/maven, META-INF/proguard, licence and notice files are left out by default. packaging.excludes leaves out more, with glob patterns matched against the path with a leading /:

pkl
["app"] = new Android.AndroidApk {
  packaging {
    excludes { "/META-INF/{AL2.0,LGPL2.1}"; "**/*.kotlin_builtins" }
  }
}
utf-8 · pkl · Lines: 5

Libraries

An AndroidAar publishes an Android library:

pkl
artifacts {
  ["ui"] = new Android.AndroidAar {
    consumerProguardFiles { "consumer-rules.pro" }
  }
}
utf-8 · pkl · Lines: 5

elide build ui writes .dev/artifacts/ui.aar with the library’s AndroidManifest.xml, classes.jar, its resources as written (res/), R.txt, assets/, jni/<abi>/ and its consumerProguardFiles as proguard.txt. Resources ship as written, not compiled, because an application links its own and its libraries’ resources in one pass, which gives every identifier one numbering.

An application consumes a library of the same workspace through an ordinary project reference:

pkl
// elide.pkl (workspace root)
amends "elide:project.pkl"

name = "my-workspace"

workspace {
  members {
    "lib"
    "app"
  }
}
utf-8 · pkl · Lines: 11
pkl
// app/elide.pkl
amends "elide:project.pkl"

import "elide:Android.pkl" as Android

local lib = project("lib")

name = "app"

android {
  namespace = "com.example.app"
  compileSdk = 36
}

dependencies {
  maven {
    packages { lib }
  }
}

artifacts {
  ["app"] = new Android.AndroidApk { release = false }
}
utf-8 · pkl · Lines: 23

The application reads the library’s classes, resources, manifest, assets and native libraries straight from its build, never from its .aar: elide build app builds the library’s classes but not its archive. References are followed transitively. Precedence is Android’s: the application beats its libraries, a nearer reference beats a transitive one, and an earlier declaration beats a later one. A library’s string is overridden by declaring the same name in the application.

When workspace members resolve one library at different versions, the newest ships, as Gradle’s conflict resolution would pick it, and its resources and manifest are the ones linked, so code and R agree. Published AARs are covered in Android dependencies.

App bundles

An AndroidAab builds the bundle a store splits into per-device APKs:

pkl
artifacts {
  ["bundle"] = new Android.AndroidAab {
    versionCode = 3
    versionName = "1.2.0"
    abiFilters { "arm64-v8a" }
    signing {
      keystore = "../keys/upload.jks"
      alias = "upload"
      dname = "CN=Example, O=Example, C=US"
      validity = 9125
      passwordEnv = "UPLOAD_KEYSTORE_PASS"
    }
  }
}
utf-8 · pkl · Lines: 14

elide build bundle writes .dev/artifacts/bundle.aab. A bundle is signed with the upload key when it declares signing (Signing and release); the store re-signs the APKs it generates with the app signing key. Nothing installs a bundle directly, so it has no run or debug target.

Dynamic feature modules

An AndroidFeature is a module of a bundle, delivered at install time or on demand. Its sources are a source set of the app project, and it declares its own manifest, whose package is its R namespace:

pkl
amends "elide:project.pkl"

import "elide:Android.pkl" as Android
import "elide:Sources.pkl" as Sources

name = "my-app"

android {
  namespace = "com.example.app"
  compileSdk = 36
}

sources {
  ["main"] = new Sources.SourceSetSpec { paths { "src/main/kotlin/**/*.kt" } }
  ["detail"] = new Sources.SourceSetSpec {
    paths { "src/detail/kotlin/**/*.kt" }
    dependsOn { "main" }
  }
}

artifacts {
  ["bundle"] = new Android.AndroidAab {
    sources { "main" }
    features { "detail" }
  }
  ["detail"] = new Android.AndroidFeature {
    base = "bundle"
    sources { "detail" }
    manifestFile = "src/detail/AndroidManifest.xml"
    resources { "src/detail/res" }
    delivery = "on-demand"
    title = "@string/detail_title"
    fusing = false
  }
}
utf-8 · pkl · Lines: 35
  • title names a string the base declares.
  • delivery is "on-demand" (default) or "install-time"; it and fusing generate the dist:module element, so do not declare one in XML.
  • A feature takes no defaults from the layout: declare its sources, and its resources, assets and jniLibs when it has any.
  • featureDependencies { "common" } declares a feature it depends on; its source set’s dependsOn lists both "main" and "common".
  • The base owns every Maven and workspace dependency; features dex only their own classes.

elide build android-module-detail builds the feature module alone. On-demand delivery in a shipped app goes through Play Feature Delivery’s SplitInstallManager.

Была ли эта страница полезной?