[wiki migration] Android team pages (#148585)

This puts the wiki pages owned by the Android team into the docs/platforms/android directory as planned in [flutter.dev/go/migrate-flutter-wiki-spreadsheet](https://docs.google.com/spreadsheets/d/1x65189ZBdNiLRygpUYoU08pwvXD4M-Z157c6pm8deGI/edit?usp=sharing) 

It also adds the android team labels to the label bot for future PRs.

Changes to the content were only updating cross links, or link to refer to the main branch rather than master.
Remaining links to the wiki will be updated once all other pages have finished moving, they still work in the meantime.

Part of https://github.com/flutter/flutter/issues/145009
This commit is contained in:
Kate Lovett
2024-05-21 14:20:51 -05:00
committed by GitHub
parent 654a5b3773
commit 1fa6f56b48
10 changed files with 23 additions and 8 deletions

View File

@@ -0,0 +1,15 @@
# Dex Overview
Android apps are compiled into .dex (dalvik executable) files that run on the Dalvik virtual machine. Before Android API 21, apps compiled into a single .dex file, which has the limitation of only supporting 64k method names. This means that larger apps frequently exceed this limit, thus requiring enabling "Multidex" support (see [https://android-doc.github.io/tools/building/multidex.html](https://android-doc.github.io/tools/building/multidex.html) for more details).
In Flutter applications, it is easy to exceed the single dex limit primarily through using plugins. Thus, even small apps may occasionally exceed the limit by importing a full suite of standard Google plugins. Even though unused methods are stripped from releases, the dex limit can still prevent apps from building.
# Multidex support
Flutter supports automatic multidex error detection and support for apps targeting API 20 and below. When apps fail to build due to the multidex error, Flutter will automatically prompt the user if multidex support should be enabled. Answering yes will add support and subsequent builds in the future will build with multidex support enabled.
You may pass the `--no-multidex` flag to temporarily disable multidex support during a build.
# API 21+
Apps that target API 21+ devices already have multidex natively supported. However, we do not recommend targeting API 21+ purely to resolve the multidex issue as this may inadvertently exclude potential users running older devices.

View File

@@ -0,0 +1,89 @@
# Background
Flutter UIs function conceptually similar to a WebView on Android. The
Flutter Framework takes a widget tree described by the app developer and
translates that into an internal widget hierarchy, and then from that decides
what pixels to actually render. Web developers can think of this as analogous
to how a browser takes HTML/CSS, creates a Document Object Model ("DOM"), and
then uses that DOM to actually render pixels each frame. Also like a WebView,
Flutter UIs aren't translated to a set of Android View widgets and composited
by the Android OS itself. Flutter controls a texture (generally through a
[SurfaceView](https://developer.android.com/reference/android/view/SurfaceView))
and uses Skia to render directly to the texture without ever using any kind of
Android View hierarchy to represent its internal Flutter widget hierarchy or
UI.
This means that like a WebView, by default a Flutter UI could never contain
an Android View within its widget hierarchy. Since the Flutter UI is just
being drawn to a texture and its widget tree is entirely internal, there's no
place for the View to "fit" within Flutter's internal model or render
interleaved within Flutter widgets. That's a problem for developers that would
like to include complex pre-existing Android views in their Flutter apps, like
WebViews themselves, or maps.
To solve this problem Flutter created an [AndroidView
widget](https://api.flutter.dev/flutter/widgets/AndroidView-class.html) that
Flutter developers can use to visually embed actual Android View components
within their Flutter UI.
# Approaches
There are currently three different implementations of Android platform views:
- [Virtual Display](Virtual_Display.md) (VD)
- [Hybrid Composition](https://github.com/flutter/flutter/wiki/Hybrid-Composition) (HC)
- [Texture Layer Hybrid Composition](Texture-Layer-Hybrid-Composition.md) (TLHC)
Each has a different set of limitations and tradeoffs, as discussed below. The pages linked above give details about each implementation.
## Virtual Display
This mode works by rendering the platform view into [a `VirtualDisplay`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay), whose contents are connected to [a Flutter `Texture`](https://api.flutter.dev/flutter/widgets/Texture-class.html).
Because this renders to a `Texture`, it integrates well into the Flutter drawing system. However, the use of `VirtualDisplay` introduces a number of compatibility issues, including with text input, accessibility, and secondary views (see [the Virtual Display page](Virtual-Display.md) for details).
This display mode requires SDK 20 or later.
## Hybrid Composition
This mode directly displays the native Android [`View`](https://developer.android.com/reference/android/view/View) in the view hierarchy. This requires several significant changes to the way Flutter renders:
- The Flutter widget tree is divided into two different Android `View`s, one below the platform view and one above it.
- To avoid tearing or other visual artifacts, Flutter's composition must be done on the platform thread rather than a dedicated thread.
Because the native view is being displayed directly, just as it would be in non-Flutter application, this mode is the least likely to have compatibility issues with the platform view. However, the rendering changes can significantly impact Flutter's performance. In addition, on versions of Android before SDK 29 (Android 10) Flutter frames have a GPU->CPU->GPU round trip that further impacts performance.
This display mode requires SDK 19 or later, and uses the FlutterImageView based renderer.
## Texture Layer Hybrid Composition
This mode, introduced in Flutter 3.0, attempted to address the limitations of the modes above, and was originally intended to replace both. Like Hybrid Composition, the view is actually placed on the screen at the correct location. However, as with Virtual Display the drawing uses a `Texture`, which in this case is populated by redirecting [`draw`](https://developer.android.com/reference/android/view/View#draw(android.graphics.Canvas)).
In most cases this combines the best aspects of Virtual Display and Hybrid Composition, and should be preferred when possible. One notable exception however is that if the platform view is, or contains, a [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) this mode will not work correctly, and the `SurfaceView` will be drawn at the wrong location and/or z-index.
This display mode requires SDK 23 or later.
# Selecting a mode
Usually Android platform views are created with one of the `init*` methods:
- `initAndroidView` creates a [`TextureAndroidViewController`](https://api.flutter.dev/flutter/services/TextureAndroidViewController-class.html), which will use TLHC mode if possible, and fall back to VD if not. The fallback is triggered if:
- the current SDK version is <23, or
- the platform view hierarchy contains a `SurfaceView` (or subclass) **at creation time**.
There is [a known issue](https://github.com/flutter/flutter/issues/109690) where if the view hierarchy does not contain a `SurfaceView` at creation time, but one is added later, rendering will not work correctly. Until that issue is resolved, plugin authors can work around this by either:
- including a 0x0 `SurfaceView` in the view hierarchy at creation time, to trigger fallback to VD, or
- switching to `initExpensiveAndroidView` to require HC.
(The behavior described above is for Flutter 3.3+. Flutter 3.0 did not include the fallback to VD, and in Flutter <3.0 this always used VD.)
- `initSurfaceAndroidView` creates a [`SurfaceAndroidViewController`](https://api.flutter.dev/flutter/services/SurfaceAndroidViewController-class.html), which will use TLHC if possible, and fall back to HC if not. As above, the fallback is triggered if:
- the current SDK version is <23, or
- the platform view hierarchy contains a `SurfaceView` (or subclass) **at creation time**.
There is [a known issue](https://github.com/flutter/flutter/issues/109690) where if the view hierarchy does not contain a `SurfaceView` at creation time, but one is added later, rendering will not work correctly. Until that issue is resolved, plugin authors can work around this by either:
- including a 0x0 `SurfaceView` in the view hierarchy at creation time, to trigger fallback to HC, or
- switching to `initExpensiveAndroidView` to require HC directly.
(The behavior described above is for Flutter 3.7+. In Flutter 3.0 and 3.0 this behaved identically to `initAndroidView`, and in Flutter <3.0 this always used HC.)
- `initExpensiveAndroidView` creates an [`ExpensiveAndroidViewController`](https://api.flutter.dev/flutter/services/ExpensiveAndroidViewController-class.html), which will always use HC.
(This API was introduced in Flutter 3.0.)
In general, plugins will likely get the best performance by using `initAndroidView` or `initSurfaceAndroidView` (depending on which fallback mode is desired for older versions of Android), and should only use `initExpensiveAndroidView` if the plugin is found to be incompatible with TLHC (such as dynamically adding a `SurfaceView`).

View File

@@ -0,0 +1,151 @@
Flutter builds APKs through a long set of steps that ultimately defer to Gradle.
The logic lives in many places: the Flutter framework, the Flutter app template,
and a mix of both Dart and Gradle scripts. This doc is a high level description
of the overall flow, which can be hard to follow given how many different
places at once it lives and how asynchronous Gradle builds are.
- The Flutter tool fetches any plugin dependencies of the Flutter app from pub
and downloads it to the pub cache. This step follows normal `pub get` behavior
and version solving.
- The Flutter tool writes project metadata to some local temporary files. This
includes things like the location of the local Flutter SDK, and paths to all
the newly downloaded plugins from pub on disk.
- The Flutter tool invokes Gradle to build the APK.
- Gradle reads the app's `build.gradle`, which applies the Flutter framework's
`flutter.gradle` plugin.
- `flutter.gradle` adds the Flutter engine and all of its support library
dependencies as a `jar` dependency to the Flutter app.
- `flutter.gradle` adds all of the Flutter plugins of an app as "subproject"
Gradle dependencies (usually).
- Gradle compiles the Flutter app and all of its subprojects (Flutter plugins)
into a root APK.
Those steps are deliberately listed as bullet points and not a numbered list.
Gradle invokes a lot of these steps at seemingly random and parallel times.
Time is relative.
## Plugins are compiled as subprojects (usually)
Flutter plugins are typically included in Flutter apps as source code and
compiled by Gradle, the same as the rest of the code in the Flutter app.
This is done by using the Gradle concept of
["subprojects"](https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:subproject_configuration),
a way of structuring build tasks so that they're marked as being dependent on
each other. All Flutter apps are built with one subproject for the actual
Android code of the app and other subprojects for the Android code of any
plugins.
For example, let's assume that there's a Flutter app that uses two plugins:
`foo` and `bar`. Let's also assume `foo` depends on `baz`, so `baz` is included
as part of the build at runtime. Running `./gradlew projects` in the app's
`android` directory (after first running `flutter build apk` to give the Flutter
tool's Dart code an opportunity to set up the build) would reveal the following
project structure:
```console
$ ./gradlew projects
> Task :projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'android'
+--- Project ':app'
+--- Project ':bar'
+--- Project ':baz''
\--- Project ':foo'
```
### The how
- [The Flutter tool's Dart
code](https://github.com/flutter/flutter/blob/82410a5ae7159b9f39e85b713decf532840ac70c/packages/flutter_tools/lib/src/build_system/targets/assets.dart#L115-L119)
writes a `.flutter-plugins` file containing a list of all the plugins used by
the app and their location on disk.
- [`flutter.gradle`](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/packages/flutter_tools/gradle/flutter.gradle#L243-L264)
reads from this file and actually adds the plugins as Gradle subproject
dependencies of the app.
- This also depends on the Flutter app having a block of code in its template
that actually references and builds these subprojects once they're defined,
see ["hello world" as an example of the app's Gradle
script](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/examples/hello_world/android/settings.gradle#L15-L19).
### Special Considerations
Including plugin dependencies as source code and then compiling them is not
something Gradle generally expects apps to be doing. In the past we've hit
several issues caused by Gradle tooling around dependency management being built
for typical Android patterns (including dependencies as `.aars` or `.jars`) and
breaking down for the Flutter use case.
For example:
- Plugins have their own `build.gradle` scripts that may conflict with the app
and other plugins in unexpected ways. How Gradle handles these conflicts is
situational and sometimes undefined behavior.
- If a plugin has a problem in its Android code, the entire user's app will fail
to compile with a stack trace that may or may not really point to the plugin's
code on disk.
- If the plugin's code conflicts with the app's Android source code (for
example, the plugin has a conflicting transitive dependancy), Gradle will
generally fail to compile with difficult to translate stack traces.
- These subprojects are added in an `afterEvaluate` hook in `flutter.gradle`.
Generally adding any logic in `afterEvaluate` blocks is discouraged because it
runs after so much of Gradle's standard pipeline.
## Sometimes plugins are compiled as AARs
Because of the problems with compiling the plugins as subprojects, the
Flutter team explored switching the plugins to instead be compiled first as
standalone AARs and then included as binary dependencies as is more typical for
Android dependencies and expected by Gradle. This effort ended up being blocked,
but is still occasionally automatically used as a fallback attempt when one of the typical
problems with building as subprojects is detected as failing a build today.
The basic AAR flow goes like this:
- The Flutter tool fetches any plugin dependencies of the Flutter app from pub
and downloads it to the pub cache. This step follows normal `pub get` behavior
and version solving.
- The Flutter tool writes project metadata to some local temporary files. This
includes things like the location of the local Flutter SDK, and paths to all
the newly downloaded plugins from pub on disk.
- The Flutter tool invokes Gradle to build the APK.
- Gradle reads the app's `build.gradle`, which applies the Flutter framework's
`flutter.gradle` plugin.
- `flutter.gradle` adds the Flutter engine and all of its support library
dependencies as a `jar` dependency to the Flutter app.
- `flutter.gradle` compiles all of the plugins individually into `aars`, and
adds all of the Flutter plugins as `aar` dependencies to the Flutter app.
- Gradle compiles the Flutter app and all of its binary dependencies into a root
APK.
### Why this isn't the standard
There were a couple blocking issues that kept us from following this flow in all
cases:
- The implementation of this depends on `TaskInternal.execute()`, a Gradle API
that's been removed with no replacement in Gradle 5.
- This style of build is extremely slow compared to the previous build flow.
- The plugins currently in the Flutter ecosystem were written under the previous
build system and in many cases depend on the transitive dependency bleed to
work. For example, many plugins built this way will fail to compile
individually because they're missing an explicit dependency on
`androidx.annotation`. There are also other plugins that deliberately expose
their own transitive `jar` dependencies in a way where they're automatically
included in the app at build time by the subproject system, but apps including them as
AARs will fail with "Class not found errors" unless the transitive plugin dependencies
from the plugin are manually added into the apps' Gradle dependencies as well.
### When this is still used
We still fall back on this as a silent retry if we detect that the failure is
caused by one of the likely error cases from the subproject structure:
specifically, if we detect that the build has failed because of a likely
AndroidX incompatibility issue. Jetifier is one of the Gradle tools that doesn't
function normally with the subproject structure, so building the plugins as AARs
can sometimes fix errors in that class.

View File

@@ -0,0 +1,42 @@
# Why does my build fail?
Flutter utilizes native build systems on Android and iOS.
As these systems evolve and introduce additional features on their own pace, developers sometimes need to re-adjust the configuration to prevent build failures.
This page collects such errors and the standard procedure to work around them.
## Android
Android builds are handled by the Gradle tool, which is configured using .gradle files.
Android Apps themselves are packaged, compiled & optimized by a variety of tools - all being orchestrated by the Android Gradle Plugin (AGP).
Usually, Android Studio and AGP are aligned in their versions. Android Studio also contains a few assistants to smoothen the upgrade process for existing projects.
### Quick Fix (suitable for most build errors)
- Open Android Studio
- Use the File Menu, then "Check for Updates" & retrieve the latest version if necessary
- After the upgrade, use the File Menu and open the "android/build.gradle" or "example/android/build.gradle" file directly. Android Studio will then import the Android portion of the project and will offer you to upgrade the Gradle plugin and the Gradle wrapper
- If you develop a plugin, you need to manually check `android/build.gradle` and `example/android/build.gradle` afterwards and ensure they are referring to the same AGP version
### Minimum supported Gradle version is 5.4.1. Current version is 4.10.2.
In this example, the Gradle plugin upgrade was stopped mid-way and the build can not continue.
To fix it, open `android/gradle/wrapper/gradle-wrapper.properties` (`example/android/gradle/wrapper/gradle-wrapper.properties` if you develop a plugin) and replace the version in there with the required version.
### The Android Gradle plugin only supports Kotlin Gradle Plugin 1.3.10 or higher
With the steps above, only AGP and the wrapper got upgraded. The Kotlin compiler infrastructure is a secondary dependency which is also dependent on certain AGP/Gradle versions.
To resolve it:
- Scan your build.gradle files for a placeholder like `ext.kotlin_version = '1.2.71'` and replace it with the latest Kotlin version (or the version named in the error message).
- Android Studio can recommend the latest version, if you open the build.gradle file in it
- New Kotlin versions introduce language & syntax optimizations; it is therefore recommended to use Android Studio again and open the native part of the App (see above) and let it run a full compilation, including a clean operation. Android Studio will report build failures and suggestions to optimize your code.
### Gradle task assembleDebug failed with exit code 1
If `assembleDebug` or `assembleRelease` failed but no error message can be seen, you are likely on a older Flutter version (`<= 1.9.1.hotfix4`) which may suppress the root cause of the error.
This problem can be solved in two ways:
- Please try a more recent channel, like `beta` or `dev` and restart the compilation. It may still fail, but the error will now be printed
- Open the `$FLUTTER_ROOT/packages/flutter_tools/gradle/flutter.gradle` file in your local installation and comment the line which reads `gradle.useLogger(new FlutterEventLogger())`, then retry your build

View File

@@ -0,0 +1,59 @@
## Testing on Emulators in the Devicelab
While the Devicelab is typically for testing on Devices we have added support so that developers can now test Android changes via the LUCI recipes on an Android Emulator.
You can specify a new test via the .ci.yaml file in the repository. This allows the infra framework to run the test automatically with minimal work from the developer.
### Adding a Brand New Target
Adding a new Devicelab Android Emulator test for Android feature changes requires the following steps:
Starting with the finished target yaml definition:
```
- name: Linux_android android_defines_test
recipe: devicelab/devicelab_drone
presubmit: true
timeout: 60
dimensions: {
kvm: “1”,
cores: “8”,
Machine_name: “n1-standard-8”
}
properties:
device_type: “none”
task_name: android_defines_test
use_emulator: “true”
dependencies: >-
[
{"dependency": "android_virtual_device", "version": "31"}
]
tags: >
["devicelab", “linux”]
timeout: 300
```
1. The `name` of the target consists of the platform which can be either `Linux` or `Linux_Android`. It is better to pick `Linux_Android` since it will provide you more of the needed dependencies.
2. The `recipe` is always `devicelab/devicelab_drone`. This is the recipe that will launch the emulator and drive the test.
3. `presubmit` can be `true` or `false`. To run the test in any PR that is opened you should set this to `true`. This is the best approach to catch bugs before they are mirrored to google3.
4. `timeout` is an integer value.
5. `dimensions` is needed to tell the LUCI framework that we need to run this test on a machine that supports nested virtualization and should be set as shown.
6. `properties`:
a. `device_type` must be set to `none` so that we use a machine without an attached android phone. This will cause problems if not set.
b. `task_name` is the name to apply to the task.
c. `use_emulator` is the flag to tell the test recipe that we need to create an Emulator for this test.
d. `dependencies` can be used to override the `android_virtual_device` api version.
e. `tags` should be set to `devicelab` and `linux`
f. `timeout` the timeout here is for the time to give the test to run before killing it.
### Updating an Existing Target
If you want to update an existing target you will only need to add the following changes:
1. add a `dimensions` field as described above.
2. Set the `device_type: "none"`.
3. Add `use_emulator: "true"` flag in properties. Note this is not a boolean but must be a string.
4. Add a dependency for the android emulator version to the properties. If there are no dependencies add it as shown above.
5. Remove any tags that specify android. These are for benchmark tests and having them in will cause some of the device checks to fail.

View File

@@ -0,0 +1,20 @@
_See also: [Hybrid Composition|Hybrid Composition#Android](https://github.com/flutter/flutter/wiki/Hybrid-Composition)_
# Background
Texture Layer Hybrid Composition (TLHC) is one of several modes for displaying platform views on Android. See [Android Platform Views](Android-Platform-Views.md) for an overview of modes.
It was introduced in Flutter 3.0 to combine the best aspects of Virtual Display and Hybrid Composition while addressing their most significant issues. While it was originally intended to replace both, it turned out to have some limitations that prevented serving as a complete replacement, so is now a third option.
# Approach
TLHC uses a [custom `FrameLayout`](https://github.com/flutter/engine/blob/7025645c52bfaeb1cc67be5ca842b65772c89c8e/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L35-L46), which is placed in the native view hierarchy as normal, but [redirects drawing](https://github.com/flutter/engine/blob/7025645c52bfaeb1cc67be5ca842b65772c89c8e/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java#L299-L309) to a canvas that backs a Flutter [`Texture`](https://api.flutter.dev/flutter/widgets/Texture-class.html) in order to compose with the rest of the Flutter UI as normal (without the layering and threading complexities of Hybrid Composition).
# Limitations
- Because of the APIs involved, this requires SDK level 23 or later, so does not support quite as far back as Virtual Display (SDK 20+) or Hybrid Composition (SDK 19+).
- [Android `SurfaceView`s](https://developer.android.com/reference/android/view/SurfaceView) bypass the normal drawing mechanism of Android views, so are not redirected as expected. Instead, they draw as if the redirect did not happen, which generally causes them to draw over all Flutter content.
- To mitigate this, platform views attempt to automatically fall back to another mode when a `SurfaceView` is present. However, this currently [only works if the `SurfaceView` is present when the platform view is created](https://github.com/flutter/flutter/issues/109690).
- [Android `TextureView`s](https://developer.android.com/reference/android/view/TextureView) do not always show updates correctly. This is [still under investigation](https://github.com/flutter/flutter/issues/103686), and for now affected plugins are best off using another mode.
To see all known issues specific to this mode, search for the [`tlhc-only` label](https://github.com/flutter/flutter/labels/tlhc-only).

View File

@@ -0,0 +1,42 @@
When new Android versions become available, the following steps should be taken in order to fully support the new API version in the Flutter Engine. These steps only change the Engine to build against and target the new API and does not guarantee everything works with the changes in the new android API.
## Buildroot:
build/config/android/config.gni: Edit default_android_sdk_version and default_android_sdk_build_tools_version
## SDK and other dependencies:
Flutter now includes a script to download, package, and upload the Android SDK to CIPD. These CIPD packages are then used as dependencies by the Flutter engine and recipes so that there is a stable archived version of the Android SDK to depend on. The script is located in the Flutter engine repo under `tools/android-sdk/create_cipd_packages.sh`.
Edit `tools/android-sdk/packages.txt` to refer to the updated versions you want. The format for each line in packages.txt is `<package_name>:<subdirectory_to_upload>`. Typically, each <package_name> should be updated to the latest available version which can be found with the `sdkmanager --list --include_obsolete`. `sdkmanager` can be found in your `commandline-tools` package of the android sdk.
The script must be run on a Linux or Mac host. Run:
`$ ./tools/android-sdk/create_cipd_packages.sh <new_version_tag> <path_to_your_local_android_sdk>`
This script will download and re-upload the entire SDK, so it may take a long time to complete. `cmdline-tools` should be installed in your local sdk as the script uses `sdkmanager`. Once the CIPD packages are finished uploading, you can update the sdk version tag used in `.ci.yaml`, `DEPS`, and elsewhere.
It is no longer recommended to upload CIPD android sdk packages manually, but if it must be done, run the following commands to zip and upload each package to CIPD:
`$ cipd create -in <your-android-dir>/Android/sdk/<some_package> -name flutter/android/sdk/<some_package> -tag version:<new-version-tag>`
Typically, `<your-android-dir>` is in your home directory under `~/Library/Android`. The `<new-version-tag>` is what you will use to specify the new package you uploaded in the Flutter engine DEPS file.
## Engine:
* DEPS: Roll buildroot hash
* DEPS: Change the version parameter under `flutter/android/sdk/all/${{platform}}` to the newly uploaded CIPD version tag. Eg, `'version': 'version:30r2'`
* Change any references to the old SDK version to the latest in build.gradle across the repo.
* In `tools/javadoc/gen_javadoc.py`, bump the reference to `android-XX` in `classpath` to the latest version.
* In `tools/cipd/android_embedding_bundle/build.gradle`, bump `compileSdkVersion XX` to the latest version.
* In `shell/platform/android/test_runner/build.gradle`, bump `compileSdkVersion XX` to the latest version.
* In all android projects in `testing`, upgrade the `compileSdkVersion` and `targetSdkVersion` in `android/app/build.gradle`.
## Flutter LUCI Recipes:
Scenario tests recipes/engine/scenarios.py: Update the CIPD hash to the latest version that contains the configuration for the Android Virtual Device (AVD) desired `generic_android<API#>.textpb` and change the script to use the new textpb config and change the API number in the logs. The CIPD package for the AVD launcher can be found at https://chrome-infra-packages.appspot.com/p/chromium/tools/android/avd/+/ and updating the packages uploaded there is tracked in https://bugs.chromium.org/p/chromium/issues/detail?id=1112429#c7
## Framework, examples and samples
* Templates in tooling: Change `targetSdkVersion` in various `build.gradle.tmpl` files to use the new API version
* Examples, samples, gallery, etc: Change `targetSdkVersion` in `android/app/build.gradle` for each project to the new API version.

View File

@@ -0,0 +1,263 @@
_If you `flutter create`d your project prior to version 1.12, this may apply to your project_
# Background
In order to better support the execution environments of adding Flutter to an existing project, the old Android platform-side wrappers hosting the Flutter runtime at [`io.flutter.app.FlutterActivity`](https://github.com/flutter/engine/blob/main/shell/platform/android/io/flutter/app/FlutterActivity.java) and their associated classes are now deprecated. New wrappers at [`io.flutter.embedding.android.FlutterActivity`](https://github.com/flutter/engine/blob/main/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java) and associated classes now replace them.
Those classes better support real world scenarios where the FlutterActivity isn't the first and only Android Activity in an application.
# Motivation
Your existing full-Flutter projects aren't immediately affected and will continue to work as before for the foreseeable future.
However, the new Android wrappers also introduce a new set of Android plugin development APIs. Plugins developed exclusively on the new plugins API will not work on older pre-1.12 Android projects. Building a pre-1.12 Android project that uses plugins created after 1.12 will yield a build-time error unless the plugin developer explicitly opted to create a second backward compatible implementation.
Add-to-app was not officially supported on the old Android APIs. If you followed the experimental instructions in the wiki for add-to-app prior to 1.12, you should follow the migration steps under the [add-to-app section](#add-to-app-migration) below.
# Full-Flutter app migration
_This guide assumes you haven't manually modified your Android host project for your Flutter project. If you did, consult the add-to-app migration guide below._
If you opt to migrate your standard `flutter create`d project, follow the following steps:
**1a.** If you don't have any of your own added code to `android/app/src/main/java/[your/package/name]/MainActivity.java` - remove the body of your `MainActivity.java` and change the `FlutterActivity` import. The new `FlutterActivity` no longer requires manually registering your plugins. It will now perform the registration automatically when the underlaying `FlutterEngine` is created.
```diff
// MainActivity.java
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
+import io.flutter.embedding.android.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
}
```
```diff
// MainActivity.kt
-import android.os.Bundle
-import io.flutter.app.FlutterActivity
+import io.flutter.embedding.android.FlutterActivity
-import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- GeneratedPluginRegistrant.registerWith(this)
- }
}
```
Since the body of the `MainActivity` is now empty, you can also optionally delete the `MainActivity.java/kt` file if you'd like. If you do, you need to change your `AndroidManifest.xml`'s reference to `.MainActivity` to `io.flutter.embedding.android.FlutterActivity`.
**1b.** If you had existing custom platform channel handling code in your `MainActivity.java`, below is an example of the change you can make to adopt the new embedding API:
```diff
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugin.common.MethodCall;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
+import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
-
- new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
- new MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, Result result) {
- // Your existing code
- }
- });
- }
+
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
+ new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
+ .setMethodCallHandler(
+ (call, result) -> {
+ // Your existing code
+ }
+ );
+ }
}
```
In other words, move the channel registration part of the code in your `onCreate` into the `configureFlutterEngine` override of the FlutterActivity subclass and use `flutterEngine.getDartExecutor().getBinaryMessenger()` as the binary messenger rather than `getFlutterView()`.
**2.** Open `android/app/src/main/AndroidManifest.xml`.
**3.** Replace the reference to `FlutterApplication` in the application tag with `${applicationName}`.
Previous configuration:
```xml
<application
android:name="io.flutter.app.FlutterApplication"
>
<!-- code omitted -->
</application>
```
New configuration:
```xml
<application
android:name="${applicationName}"
>
<!-- code omitted -->
</application>
```
**4.** Update splash screen behavior (if splash behavior is desired).
Remove all `<meta-data>` tags with key `android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"`.
Add a launch theme to `styles.xml` that configures the desired launch screen as a background `Drawable`:
```xml
<!-- You can name this style whatever you'd like -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/[your_launch_drawable_here]</item>
</style>
```
If you created your Flutter project with `flutter create` then you likely already have a `LaunchTheme` defined, along with a drawable called `launch_background`. You can re-use that configuration and adjust it as desired.
Add a normal theme that to `styles.xml` that should replace the launch screen when the Android process is fully initialized:
```xml
<!-- You can name this style whatever you'd like -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/[your_normal_background_drawable]</item>
</style>
```
The "normal theme" draws the background behind your Flutter experience. That background is typically seen for a brief moment just before the first Flutter frame renders. The "normal theme" also controls Android's status bar and navigation bar visual properties for the duration of your Flutter experience.
Configure `MainActivity` to start with your launch theme and then shift to your normal theme. Also specify that you want your launch screen to continue being displayed until Flutter renders its first frame:
```xml
<activity android:name=".MainActivity"
android:theme="@style/LaunchTheme"
// some code omitted
>
<!-- Specify that the launch screen should continue being displayed -->
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- some code omitted -->
</activity>
```
**5.** Add a new `<meta-data>` tag under `<application>`.
```xml
<meta-data
android:name="flutterEmbedding"
android:value="2" />
```
Once you make this declaration in your AndroidManifest and also use plugins, the new GeneratedPluginRegistrants created by the Flutter tools during build will now be able to also use plugins that uses the new Android embedding (which registers the plugins against a `FlutterEngine` instead of a `PluginRegistry.Registrar`).
Your app should still build as normal (such as via `flutter build apk`) but you're now using the new Android classes.
# Add-to-app migration
This section details how to take add-to-app scenarios that were built using Flutter's experimental embedding, and transition that code to Flutter's new stable embedding.
## Same steps as full-Flutter
Some instructions from the "Full-Flutter app migration" section above still apply. Follow the above steps for:
3. Remove the reference to `FlutterApplication` from the application tag.
5. Update splash screen behavior (if splash behavior is desired).
6. Add a new `<meta-data>` tag under `<application>`.
## Changes specific to add-to-app
If you invoke `FlutterMain.startInitialization(...)` or `FlutterMain.ensureInitializationComplete(...)` anywhere in your code, you should remove those calls. Flutter now initializes itself at the appropriate time.
### Migrating FlutterActivity Uses
Add-to-app scenarios often involve modifications to subclasses of `FlutterActivity`. For example, such a scenario might introduce new `MethodChannel`s, a custom `FlutterEngine` instance, custom splash screen behavior, or other behaviors that require overriding existing methods. Therefore, whereas full-Flutter apps can delete their `MainActivity` and replace it with a standard `FlutterActivity`, you will need to retain your subclass so that you can keep your behavior overrides.
If your add-to-app use-cases do not modify behavior within `FlutterActivity`, you should delete your subclasses and replace them with standard `FlutterActivity`s as described in the previous section.
If your add-to-app use-cases do require modifying behavior within `FlutterActivity`, you need to migrate your code from the old `io.flutter.app.FlutterActivity` to the new `io.flutter.embedding.android.FlutterActivity`.
From:
```java
package [your.package.name];
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
// ...some amount of custom code for your app is here.
}
```
To:
```java
package [your.package.name];
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
// You do not need to override onCreate() in order to invoke
// GeneratedPluginRegistrant. Flutter now does that on your behalf.
// ...retain whatever custom code you had from before (if any).
}
```
Some apps may have required pre-warming a Flutter experience. It is now recommended that all add-to-app use-cases pre-warm Flutter experiences to achieve optimal visual performance when initially rendering a Flutter UI. Please refer to the [Use a cached FlutterEngine](https://docs.flutter.dev/development/add-to-app/android/add-flutter-screen#step-3-optional-use-a-cached-flutterengine) to update your code for pre-warming Flutter.
Your `FlutterActivity` subclass is now up to date with the new, stable Android embedding.
### Migrating FlutterFragment uses
The experimental embedding provides a class called `io.flutter.facade.FlutterFragment`, along with other classes in the `io.flutter.facade` package. The entire `io.flutter.facade` package is deprecated and you should not use any classes in that package.
The experimental `io.flutter.facade.FlutterFragment` has been replaced by `io.flutter.embedding.android.FlutterFragment`, which was designed for a much broader set of use-cases than the original `FlutterFragment`.
If you are instantiating a `io.flutter.facade.FlutterFragment` via `Flutter.createFragment(...)`, you should delete any such calls and instantiate the new `io.flutter.embedding.android.FlutterFragment` via one of the following factory methods:
* `FlutterFragment.createDefault()`
* `FlutterFragment.withNewEngine()`
* `FlutterFragment.withCachedEngine(...)`
The use of these factory methods are discussed in depth in website guides at http://flutter.dev.
### Migrating FlutterView uses
The deprecated `io.flutter.facade.Flutter` class has a factory method called `createView(...)`. This method is deprecated, along with all other code in the `io.flutter.facade` package.
Flutter does not currently provide convenient APIs for utilizing Flutter at the `View` level, so the use of a `FlutterView` should be avoided, if possible. However, it is technically feasible to display a `FlutterView`, if required. Be sure to use `io.flutter.embedding.android.FlutterView` instead of `io.flutter.view.FlutterView`. You can instantiate the new `FlutterView` just like any other Android `View`. Then, follow instructions in the associated Javadocs to display Flutter via a `FlutterView`.

View File

@@ -0,0 +1,228 @@
# Background
Virtual Display is one of several modes for displaying platform views on Android. See [Android Platform Views](Android-Platform-Views.md) for an overview of modes.
# The approach
`AndroidView` widgets need to be composited within the Flutter UI and
interleaved between Flutter widgets. However the entire Flutter UI is
rendered to a single texture.
In order to solve this problem, Flutter inflates and renders `AndroidView`
widgets inside of
[VirtualDisplays](https://developer.android.com/reference/android/hardware/display/VirtualDisplay#summary)
instead of attempting to add it to the Android View hierarchy alongside
Flutter's texture directly. The `VirtualDisplay` renders its output to a raw
graphical buffer (accessed through
[`getSurface()`](https://developer.android.com/reference/android/hardware/display/VirtualDisplay#getSurface())),
and not to any actual real display(s) of the device. This allows Flutter to
graphically interleave the Android View inside of its own Flutter widget tree
by taking the texture from the `VirtualDisplay` output and treating it as a
texture associated with any other Flutter widget in its internal hierarchy.
Then the `VirtualDisplay's` `Surface` output is composited with the rest of
the Flutter widget hierarchy and rendered as part of Flutter's larger texture
output on Android.
# Associated problems and workarounds
While this approach unblocks the core problem with embedding Android views in
a Flutter UI visually, the `VirtualDisplay` choice causes a long tail of issues
that we've needed to work around.
The heart of the problem with this approach is that the Android View inside of
the `VirtualDisplay` is, as far as Android is concerned, inside of its own View
hierarchy in a completely invisible
[Display](https://developer.android.com/reference/android/view/Display.html) and
completely unconnected and unrelated to the View hierarchy that contains the
Flutter texture output.
Much of the internal functionality to Android depends on walking the View
hierarchy and querying information about the given View in its current hierarchy
and window in order to function. Since the information for the embedded view is
"wrong" here this internal functionality tends to break down. In addition to
that this logic can change based on Android version, so we've needed to case
some of our logic based on the runtime version of the OS.
Some of our issues with this approach and extended workarounds are described
below.
To see all known issues specific to this mode, search for the [`vd-only` label](https://github.com/flutter/flutter/labels/vd-only).
## Touch events
By default touch events would not work with PlatformViews. The Android View is
inflated inside of a `VirtualDisplay`. Whenever the user taps what they _see_ as
the Android View on their primary display, they're "really" tapping the part of
Flutter's texture output that we're rendering to look like the actual Android
View. The touch event created by their input is getting sent straight to
Flutter's View, not to the Android View they're actually trying to tap on.
### The workaround
- We detect whether or not the touch from the user should collide with the
`AndroidView` Flutter widget using [hit testing logic within the Flutter
Framework](https://github.com/flutter/flutter/blob/068fa84/packages/flutter/lib/src/rendering/platform_view.dart#L774).
- When the touch is a hit, we [dispatch a message to the Android engine
embedding containing the details of the touch
event](https://github.com/flutter/flutter/blob/068fa84/packages/flutter/lib/src/rendering/platform_view.dart#L595).
- [Within the Android
embedding](https://github.com/flutter/engine/blob/5b952f286fc070e99cf192775fa5c9dfe858b692/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L194),
we convert the coordinates of this event from the ones describing the touch
inside of the larger Flutter texture to ones that would match the real Android
View inside of its `VirtualDisplay`. We then create a new `MotionEvent`
describing the touch and forward it to the real Android View inside of its
`VirtualDisplay`.
### Limitations
- We're dispatching the new `MotionEvent` directly to the known Android View
embedded by the user. In cases where the View spawns other Views this dispatch
may be sent to the wrong place.
- We're synthesizing a new `MotionEvent` based on the information handed to us
by the Flutter framework. It's possible that there's data being lost and not
totally carried over to our new `MotionEvent`, or some other general problem
with synthesizing `MotionEvent`s.
## Accessibility
### Background
Explaining our problems with accessbility (or "a11y") requires a brief detour
into explaining a11y itself on Android, and how a11y typically works on Flutter.
Android itself builds up a tree of a11y nodes for each Android View rendered to
the screen. Each a11y node contains semantic information about what information
is represented by a UI element: for example, whether it's a button, and what the
text on button is. Typically the OS builds this information itself by directly
walking the View hierarchy. A11y services on the device then use this tree of
a11y nodes to deliver the semantic information in an accessible way to the user,
by example drawing highlights over interactable UI elements and reading the
semantic information described by the UI aloud to the user. Users can then use
a11y services to interact with a UI, for example by using it to activate the
button without needing to press once directly on where the button is graphically
rendering on screen.
Some UIs, like Flutter and WebViews, don't have a tree of Android Views to
describe their UIs. For these Android has a concept of a "virtual" a11y node
hierarchy. Instead of walking the View hierarchy, Android Views may implement
an
[AccessibilityNodeProvider](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider)
that returns a tree of a11y nodes to describe whatever is being rendered
within its own View subtree. The Flutter Android embedding uses this concept to
generate an a11y node tree that matches the
[`Semantics`](https://api.flutter.dev/flutter/widgets/Semantics-class.html)
tree created by the Flutter framework.
### The problem
Theoretically we should have been able to attach the a11y tree from the
embedded Android View to our `AccessibilityNodeProvider` in Flutter, but in
reality we've found that any attempts to do so directly fail. The reality of
the situation appears to be that a11y code in Android frequently relies on
the View hierarchy for state management and querying extra information, so
attempting to parent the childs hierarchy from the embedded view to our
virtual hierarchy fails to really correctly associate the underlying view
hierarchy in a functional way for a11y. In other words, reparenting the a11y
node trees only "fixes" the a11y node tree itself, but there are still multiple
places where Android a11y code relies on being able to query the "real" view
hierarchy for required info and ends up failing with bugs in our reparented
case.
### The workaround
- Create a mirror copy of the Android View's a11y nodes as a subtree within [the
Flutter Android
embedding](https://github.com/flutter/engine/blob/5b952f286fc070e99cf192775fa5c9dfe858b692/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java).
This mirror copy consists of virtual nodes and is part of the larger virtual
a11y node tree created by [Flutter's
`AccessibilityNodeProvider`](https://github.com/flutter/engine/blob/5b952f286fc070e99cf192775fa5c9dfe858b692/shell/platform/android/io/flutter/view/AccessibilityBridge.java#L64).
- (P and above only): blacklisted APIs and reflection are usually used to create
a mirror copy of the Android View's a11y nodes. The critical private
information are the node IDs of the parent and children of the a11y hierarchy,
which we need to construct our mirror correctly but which are private data to
the nodes. On newer Android versions [we read the data we need from the
serialized binary form of the
node](https://github.com/flutter/engine/blob/5b952f286fc070e99cf192775fa5c9dfe858b692/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java#L546)
instead.
- [We forward all events sent to mirror a11y nodes to the "real" a11y nodes in
the Android View's subtree as
well](https://github.com/flutter/engine/blob/5b952f286fc070e99cf192775fa5c9dfe858b692/shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java#L268).
This forwarding involves translating any coordinates on the event, similar to
forwarding touch events. This means users can use a11y services to still
interact with the embedded Android UI.
### Limitations
- Only pre-existing virtual hierarchies can be duplicated and mirrored to our
own virtual subtree in this way. That means that embedded views like WebView
are accessible through this mechanism, but things like `Button` are not.
- The use of reflection and serialization to read private a11y node data in
order to make an accurate mirror tree is extremely fragile. This could
easily be broken by Android in a future release.
Tracked in
[flutter/flutter#19418](https://github.com/flutter/flutter/issues/19418). Better
support for reparenting a11y hierarchies is also an [Android SDK issue](https://issuetracker.google.com/issues/138442751).
## Text input
Normally the embedded Android view wouldnt be able to get any text input
because it's inside of a `VirtualDisplay` that is always reported as being
unfocused. Android doesn't offer any APIs to dynamically set or change the focus
of a `Window`. The focused `Window` for a Flutter app would normally be the one
actually holding the "real" Flutter texture and UI, and directly visible to the
user. `InputConnections` (how text is entered in Android) to unfocused Views are
normally discarded.
### Workaround
- We override
[checkInputConnectionProxy](https://github.com/flutter/engine/blob/036ddbb0ee6858ae532df82a2747aa93faee4487/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java#L376)
so that Android treats the Flutter View as a proxy for the Input Method Editor
(IME) when it tries to talk to the embedded Android View. Basically, Android
asks the Flutter View for an `InputConnection` when the embedded Android View
wants one.
- Q changed the `InputMethodManager` (IMM) to be instantiated per `Window`
instead of being a global singleton. So our naive attempt at setting up a
proxy no longer worked on Q. To work around this further we [created a
subclass of `Context` that returned the same `IMM` as the Flutter View instead
of the real `IMM` for the window when `getSystemService` is
queried](https://github.com/flutter/engine/blob/44f24bd/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java#L257).
This means whenever Android tries to use the `IMM` in our `VirtualDisplay` it
still sees and uses the `IMM` from the real display that has been set to use
the Flutter View as a proxy.
- When the Flutter Android embedding is asked for an `InputConnection`, it
checks to see if an embedded Android View is "really" the target of the input.
If so it [internally goes to the embedded Android View, gets the
`InputConnection` from
there](https://github.com/flutter/engine/blob/036ddbb0ee6858ae532df82a2747aa93faee4487/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java#L206),
and returns it to Android as if the embedded Android View's `InputConnection`
is its own.
- Android sees that the Flutter view is focused and available, so it takes the
`InputConnection` that's ultimately from the embedded Android View and uses it
successfully.
### In embedded WebViews
WebViews running on Android versions pre N have additional complications because
they have their own internal logic for creating and setting up input connections
that don't completely defer to Android. Within the `flutter_webview` plugin
we've also needed to add extra workarounds in order to enable text input there.
- [Set a proxy view that listens for input connections on the same thread as
WebView.](https://github.com/flutter/packages/blob/27f3de3/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java#L113).
Without this WebView would internally consume all `InputConnection` calls
without the Flutter View proxy ever being notified.
- [Within the proxy thread, refer back to the Flutter View for input
creation.](https://github.com/flutter/packages/blob/27f3de3e1e6ed1c0f2cd23b0d1477ff3f0955aaa/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java#L67).
- [Reset the input connection back to the Flutter thread when the WebView is
unfocused](https://github.com/flutter/packages/blob/27f3de3/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java#L128).
This prevents the text input from being "stuck" inside of the WebView.
### Limitations
- In general this depends on internal Android behavior and is brittle. The Q
workaround was a surprise for us as we were broken by what was supposed to be
an internal Android refactoring.
- Some text functionality is still broken. For example the "Copy" and "Share"
dialogue is currently not usable.