“最近”屏幕

“最近使用的应用”屏幕(也称为概览屏幕、近期任务列表或最近用过的应用屏幕)是一个系统级界面,上面列出了最近访问过的activity任务。用户可以浏览该列表,选择要恢复的任务,或通过滑动来从列表中移除任务。

“最近使用的应用”屏幕使用以文档为中心的模型(在 Android 5.0 [API 级别 21] 中引入),也就是说,如果同一 Activity 的多个实例包含不同的文档,它们可能会在“最近使用的应用”屏幕中显示为任务。比如说,Google 云端硬盘的几个 Google 文档可能各自都有一个任务。每个文档都会在“最近使用的应用”屏幕中显示为任务:

“最近使用的应用”屏幕上显示两个 Google 云端硬盘文档,每个文档都表示为单独的任务。

另一个常见的例子是当用户使用浏览器时,点按分享 > Gmail 。系统会显示 Gmail 应用的写邮件 屏幕。此时点按“最近使用的应用”按钮会显示 Chrome 和 Gmail 作为单独的任务运行:

“最近使用的应用”屏幕上显示 Chrome 和 Gmail 作为单独的任务运行。

通常情况下,您应允许系统定义您的任务和 activity 在“最近使用的应用”屏幕中的表示方式。您无需修改此行为。不过,您的应用可以确定 activity 在“最近使用的应用”屏幕中的显示方式和时间。

ActivityManager.AppTask 类可让您管理任务,而 Intent 类的 activity 标记可让您指定何时在“最近使用的应用”屏幕中添加或移除 activity。此外,您还可以使用 <activity>属性在清单中设置 行为。

将任务添加到“最近使用的应用”屏幕

使用 Intent 类的标记添加任务,您可以更好地控制在“最近使用的应用”屏幕中打开或重新打开文档的方式和时间。当您使用 <activity>属性时,您可以 选择始终在新任务中打开文档或重复使用文档的现有 任务。

使用 Intent 标记添加任务

为 activity 创建新文档时,您可以调用 startActivity() 方法并向其传递到启动该 activity 的 intent。要插入逻辑 断点,以便系统将您的 activity 视为“最近使用的应用” 屏幕中的新任务,请将 FLAG_ACTIVITY_NEW_DOCUMENT 标记传入启动该 activity 的 IntentaddFlags() 方法。

如果您在创建新文档时设置了 FLAG_ACTIVITY_MULTIPLE_TASK 标记,则系统始终会以 目标 activity 为根来创建新任务。此设置支持同一文档在多个任务中打开。以下代码演示了主 activity 如何执行此操作并从可组合项启动新 activity:

private fun newDocumentIntent(context: Context): Intent =
    Intent(context, NewDocumentActivity::class.java).apply {
        addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)
        putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, documentCounter++)
    }

@Composable
fun CreateDocumentButton() {
    val context = LocalContext.current
    Button(
        onClick = {
            val intent = newDocumentIntent(context)
            // Add FLAG_ACTIVITY_MULTIPLE_TASK if needed based on state
            context.startActivity(intent)
        }
    ) {
        Text("Create New Document")
    }
}

当主 activity 启动一个新 activity 时,系统会在现有任务中进行搜索,看是否任务的 intent 与该 activity 的 intent 组件名称和 intent 数据匹配。如果未找到该任务,或 intent 包含 FLAG_ACTIVITY_MULTIPLE_TASK 标记,那么将以该 activity 为根创建一个新任务。

如果系统找到一个任务,其 intent 与 intent 组件名称和 intent 数据匹配,则会将其带到最前端,并将新 intent 传递到 onNewIntent()。 新 activity 获取 intent,并在“最近使用的应用”屏幕中创建新文档,如下例所示:

class DocumentCentricActivity : ComponentActivity() {
    private var documentState by mutableStateOf(
        DocumentState(
            count = 0,
            textResId = R.string.hello_new_document_counter
        )
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val initialCount = intent.getIntExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0)

        documentState = documentState.copy(count = initialCount)

        setContent {
            MaterialTheme {
                DocumentScreen(
                    count = documentState.count,
                    textResId = documentState.textResId
                )
            }
        }
    }

    override fun onNewIntent(newIntent: Intent) {
        super.onNewIntent(newIntent)
        // If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this Activity is reused.
        documentState = documentState.copy(
            textResId = R.string.reusing_document_counter
        )
    }

    data class DocumentState(val count: Int, @StringRes val textResId: Int)

    companion object {
        const val KEY_EXTRA_NEW_DOCUMENT_COUNTER = "KEY_EXTRA_NEW_DOCUMENT_COUNTER"
    }
}

@Composable
fun DocumentScreen(count: Int, @StringRes textResId: Int) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        // UI reacts to whichever string resource ID was passed down
        Text(text = stringResource(id = textResId))
        Spacer(modifier = Modifier.height(8.dp))
        Text(text = "Counter: $count")
    }
}

在上面的代码中,activity 处理操作系统级路由(onCreateonNewIntent),而 @Composable 函数仅负责根据提供的状态呈现界面。

使用 activity 属性添加任务

activity 还可以在其清单中使用 <activity>属性 android:documentLaunchMode指定始终启动到新 任务。该属性具有四个值,在用户使用应用打开文档时分别会产生以下效果:

intoExisting
activity 重复使用文档的现有任务。这与设置了 FLAG_ACTIVITY_NEW_DOCUMENT 标记但没有设置 FLAG_ACTIVITY_MULTIPLE_TASK 标记的效果相同,如 使用 Intent 标记添加任务部分所述。
always
activity 会为文档创建新任务,即使文档已打开也一样。使用该值与同时设置了 FLAG_ACTIVITY_NEW_DOCUMENTFLAG_ACTIVITY_MULTIPLE_TASK 标记的效果相同。
none
activity 不会为文档创建新任务。“最近使用的应用”屏幕会以默认方式处理 activity。它会显示应用的单个任务,该任务是从用户上次调用的任何 activity 恢复的。
never
activity 不会为文档创建新任务。设置此值会替换 FLAG_ACTIVITY_NEW_DOCUMENTFLAG_ACTIVITY_MULTIPLE_TASK 标记的行为。如果在 intent 中设置了任意一个,而且“最近使用的应用”屏幕会显示应用的单个任务,该任务是从用户上次调用的任何 activity 恢复的。

移除任务

默认情况下,当相关的 activity 完成时,文档任务会自动从“最近使用的应用”屏幕中退出。您可以使用 ActivityManager.AppTask 类、Intent标记或 <activity>属性来替换此行为。

<activity>属性 android:excludeFromRecents 设置为 true,即可始终将任务从“最近使用的应用”屏幕中完全排除。

<activity> 属性 android:maxRecents 设置为一个 整数,即可设置您的应用可在 “最近使用的应用”屏幕中包含的最大任务数。一旦达到最大任务数,最早使用的任务将从“最近使用的应用”屏幕中消失。默认值为 16,最大值为 50(内存较低的设备上为 25)。小于 1 的值无效。

使用 AppTask 类移除任务

对于在“最近使用的应用”屏幕中创建新任务的 activity,您可以通过调用 finishAndRemoveTask() 方法来指定何时应移除任务并完成与该任务相关联的所有 activity:

@Composable
fun RemoveTaskButton() {
    val context = LocalContext.current
    Button(
        onClick = {
            // It is good practice to remove a document from the overview stack if not needed anymore.
            (context as? Activity)?.finishAndRemoveTask()
        }
    ) {
        Text("Remove from Recents")
    }
}

保留已完成的任务

如果您即使在任务的 activity 已完成后也希望在“最近使用的应用”屏幕中保留它,请将 FLAG_ACTIVITY_RETAIN_IN_RECENTS 标记传入启动该 activity 的 intent 的 addFlags() 方法。

private fun newDocumentIntent() =
        Intent(this, NewDocumentActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
                    android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)
            putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, getAndIncrement())
        }

要达到同样的效果,请将 <activity>属性 android:autoRemoveFromRecents 设置为 false。文档 activity 的默认值为 true,常规 activity 的默认值为 false。使用此属性会替换 FLAG_ACTIVITY_RETAIN_IN_RECENTS 标记。

启用“最近使用的应用”网址共享(仅限 Pixel)

在搭载 Android 12 或更高版本的 Pixel 设备上,用户可以直接通过“最近使用的应用”屏幕分享最近浏览的网页内容的链接。在某个应用中访问内容后,用户可以滑动到“最近使用的应用”屏幕,并找到在其中查看了相应内容的应用,然后点按链接按钮来复制或分享网址。

“最近使用的应用”屏幕,其中包含用于分享 最近浏览的 Web 内容的链接。

任何应用都可以通过提供 Web 界面并 替换 onProvideAssistContent()来为用户启用“最近使用的应用”链接, 如以下示例所示:

class MainActivity : ComponentActivity() {

    // Track the current URL as state so the UI can update it during navigation
    private var currentWebUri by mutableStateOf("https://example.com/home")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            AppTheme {
                // Pass a lambda to your Compose UI so it can update the URL state
                // as the user navigates through your app.
                MainScreen(
                    onPageChanged = { newUrl -> currentWebUri = newUrl }
                )
            }
        }
    }

    override fun onProvideAssistContent(outContent: AssistContent) {
        super.onProvideAssistContent(outContent)

        // The system calls this when the user enters the Recents screen.
        // Provide the active URI tracked by the Compose state.
        outContent.webUri = Uri.parse(currentWebUri)
    }
}