Экран "Недавние"

Экран "Недавние" (также называемый экраном обзора, списком недавних задач или экраном недавних приложений) – это системный интерфейс, в котором перечислены недавно запущенные действия и задачи. Пользователь может просматривать список, выбирать задачу, которую нужно возобновить, или удалять задачи из списка, проводя по ним пальцем.

На экране "Недавние" используется модель, ориентированная на документы, которая была представлена в Android 5.0 (уровень API 21). В ней несколько экземпляров одного и того же действия, содержащего разные документы, могут отображаться как задачи на экране "Недавние". Например, в Google Диске может быть задача для каждого из нескольких документов Google. Каждый документ отображается как задача на экране "Недавние":

На экране "Недавние" показаны два документа Google Диска, каждый из которых представлен как отдельная задача.

Другой распространенный пример – когда пользователь нажимает Поделиться > Gmail в браузере. Откроется экран Написать приложения Gmail. Если нажать кнопку "Недавние", Chrome и Gmail будут показаны как отдельные задачи.

Экран "Недавние", на котором Chrome и Gmail запущены как отдельные задачи.

Обычно система сама определяет, как задачи и действия отображаются на экране "Недавние". Вам не нужно изменять это поведение. Однако вы можете настроить, как и когда действия будут появляться на экране "Недавние".

Класс ActivityManager.AppTask позволяет управлять задачами, а флаги класса Intent – указывать, когда действие добавляется на экран "Недавние" или удаляется с него. Кроме того, атрибуты <activity> позволяют задать поведение в манифесте.

Как добавить задачи на экран "Недавние"

Использование флагов класса Intent для добавления задачи позволяет лучше контролировать, когда и как документ открывается или повторно открывается на экране "Недавние". При использовании атрибутов <activity> можно выбрать, открывать ли документ в новой задаче или использовать для него существующую.

Как добавить задачу с помощью флага Intent

При создании нового документа для объекта activity вы вызываете метод startActivity(), передавая ему намерение, которое запускает объект activity. Чтобы вставить логический разрыв, в результате которого система будет обрабатывать ваше действие как новую задачу на экране "Недавние", передайте флаг FLAG_ACTIVITY_NEW_DOCUMENT в методе addFlags() объекта Intent, который запускает действие.

Если при создании нового документа вы установите флаг FLAG_ACTIVITY_MULTIPLE_TASK, система всегда будет создавать новую задачу с целевым действием в качестве корневого. Этот параметр позволяет открывать один и тот же документ в нескольких задачах. В следующем коде показано, как основное действие делает это и запускает новое Activity из вашей composable-функции:

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")
    }
}

Когда основное действие запускает новое действие, система ищет среди существующих задач ту, намерение которой соответствует имени компонента намерения и данным намерения для действия. Если задача не найдена или намерение содержит флаг FLAG_ACTIVITY_MULTIPLE_TASK, создается новая задача с действием в качестве корневого элемента.

Если система находит задачу, намерение которой совпадает с названием компонента намерения и данными намерения, она выводит эту задачу на передний план и передает новое намерение в onNewIntent(). Новое действие получает намерение и создает новый документ на экране "Недавние", как показано в следующем примере:

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 обрабатывает маршрутизацию на уровне ОС (onCreate и onNewIntent), а функция @Composable отвечает только за отрисовку пользовательского интерфейса на основе предоставленного состояния.

Как добавить задачу с помощью атрибута действия

В манифесте действия также можно указать, что оно всегда запускается в новой задаче. Для этого используется атрибут android:documentLaunchMode тега <activity>. У этого атрибута есть четыре значения, которые определяют, что происходит, когда пользователь открывает документ в приложении:

intoExisting
В действии повторно используется существующая задача для документа. Это то же самое, что установить флаг FLAG_ACTIVITY_NEW_DOCUMENT без флага FLAG_ACTIVITY_MULTIPLE_TASK, как описано в разделе Использование флага Intent для добавления задачи.
always
Действие создает новую задачу для документа, даже если он уже открыт. Использование этого значения аналогично установке флагов FLAG_ACTIVITY_NEW_DOCUMENT и FLAG_ACTIVITY_MULTIPLE_TASK.
none
Действие не создает новую задачу для документа. На экране "Недавние" действие обрабатывается по умолчанию. В нем отображается одна задача для приложения, которая возобновляется с того действия, которое пользователь вызывал последним.
never
Действие не создает новую задачу для документа. Если задать это значение, оно переопределит поведение флагов FLAG_ACTIVITY_NEW_DOCUMENT и FLAG_ACTIVITY_MULTIPLE_TASK. Если в намерении задан один из этих флагов, а на экране "Недавние" отображается одна задача для приложения, оно возобновляет работу с того действия, которое пользователь вызывал последним.

Удалить задачи

По умолчанию задача документа автоматически удаляется из списка недавних приложений, когда ее действие завершается. Это поведение можно переопределить с помощью класса ActivityManager.AppTask, флага Intent или атрибута <activity>.

Вы можете полностью исключить задачу из списка недавних, задав для атрибута <activity> android:excludeFromRecents значение true.

Вы можете задать максимальное количество задач, которые приложение может включать в экран "Недавние", установив для атрибута <activity> android:maxRecents целочисленное значение. Когда максимальное количество задач будет достигнуто, задача, которая использовалась раньше всех, исчезнет с экрана "Недавние". По умолчанию используется значение 16, а максимальное значение – 50 (25 на устройствах с небольшим объемом памяти). Значения меньше 1 недопустимы.

Как удалять задачи с помощью класса AppTask

В действии, которое создает новую задачу на экране "Недавние", вы можете указать, когда нужно удалить задачу и завершить все связанные с ней действия, вызвав метод finishAndRemoveTask():

@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")
    }
}

Хранить завершенные задачи

Если вы хотите сохранить задачу на экране "Недавние", даже если ее действие завершено, передайте флаг FLAG_ACTIVITY_RETAIN_IN_RECENTS в методе 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. Значение по умолчанию – true для действий с документами и false для обычных действий. Этот атрибут переопределяет флаг FLAG_ACTIVITY_RETAIN_IN_RECENTS.

Включить отправку URL из списка недавних (только для Pixel)

На устройствах Pixel с Android 12 или более поздней версией пользователи могут делиться ссылками на недавно просмотренный веб-контент прямо с экрана "Недавние". После просмотра контента в приложении пользователь может перейти на экран "Недавние", найти приложение, в котором он просматривал контент, и нажать кнопку ссылки, чтобы скопировать URL или поделиться им.

Экран "Недавние" со ссылкой, чтобы поделиться недавно просмотренным веб-контентом.

Чтобы включить ссылки на недавние действия, разработчику приложения нужно предоставить веб-интерфейс и переопределить метод 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)
    }
}