mirror of
https://github.com/android/skills.git
synced 2026-09-14 20:07:09 +08:00
Updates skills (2026-04-27 08:51)
This commit is contained in:
@@ -27,8 +27,7 @@ metadata:
|
||||
- If refining the rule, give instructions on finding a narrower and specific keep rule using the [/references/REFLECTION-GUIDE.md](references/REFLECTION-GUIDE.md).
|
||||
- If removing, provide reasoning on why it needs to be removed.
|
||||
- \[ \] Step 8: After keep analysis, order the keep rule analysis based on the impact to the codebase hierarchy defined in [references/KEEP-RULES-IMPACT-HIERARCHY.md](references/KEEP-RULES-IMPACT-HIERARCHY.md)
|
||||
- \[ \] Step 9: Advise the user to run tests using [UI
|
||||
automator](https://developer.android.com/training/testing/other-components/ui-automator) to assess that there is no issue with the suggested changes, concentrating on the packages where keep rules will be affected.
|
||||
- \[ \] Step 9: Advise the user to run tests using [UI automator](references/android/training/testing/other-components/ui-automator.md) to assess that there is no issue with the suggested changes, concentrating on the packages where keep rules will be affected.
|
||||
|
||||
## Mandatory rules
|
||||
|
||||
|
||||
+8
-5
@@ -12,6 +12,9 @@ performance, and more. To your users, this means:
|
||||
> **Important:** You should always enable optimization for your app's release build; however, you probably don't want to enable it for tests or libraries. For more information about using R8 with tests, see [Test and troubleshoot the
|
||||
> optimization](https://developer.android.com/topic/performance/app-optimization/test-and-troubleshoot-the-optimization). For more information about enabling R8 from libraries, see [Optimization for library authors](https://developer.android.com/topic/performance/app-optimization/library-optimization).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Important:** We released an agent skill that you can use to improve your app performance with R8. Try out the skill from the [Android skills repository](https://github.com/android/skills).
|
||||
|
||||
## R8 optimization overview
|
||||
|
||||
R8 uses a multi-phase process to optimize your app for size and speed. Key
|
||||
@@ -27,8 +30,8 @@ operations include the following:
|
||||
efficiency and reduce overhead. Key techniques include:
|
||||
|
||||
- **Method inlining**: R8 replaces a method call site with the actual body
|
||||
of the called method. This eliminates the overhead of a function call and
|
||||
lets R8 conduct further optimizations.
|
||||
of the called method. This eliminates the overhead of a function call
|
||||
and lets R8 conduct further optimizations.
|
||||
|
||||
- **Class merging**: R8 combines sets of classes and interfaces into a
|
||||
single class. This reduces the number of classes in the app, lowering
|
||||
@@ -119,8 +122,8 @@ removed.
|
||||
|
||||
### Enable optimized resource shrinking
|
||||
|
||||
To turn on the new optimized resource shrinking pipeline for a version of AGP
|
||||
before 9.0.0, add the following to your project's `gradle.properties` file:
|
||||
To enable the new optimized resource shrinking pipeline for AGP 8.12 or 8.13,
|
||||
add the following to your project's `gradle.properties` file:
|
||||
|
||||
android.r8.optimizedResourceShrinking=true
|
||||
|
||||
@@ -167,7 +170,7 @@ the Android Gradle Plugin (AGP) and the R8 compiler.
|
||||
|---|---|
|
||||
| 9.1 | **Classes repackaged by default:** R8 repackages classes (moving them to the unnamed package, at the top level) to compact DEX further, eliminating the need to specify `-repackageclasses` option. For information about how this works and how to opt out, see [global options](https://developer.android.com/topic/performance/app-optimization/global-options#global-options). |
|
||||
| 9.0 | **Optimized resource shrinking:** Enabled by default (controlled using `android.r8.optimizedResourceShrinking`). [Optimized resource shrinking](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization#optimize-resource-shrinking) helps integrate resource shrinking with the code optimization pipeline, leading to smaller, faster apps. By optimizing both code and resource references simultaneously, it identifies and removes resources referenced exclusively from unused code. This is a significant improvement over the previous separate optimization processes. This is especially useful for apps that share substantial resources and code across different form factor verticals, with measured improvements of over 50% in app size. The resulting size reduction leads to smaller downloads, faster installations, and a better user experience with faster startup, improved rendering, and fewer ANRs. **Library rule filtering:** Support for global options (for example, `-dontobfuscate`) in library consumer rules has been dropped, and apps will filter them out. For more information, see [Add global options](https://developer.android.com/topic/performance/app-optimization/global-options). **Kotlin null checks:** Optimized by default (controlled using `-processkotlinnullchecks`). This version also introduced significant improvements in build speed. For more information, see [Global options for additional optimization](https://developer.android.com/topic/performance/app-optimization/global-options#global-options). **Optimize specific packages:** You can use `packageScope` to optimize specific packages. This is in experimental support. For more information, see [Optimize specified packages with `packageScope`](https://developer.android.com/topic/performance/app-optimization/optimize-specified-packages). **Optimized by default:** Support for `getDefaultProguardFile("proguard-android.txt")` has been dropped, because it includes `-dontoptimize`, which should be avoided. Instead, use `"proguard-android-optimize.txt"`. If you need to globally disable optimization in your app, [add the flag manually to a proguard file](https://developer.android.com/topic/performance/app-optimization/global-options#global-options-2). |
|
||||
| 8.12 | **Resource shrinking:** Initial support added (Off by default. Enable using `isShrinkResources`). Resource shrinking works in tandem with R8 to identify and remove unused resources effectively. **Logcat retracing:** Support for automatic retracing in the Android Studio [Logcat window](https://developer.android.com/studio/debug/logcat). |
|
||||
| 8.12 | **Optimized resource shrinking:** Initial support added (controlled using `android.r8.optimizedResourceShrinking`). [Optimized resource shrinking](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization#optimize-resource-shrinking) helps integrate resource shrinking with the code optimization pipeline. You must manually enable it in this version of AGP. **Logcat retracing:** Support for automatic retracing in the Android Studio [Logcat window](https://developer.android.com/studio/debug/logcat). |
|
||||
| 8.6 | **Improved retracing:** Includes filename and line number retracing by default for all `minSdk` levels (previously required `minSdk` 26+ in version 8.2). Updating R8 helps ensure that stack traces from obfuscated builds are readily and clearly readable. This version improves how line numbers and source files are mapped, making it easier for tools like the Android Studio Logcat to automatically retrace crashes to the original source code. |
|
||||
| 8.0 | **Full mode by default:** [R8 full mode](https://developer.android.com/topic/performance/app-optimization/full-mode) provides significantly more powerful optimization. It is enabled by default. You can opt out using `android.enableR8.fullMode=false`. |
|
||||
| 7.0 | **Full mode available:** Introduced as an opt-in feature using `android.enableR8.fullMode=true`. Full mode applies more powerful optimizations by making stricter assumptions about how your code uses reflection and other dynamic features. While it reduces app size and improves performance, it might require additional keep rules to prevent necessary code from being stripped. |
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
The UI Automator testing framework provides a set of APIs to build UI tests that
|
||||
interact with user apps and system apps.
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** This documentation covers the modern approach to writing UI Automator tests, introduced with [UI Automator 2.4](https://developer.android.com/jetpack/androidx/releases/test-uiautomator#2.4.0). This approach makes your tests more concise, readable, and robust. The API is under development, and we strongly recommend using it for any new development with UI Automator. The [legacy API guidance](https://developer.android.com/training/testing/other-components/ui-automator-legacy) is also available.
|
||||
|
||||
## Introduction to modern UI Automator testing
|
||||
|
||||
UI Automator 2.4 introduces a streamlined, Kotlin-friendly Domain Specific
|
||||
Language (DSL) that simplifies writing UI tests for Android. This new API
|
||||
surface focuses on predicate-based element finding and explicit control over app
|
||||
states. Use it to create more maintainable and reliable automated tests.
|
||||
|
||||
UI Automator lets you test an app from outside of the app's process. This
|
||||
lets you test release versions with minification applied. UI Automator also
|
||||
helps when writing macrobenchmark tests.
|
||||
|
||||
Key features of the modern approach include:
|
||||
|
||||
- A dedicated `uiAutomator` test scope for cleaner and more expressive test code.
|
||||
- Methods like `onElement`, `onElements`, and `onElementOrNull` for finding UI elements with clear predicates.
|
||||
- Built-in waiting mechanism for conditional elements `onElement*(timeoutMs:
|
||||
Long = 10000)`
|
||||
- Explicit app state management such as `waitForStable` and `waitForAppToBeVisible`.
|
||||
- Direct interaction with accessibility window nodes for multi-window testing scenarios.
|
||||
- Built-in screenshot capabilities and a `ResultsReporter` for visual testing and debugging.
|
||||
|
||||
## Set up your project
|
||||
|
||||
To begin using the modern UI Automator APIs, update your project's
|
||||
`build.gradle.kts` file to include the [latest dependency](https://developer.android.com/jetpack/androidx/releases/test-uiautomator#2.4.0):
|
||||
|
||||
### Kotlin
|
||||
|
||||
dependencies {
|
||||
...
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
|
||||
}
|
||||
|
||||
### Groovy
|
||||
|
||||
dependencies {
|
||||
...
|
||||
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
|
||||
}
|
||||
|
||||
## Core API concepts
|
||||
|
||||
The following sections describe core concepts of the modern UI Automator API.
|
||||
|
||||
### The uiAutomator test scope
|
||||
|
||||
Access all new UI Automator APIs within the **`uiAutomator { ... }`**
|
||||
block. This function creates a `UiAutomatorTestScope` that provides a concise
|
||||
and type-safe environment for your test operations.
|
||||
|
||||
uiAutomator {
|
||||
// All your UI Automator actions go here
|
||||
startApp("com.example.targetapp")
|
||||
onElement { textAsString() == "Hello, World!" }.click()
|
||||
}
|
||||
|
||||
### Find UI elements
|
||||
|
||||
Use UI Automator APIs with predicates to locate UI elements. These predicates
|
||||
let you define conditions for properties such as text, selected or focused
|
||||
state, and content description.
|
||||
|
||||
- `onElement { predicate }`: Returns the first UI element that matches the
|
||||
predicate within a default timeout. The function throws an exception if it
|
||||
doesn't locate a matching element.
|
||||
|
||||
// Find a button with the text "Submit" and click it
|
||||
onElement { textAsString() == "Submit" }.click()
|
||||
|
||||
// Find a UI element by its resource ID
|
||||
onElement { viewIdResourceName == "my_button_id" }.click()
|
||||
|
||||
// Allow a permission request
|
||||
watchFor(PermissionDialog) {
|
||||
clickAllow()
|
||||
}
|
||||
|
||||
- `onElementOrNull { predicate }`: Similar to `onElement`, but returns
|
||||
`null` if the function finds no matching element within the timeout. It
|
||||
doesn't throw an exception. Use this method for optional elements.
|
||||
|
||||
val optionalButton = onElementOrNull { textAsString() == "Skip" }
|
||||
optionalButton?.click() // Click only if the button exists
|
||||
|
||||
- `onElements { predicate }`: Waits until at least one UI element matches
|
||||
the given predicate, then returns a list of all matching UI elements.
|
||||
|
||||
// Get all items in a list Ui element
|
||||
val listItems = onElements { className == "android.widget.TextView" && isClickable }
|
||||
listItems.forEach { it.click() }
|
||||
|
||||
Here are some tips for using `onElement` calls:
|
||||
|
||||
- Chain `onElement` calls for nested elements: You can chain `onElement`
|
||||
calls to find elements within other elements, following a parent-child
|
||||
hierarchy.
|
||||
|
||||
// Find a parent Ui element with ID "first", then its child with ID "second",
|
||||
// then its grandchild with ID "third", and click it.
|
||||
onElement { viewIdResourceName == "first" }
|
||||
.onElement { viewIdResourceName == "second" }
|
||||
.onElement { viewIdResourceName == "third" }
|
||||
.click()
|
||||
|
||||
- Specify a timeout for `onElement*` functions by passing a value representing
|
||||
milliseconds.
|
||||
|
||||
// Find a Ui element with a zero timeout (instant check)
|
||||
onElement(0) { viewIdResourceName == "something" }.click()
|
||||
|
||||
// Find a Ui element with a custom timeout of 10 seconds
|
||||
onElement(10_000) { textAsString() == "Long loading text" }.click()
|
||||
|
||||
### Interact with UI elements
|
||||
|
||||
Interact with UI elements by simulating clicks or setting text in editable
|
||||
fields.
|
||||
|
||||
// Click a Ui element
|
||||
onElement { textAsString() == "Tap Me" }.click()
|
||||
|
||||
// Set text in an editable field
|
||||
onElement { className == "android.widget.EditText" }.setText("My input text")
|
||||
|
||||
// Perform a long click
|
||||
onElement { contentDescription == "Context Menu" }.longClick()
|
||||
|
||||
## Handle app states and watchers
|
||||
|
||||
Manage the lifecycle of your app and handle unexpected UI elements that might
|
||||
appear during your tests.
|
||||
|
||||
### App lifecycle management
|
||||
|
||||
The APIs provide ways to control the state of the app under test:
|
||||
|
||||
// Start a specific app by package name. Used for benchmarking and other
|
||||
// self-instrumenting tests.
|
||||
startApp("com.example.targetapp")
|
||||
|
||||
// Start a specific activity within the target app
|
||||
startActivity(SomeActivity::class.java)
|
||||
|
||||
// Start an intent
|
||||
startIntent(myIntent)
|
||||
|
||||
// Clear the app's data (resets it to a fresh state)
|
||||
clearAppData("com.example.targetapp")
|
||||
|
||||
### Handle unexpected UI
|
||||
|
||||
The `watchFor` API lets you define handlers for unexpected UI elements,
|
||||
such as permission dialogs, that might appear during your test flow. This
|
||||
uses the internal watcher mechanism but offers more flexibility.
|
||||
|
||||
import androidx.test.uiautomator.PermissionDialog
|
||||
|
||||
@Test
|
||||
fun myTestWithPermissionHandling() = uiAutomator {
|
||||
startActivity(MainActivity::class.java)
|
||||
|
||||
// Register a watcher to click "Allow" if a permission dialog appears
|
||||
watchFor(PermissionDialog) { clickAllow() }
|
||||
|
||||
// Your test steps that might trigger a permission dialog
|
||||
onElement { textAsString() == "Request Permissions" }.click()
|
||||
|
||||
// Example: You can register a different watcher later if needed
|
||||
clearAppData("com.example.targetapp")
|
||||
|
||||
// Now deny permissions
|
||||
startApp("com.example.targetapp")
|
||||
watchFor(PermissionDialog) { clickDeny() }
|
||||
onElement { textAsString() == "Request Permissions" }.click()
|
||||
}
|
||||
|
||||
`PermissionDialog` is an example of a `ScopedWatcher<T>`, where `T` is the
|
||||
object passed as a scope to the block in `watchFor`. You can create custom
|
||||
watchers based on this pattern.
|
||||
|
||||
### Wait for app visibility and stability
|
||||
|
||||
Sometimes tests need to wait for elements to become visible or stable.
|
||||
UI Automator offers several APIs to help with this.
|
||||
|
||||
The `waitForAppToBeVisible("com.example.targetapp")` waits for a UI element with
|
||||
the given package name to appear on the screen within a customizable timeout.
|
||||
|
||||
// Wait for the app to be visible after launching it
|
||||
startApp("com.example.targetapp")
|
||||
waitForAppToBeVisible("com.example.targetapp")
|
||||
|
||||
Use the `waitForStable()` API to verify that the app's UI is considered stable
|
||||
before interacting with it.
|
||||
|
||||
// Wait for the entire active window to become stable
|
||||
activeWindow().waitForStable()
|
||||
|
||||
// Wait for a specific Ui element to become stable (e.g., after a loading animation)
|
||||
onElement { viewIdResourceName == "my_loading_indicator" }.waitForStable()
|
||||
|
||||
> [!NOTE]
|
||||
> **Note:** In most cases, `waitForStable()` isn't strictly necessary when using `onElement { ... }` because `onElement` already includes a timeout. Use `waitForStable()` primarily in combination with `onElements { ... }` to verify that all UI elements are visible, when you know that the UI is in an unstable state, or for specific screenshot testing scenarios where you need the UI to completely settle before capturing. `waitForStable()` works by waiting until no changes are detected in the accessibility tree for a set period. Note that this UI stability check doesn't guarantee that the app is fully idle, as background tasks might still be running.
|
||||
|
||||
## Use UI Automator for Macrobenchmarks and Baseline Profiles
|
||||
|
||||
Use UI Automator for performance testing with [Jetpack Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)
|
||||
and for generating [Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles/overview), as it provides a reliable way to
|
||||
interact with your app and measure performance from an end-user perspective.
|
||||
|
||||
Macrobenchmark uses UI Automator APIs to drive the UI and measure interactions.
|
||||
For example, in startup benchmarks, you can use `onElement` to detect when UI
|
||||
content is fully loaded, enabling you to measure [Time to Full Display
|
||||
(TTFD)](https://developer.android.com/topic/performance/vitals/launch-time#time-full). In jank benchmarks, UI Automator APIs are used to scroll lists or
|
||||
run animations to measure frame timings. Functions like `startActivity()` or
|
||||
`startIntent()` are useful for getting the app into the correct state before
|
||||
measurement begins.
|
||||
|
||||
When [generating Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles/create-baselineprofile), you automate your app's critical user
|
||||
journeys (CUJs) to record which classes and methods require pre-compilation. UI
|
||||
Automator is an ideal tool for writing these automation scripts. The modern
|
||||
DSL's predicate-based element finding and built-in wait mechanisms (`onElement`)
|
||||
lead to more robust and deterministic test execution compared to other methods.
|
||||
This stability reduces flakiness and ensures that the generated Baseline Profile
|
||||
accurately reflects the code paths executed during your most important user
|
||||
flows.
|
||||
|
||||
## Advanced features
|
||||
|
||||
The following features are useful for more complex testing scenarios.
|
||||
|
||||
### Interact with multiple windows
|
||||
|
||||
The UI Automator APIs let you directly interact with and inspect UI
|
||||
elements. This is particularly useful for scenarios involving multiple windows,
|
||||
such as Picture-in-Picture (PiP) mode or split-screen layouts.
|
||||
|
||||
// Find the first window that is in Picture-in-Picture mode
|
||||
val pipWindow = windows()
|
||||
.first { it.isInPictureInPictureMode == true }
|
||||
|
||||
// Now you can interact with elements within that specific window
|
||||
pipWindow.onElement { textAsString() == "Play" }.click()
|
||||
|
||||
### Screenshots and visual assertions
|
||||
|
||||
Capture screenshots of the entire screen, specific windows, or
|
||||
individual UI elements directly within your tests. This is helpful for visual
|
||||
regression testing and debugging.
|
||||
|
||||
uiautomator {
|
||||
// Take a screenshot of the entire active window
|
||||
val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot()
|
||||
fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png"))
|
||||
|
||||
// Take a screenshot of a specific UI element (e.g., a button)
|
||||
val buttonBitmap: Bitmap = onElement { viewIdResourceName == "my_button" }.takeScreenshot()
|
||||
buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png"))
|
||||
|
||||
// Example: Take a screenshot of a PiP window
|
||||
val pipWindowScreenshot = windows()
|
||||
.first { it.isInPictureInPictureMode == true }
|
||||
.takeScreenshot()
|
||||
pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png"))
|
||||
}
|
||||
|
||||
The `saveToFile` extension function for Bitmap simplifies saving the captured
|
||||
image to a specified path.
|
||||
|
||||
### Use ResultsReporter for debugging
|
||||
|
||||
The `ResultsReporter` helps you associate test artifacts, like screenshots,
|
||||
directly with your test results in Android Studio for easier inspection and
|
||||
debugging.
|
||||
|
||||
uiAutomator {
|
||||
startApp("com.example.targetapp")
|
||||
|
||||
val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results
|
||||
val file = reporter.addNewFile(
|
||||
filename = "my_screenshot",
|
||||
title = "Accessible button image" // Title that appears in Android Studio test results
|
||||
)
|
||||
|
||||
// Take a screenshot of an element and save it using the reporter
|
||||
onElement { textAsString() == "Accessible button" }
|
||||
.takeScreenshot()
|
||||
.saveToFile(file)
|
||||
|
||||
// Report the artifacts to instrumentation, making them visible in Android Studio
|
||||
reporter.reportToInstrumentation()
|
||||
}
|
||||
|
||||
## Migrate from older UI Automator versions
|
||||
|
||||
If you have existing UI Automator tests written with older API surfaces, use the
|
||||
following table as a reference to migrate to the modern approach:
|
||||
|
||||
| Action type | Old UI Automator method | New UI Automator method |
|
||||
|---|---|---|
|
||||
| Entry point | `UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())` | Wrap test logic in the `uiAutomator { ... }` scope. |
|
||||
| Find UI elements | `device.findObject(By.res("com.example.app:id/my_button"))` | `onElement { viewIdResourceName == "my\_button" }` |
|
||||
| Find UI elements | `device.findObject(By.text("Click Me"))` | `onElement { textAsString() == "Click Me" }` |
|
||||
| Wait for idle UI | `device.waitForIdle()` | Prefer `onElement`'s built-in timeout mechanism; otherwise, `activeWindow().waitForStable()` |
|
||||
| Find child elements | Manually nested `findObject` calls | `onElement().onElement()` chaining |
|
||||
| Handle permission dialogs | `UiAutomator.registerWatcher()` | `watchFor(PermissionDialog)` |
|
||||
Reference in New Issue
Block a user