Aller au contenu

Cette page n'est pas encore traduite. Elle s'affiche en anglais.

Signing and release

Sign Android apps with a debug or release keystore, keep passwords in the environment, and build shrunk, signed APKs and bundles with elide build --release.

Lire hors ligneelide help --lang fr android-release

Sur cette page

Signing

ArtifactSigned with
Declares signingThe keystore it names.
A debug build (release unset, the default, or false), no signingThe Android debug keystore, created on first use.
A release build (release = true, or unset under --release), no signingNothing: published unsigned, with a warning.

An APK is signed with apksigner, with the signature schemes a device checks on install. A bundle is signed with the JDK’s jarsigner, which is how a store’s upload key is applied. An unsigned APK cannot be installed, so it has no run, debug or test target.

The debug keystore

A debug build with no signing is signed with debug.keystore in Android’s user directory (ANDROID_USER_HOME, else ~/.android), the keystore Android Studio and the Android Gradle Plugin sign debug builds with, so an app built by either installs over the other. When it is missing it is created with the Android debug conventions: alias androiddebugkey, password android, CN=Android Debug,O=Android,C=US, RSA 2048, valid for 30 years. An existing file is never replaced.

Your own keystore

pkl
artifacts {
  ["app"] = new Android.AndroidApk {
    signing {
      keystore = "../keys/release.jks"
      alias = "release"
      dname = "CN=Example, O=Example, C=US"
      validity = 9125
      passwordEnv = "RELEASE_KEYSTORE_PASS"
      keyPasswordEnv = "RELEASE_KEY_PASS"
    }
  }
}
utf-8 · pkl · Lines: 12
PropertyDefaultMeaning
keystorerequiredKeystore path, relative to the project.
aliasandroiddebugkeyKey alias.
passwordEnvANDROID_KEYSTORE_PASSVariable holding the keystore password.
keyPasswordEnvnoneVariable holding the key password, when it differs.
dnameCN=Android Debug, O=Android, C=USDistinguished name of a key Elide creates.
validity10000Days a key Elide creates stays valid.

Passwords never appear in the manifest or on a command line. The variables are read at signing time and handed to apksigner, jarsigner and keytool through their environment, so the process table never shows them. An unset variable falls back to android, the debug keystore’s password. Password values are redacted from errors.

A build that signs checks the keystore before compiling anything:

text
error: Keystore /path/to/app/../keys/release.jks not found
hint: Create one with: elide build android-keystore-app
utf-8 · text · Lines: 2

Builds that do not sign (compiling, host tests) never need the keystore.

jarsigner and keytool come from a JDK: the build’s own, the one running Elide, then JAVA_HOME, then PATH.

Create a release keystore

elide build android-keystore-<artifact> creates the keystore an artifact’s signing names, once; it never overwrites one or adds a key to it. With --release it refuses anything that would make it a debug key: an artifact with no signing, the debug keystore’s path, the alias androiddebugkey, the debug distinguished name, the default validity, and a passwordEnv that is unset.

Terminal
bash
export RELEASE_KEYSTORE_PASS=…   # from your secret store
export RELEASE_KEY_PASS=…
elide build android-keystore-app --release

Keep the keystore out of the repository, and back it up: an app on a store can only be updated with the key it was first signed with (or, with Play App Signing, the upload key Play knows).

Release builds

Terminal
bash
elide build --release

An application that leaves release unset is a debug build: debuggable, and signed with the debug keystore when it declares no signing. --release makes every such application a release build; one that declares release is what it declares, with or without the flag. --release also decides minify for an application that leaves it unset:

The applicationWithout --releaseWith --release
declares minifyas declaredas declared
declares proguardFiles, or consumes a workspace library declaring consumerProguardFilesd8R8
consumes published libraries onlyd8R8 if one ships proguard.txt, else d8
is a bundle declaring feature modulesd8d8

In the third case the build says so when it falls back to d8 (… with d8, unshrunk: neither 'app' nor its libraries declare ProGuard rules). Under --release, an application that may be shrunk has no instrumented tests (Testing). What a build wrote without --release is never reported up to date for a build with it.

SettingDecides
releaseWhether debug information is kept and the app is debuggable.
minifyWhether R8 shrinks, optimizes and obfuscates the code, in place of d8.

Either can be set without the other. A release APK for a store:

pkl
artifacts {
  ["app"] = new Android.AndroidApk {
    versionCode = 4
    versionName = "1.3.0"
    release = true
    minify = true
    proguardFiles { "proguard-rules.pro" }
    signing {
      keystore = "../keys/release.jks"
      alias = "release"
      dname = "CN=Example, O=Example, C=US"
      validity = 9125
      passwordEnv = "RELEASE_KEYSTORE_PASS"
    }
  }
}
utf-8 · pkl · Lines: 16

Shrinking with R8

R8 is pinned per Elide release, downloaded once and verified by SHA-256; the Android SDK does not ship it. Its rules are applied in one chain: what consumed libraries contribute (a published AAR’s proguard.txt, a workspace library’s consumerProguardFiles), then the artifact’s own proguardFiles. A build that would run R8 with no rules fails instead, since R8 with nothing to keep strips the app to nothing.

Ahead of that chain, R8 is given the keep rules aapt2 link --proguard writes for the classes the merged manifest and the resources name: the application class, activities, services, receivers, providers, instrumentation, and views a layout inflates. The platform instantiates these by name, so a shrink never removes them, and your rules need keep only what the app reaches by reflection. A declared minify whose libraries and proguardFiles contribute no rule is still refused.

text
# proguard-rules.pro
# Keep line numbers, so stack traces from the field can be mapped back.
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
utf-8 · text · Lines: 4

mapping.txt

A minified build publishes R8’s mapping beside the archive, as .dev/artifacts/<artifact>-mapping.txt. It is the only way to read a stack trace from a shipped build: keep it with every release, and upload it to the store with the bundle. A shrink served from the build cache still publishes it.

The lockfile records which d8 or R8 produced each application’s dex (r8 9.4.17, or d8 build-tools;35.0.0), and elide project info shows it (Android SDK).

App bundles

Stores take an app bundle rather than an APK:

Terminal
bash
export UPLOAD_KEYSTORE_PASS=…
elide build android-keystore-bundle --release   # once
elide build --release bundle

The bundle (.dev/artifacts/bundle.aab) is signed with the upload key; the store re-signs the APKs it generates with the app signing key. A bundle with dynamic feature modules cannot be minified. Bundle settings are covered in Manifest and resources.

Reproducible builds

A build whose SDK components, R8 and bundletool releases are all recorded in the lockfile produces the same unsigned, aligned APK and the same unsigned bundle on another machine, from another checkout path and in another time zone. Every archive Elide writes stamps its entries with a fixed date and adds them in a fixed order, tool inputs are sorted, and no host path reaches an archive. Signing is outside this: a JAR signature records when it was made, so compare the aligned APK (.dev/android/<a>/aligned.apk) or an unsigned archive. Commit .dev/elide.lock.v2.bin, run elide install before building in CI, and re-resolve deliberately with elide install --update (Android SDK).

Terminal
bash
elide install --accept-android-licenses
elide build --release

Cette page vous a-t-elle aidé ?