|
| 1 | +--- |
| 2 | +title: Restrict Android engine flags in release mode |
| 3 | +description: >- |
| 4 | + Passing engine configuration flags to Android release binaries using |
| 5 | + Android `Intent` extras or `--use-application-binary` is no longer supported. |
| 6 | +--- |
| 7 | + |
| 8 | +{% render "docs/breaking-changes.md" %} |
| 9 | + |
| 10 | +## Summary |
| 11 | + |
| 12 | +Previously, the Flutter Android embedding accepted engine configuration flags |
| 13 | +(such as `--dart-flags`, `--route`, or tracing flags) |
| 14 | +at launch time through Android `Intent` extras in all build modes. |
| 15 | +This mechanism allowed developers and tools—including the Flutter CLI |
| 16 | +when using `--use-application-binary`—to dynamically configure |
| 17 | +running applications without modifying their source code or manifest. |
| 18 | + |
| 19 | +To protect production applications against `Intent`-based spoofing |
| 20 | +and parameter injection vulnerabilities, |
| 21 | +Flutter Android release builds now ignore engine configuration flags |
| 22 | +passed through `Intent` extras, |
| 23 | +reading engine configuration strictly from the compiled `AndroidManifest.xml` |
| 24 | +or from programmatic configuration prior to engine initialization. |
| 25 | + |
| 26 | +This change impacts workflows in two ways: |
| 27 | + |
| 28 | +- **Direct `Intent` launches:** |
| 29 | + Passing engine configuration flags through `Intent` extras |
| 30 | + (such as through `adb shell am start` or native Android code) |
| 31 | + is ignored by the embedding in release builds. |
| 32 | +- **Flutter CLI prebuilt binaries:** |
| 33 | + Because prebuilt binaries cannot have their manifests dynamically modified |
| 34 | + after compilation, the Flutter CLI now produces a fatal error |
| 35 | + if you pass engine configuration flags to a prebuilt release binary |
| 36 | + with `flutter run --release --use-application-binary`. |
| 37 | + This prevents flags from being silently ignored. |
| 38 | + |
| 39 | +Standard release builds |
| 40 | +(where the CLI compiles the app with Gradle and injects flags into the manifest) |
| 41 | +and all debug and profile workflows continue to work without changes. |
| 42 | + |
| 43 | +## Context |
| 44 | + |
| 45 | +The Flutter engine accepts configuration flags |
| 46 | +(such as `--dart-flags`, `--route`, or `--trace-startup`) |
| 47 | +to configure runtime behavior when running or driving an application. |
| 48 | +Historically, the Flutter Android embedding accepted these flags at runtime |
| 49 | +through [`Intent`][] extras. |
| 50 | +Both developers (using `adb shell am start` or native code) |
| 51 | +and the Flutter CLI (via `adb`) relied on `Intent` extras |
| 52 | +to pass flags to running apps. |
| 53 | + |
| 54 | +However, runtime `Intent` extras on Android can be spoofed or intercepted |
| 55 | +by other applications on a user's device. |
| 56 | +Allowing arbitrary engine flags in production builds presents |
| 57 | +security vulnerabilities, such as dynamic parameter injection attacks |
| 58 | +(for example, spoofing `--aot-shared-library-name`). |
| 59 | + |
| 60 | +To harden production applications, Flutter establishes |
| 61 | +a build-mode-specific trust boundary: |
| 62 | + |
| 63 | +- **Release mode:** |
| 64 | + Production application boundaries are treated as immutable. |
| 65 | + The Android embedding ignores engine configuration flags passed |
| 66 | + through `Intent` extras and logs a warning. |
| 67 | + Release builds read configuration strictly from a signed |
| 68 | + `AndroidManifest.xml` or from programmatic setup before engine startup. |
| 69 | +- **Debug and profile modes:** |
| 70 | + Dynamic `Intent` flag support is intentionally maintained |
| 71 | + to preserve developer velocity, dynamic benchmarking, |
| 72 | + and local testing workflows. |
| 73 | + |
| 74 | +For release builds of standard Gradle-based projects, |
| 75 | +the Flutter CLI automatically injects command-line flags |
| 76 | +into `AndroidManifest.xml` during compilation. |
| 77 | +When you use a prebuilt release binary with `--use-application-binary`, |
| 78 | +the CLI cannot modify the compiled manifest, |
| 79 | +and the binary ignores runtime `Intent` flags. |
| 80 | +To prevent tests or scripts from running with unnoticed configuration failures, |
| 81 | +the CLI reports a fatal error. |
| 82 | + |
| 83 | +Similarly, any automated scripts, test runners, or host applications |
| 84 | +that directly construct Android `Intent`s with configuration extras |
| 85 | +will find those flags ignored when launching a release binary. |
| 86 | + |
| 87 | +[`Intent`]: https://developer.android.com/reference/android/content/Intent |
| 88 | + |
| 89 | +## Description of change |
| 90 | + |
| 91 | +### Android embedding behavior |
| 92 | + |
| 93 | +The Flutter Android embedding enforces the following behavior |
| 94 | +when receiving engine configuration flags through Android `Intent` extras |
| 95 | +(such as from `adb shell am start` or native `Intent.putExtra()` calls): |
| 96 | + |
| 97 | +| Build mode | Intent extras behavior | Notes | |
| 98 | +| :--- | :--- | :--- | |
| 99 | +| **Debug / Profile** | Accepted and parsed | Flags apply dynamically at launch time. | |
| 100 | +| **Release** | **Ignored** | The embedding logs a warning and discards all engine configuration flags received from `Intent` extras. | |
| 101 | + |
| 102 | +In addition, the internal interface method |
| 103 | +`FlutterActivityAndFragmentDelegate.Host.getFlutterShellArgs` is deprecated |
| 104 | +in favor of `getFlutterEngineFlags()`. |
| 105 | + |
| 106 | +### Flutter CLI behavior |
| 107 | + |
| 108 | +The Flutter CLI enforces the following behavior |
| 109 | +when running Flutter apps on Android: |
| 110 | + |
| 111 | +| Build mode | Using `--use-application-binary` | CLI behavior | Notes | |
| 112 | +| :--- | :--- | :--- | :--- | |
| 113 | +| **Debug / Profile** | Yes | Passes flags to binary through `adb` | No rebuild required; flags apply at runtime. | |
| 114 | +| **Debug / Profile** | No | Builds and passes flags through `adb` | Standard development workflow. | |
| 115 | +| **Release** | Yes | **Fatal error** if configuration flags are provided | Prebuilt release binaries cannot be dynamically configured. | |
| 116 | +| **Release** | No | Injects flags into `AndroidManifest.xml` during compilation | Standard release build workflow. | |
| 117 | + |
| 118 | +## Migration guide |
| 119 | + |
| 120 | +:::note |
| 121 | +You are **not affected** and do not need to take action if: |
| 122 | +- You build and run standard release apps with the Flutter CLI |
| 123 | + (`flutter run --release`, `flutter build apk --release`, |
| 124 | + or `flutter build appbundle`). |
| 125 | +- You run tests and benchmarks in **debug** or **profile** mode. |
| 126 | +- You use `--use-application-binary` without passing engine configuration flags. |
| 127 | +::: |
| 128 | + |
| 129 | +You are **affected** if: |
| 130 | +- You launch release builds using Android `Intent` extras directly |
| 131 | + (such as with `adb shell am start` or custom launch intents in native code) |
| 132 | + to pass engine configuration flags or route arguments. |
| 133 | +- Your CI/CD pipelines, automated scripts, or test runners pass flags |
| 134 | + to prebuilt release binaries using `--use-application-binary`. |
| 135 | +- You build Flutter Android applications using non-Gradle or hermetic build |
| 136 | + systems (such as Bazel) and configure release binaries with launch-time flags. |
| 137 | + |
| 138 | +If your workflows are affected, use one of the following migration paths: |
| 139 | + |
| 140 | +### Switch testing and benchmarking to profile mode |
| 141 | + |
| 142 | +If your automated test pipelines, scripts, or benchmarks pass flags |
| 143 | +to release binaries dynamically at launch time: |
| 144 | + |
| 145 | +1. Switch your test target to **profile mode** (`--profile`). |
| 146 | + Profile mode mirrors release performance characteristics |
| 147 | + while retaining support for dynamic runtime flag configuration |
| 148 | + through `Intent` extras and the Flutter CLI without recompilation. |
| 149 | + |
| 150 | +### Configure direct Intent launches and hermetic build systems |
| 151 | + |
| 152 | +If you launch release builds using Android `Intent`s directly |
| 153 | +(for example, via `adb shell am start` or custom test runners), |
| 154 | +or if you build Flutter Android apps with non-Gradle or hermetic build systems |
| 155 | +(such as Bazel) that separate compilation from execution: |
| 156 | + |
| 157 | +1. **Do not rely on `Intent` extras for release builds.** |
| 158 | + Any engine flags passed in `Intent` extras to a release binary |
| 159 | + are silently ignored by the embedding at runtime. |
| 160 | +1. **Use profile mode for dynamic testing:** |
| 161 | + For integration tests or performance benchmarks that require |
| 162 | + varying flags dynamically at launch time, compile and run |
| 163 | + a **profile** build instead of a release build. |
| 164 | +1. **Declare flags statically in `AndroidManifest.xml`:** |
| 165 | + If you must run a release binary with specific engine flags, |
| 166 | + statically declare those flags in your `AndroidManifest.xml` |
| 167 | + before compiling the release binary. |
| 168 | + For details, refer to |
| 169 | + [Declare engine flags in `AndroidManifest.xml`](#declare-engine-flags-in-manifest). |
| 170 | + |
| 171 | +### Build release binaries with flags directly using the Flutter CLI |
| 172 | + |
| 173 | +If you must run tests against a release binary with the Flutter CLI: |
| 174 | + |
| 175 | +1. Run `flutter build` or `flutter run` with your configuration flags |
| 176 | + without `--use-application-binary`. |
| 177 | + The CLI automatically embeds the flags into the compiled manifest. |
| 178 | +1. Alternatively, compile separate release binaries |
| 179 | + for each required configuration. |
| 180 | + |
| 181 | +### Configure engine flags programmatically in native host code |
| 182 | + |
| 183 | +If you launch Flutter from native Android code |
| 184 | +(such as in an add-to-app integration or custom native activity) |
| 185 | +and previously passed engine flags through `Intent` extras: |
| 186 | + |
| 187 | +1. **Override `getFlutterEngineFlags()`:** |
| 188 | + If you subclass `FlutterActivity` or `FlutterFragment`, override |
| 189 | + `getFlutterEngineFlags()` instead of using `Intent` extras |
| 190 | + or the deprecated `getFlutterShellArgs()`: |
| 191 | + |
| 192 | + ```kotlin |
| 193 | + class MyFlutterActivity : FlutterActivity() { |
| 194 | + override fun getFlutterEngineFlags(): List<String> { |
| 195 | + val flags = super.getFlutterEngineFlags().toMutableList() |
| 196 | + flags.add("--trace-startup") |
| 197 | + return flags |
| 198 | + } |
| 199 | + } |
| 200 | + ``` |
| 201 | + |
| 202 | +1. **Use `FlutterFragment.NewEngineFragmentBuilder`:** |
| 203 | + If you host a `FlutterFragment`, pass flags using the builder: |
| 204 | + |
| 205 | + ```kotlin |
| 206 | + val fragment = FlutterFragment.withNewEngine() |
| 207 | + .flutterEngineFlags(listOf("--trace-startup")) |
| 208 | + .build<FlutterFragment>() |
| 209 | + ``` |
| 210 | + |
| 211 | +1. **Pre-initialize and cache a `FlutterEngine`:** |
| 212 | + If you manage the engine lifecycle directly, supply engine arguments |
| 213 | + to the `FlutterEngine` constructor: |
| 214 | + |
| 215 | + ```kotlin |
| 216 | + val args = arrayOf("--trace-startup", "--enable-impeller=true") |
| 217 | + val flutterEngine = FlutterEngine(context, args) |
| 218 | + FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine) |
| 219 | + ``` |
| 220 | + |
| 221 | +### Declare engine flags in manifest |
| 222 | + |
| 223 | +To configure engine flags statically in release builds, |
| 224 | +add `<meta-data>` elements under the `<application>` tag in |
| 225 | +your `android/app/src/main/AndroidManifest.xml` file: |
| 226 | + |
| 227 | +```xml title="AndroidManifest.xml" highlightLines=6-12 |
| 228 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
| 229 | + <application |
| 230 | + android:label="my_app" |
| 231 | + android:name="${applicationName}" |
| 232 | + android:icon="@mipmap/ic_launcher"> |
| 233 | + <!-- Declare engine configuration flags statically --> |
| 234 | + <meta-data |
| 235 | + android:name="io.flutter.embedding.android.DartFlags" |
| 236 | + android:value="--some-dart-flag" /> |
| 237 | + <meta-data |
| 238 | + android:name="io.flutter.embedding.android.EnableDartProfiling" |
| 239 | + android:value="false" /> |
| 240 | + <activity |
| 241 | + ... |
| 242 | + </activity> |
| 243 | + </application> |
| 244 | +</manifest> |
| 245 | +``` |
| 246 | + |
| 247 | +## Timeline |
| 248 | + |
| 249 | +Landed in version: TBD<br> |
| 250 | +In stable release: TBD |
| 251 | + |
| 252 | +## References |
| 253 | + |
| 254 | +Relevant issues: |
| 255 | + |
| 256 | +* [Issue 180686][] |
| 257 | +* [Issue 190461][] |
| 258 | + |
| 259 | +Relevant pull requests: |
| 260 | + |
| 261 | +* [PR 190870][] |
| 262 | +* [PR 191328][] |
| 263 | +* [PR 191924][] |
| 264 | + |
| 265 | +[Issue 180686]: https://github.com/flutter/flutter/issues/180686 |
| 266 | +[Issue 190461]: https://github.com/flutter/flutter/issues/190461 |
| 267 | +[PR 190870]: https://github.com/flutter/flutter/pull/190870 |
| 268 | +[PR 191328]: https://github.com/flutter/flutter/pull/191328 |
| 269 | +[PR 191924]: https://github.com/flutter/flutter/pull/191924 |
0 commit comments