最近の画面

[最近] 画面は、オーバービュー画面、最近のタスクリスト、最近のアプリ画面とも呼ばれ、最近アクセスしたアクティビティタスクを一覧表示するシステムレベルの UI です。ユーザーはリスト内を移動して、再開するタスクを選択したり、スワイプでリストからタスクを削除したりできます。

[最近] 画面では、Android 5.0(API レベル 21)で導入されたドキュメント中心モデルが使用されています。このモデルでは、異なるドキュメントを含んだ同じアクティビティの複数のインスタンスが [最近] 画面にタスクとして表示されます。たとえば、Google ドライブには Google ドキュメントごとにタスクがあります。各ドキュメントは、[最近] 画面にタスクとして表示されます。

2 つの Google ドライブ ドキュメントをそれぞれ別のタスクとして表示している [最近] 画面。

もう 1 つの一般的な例は、ユーザーがブラウザを使用しているときに、[共有] > [Gmail] をタップする場合です。Gmail アプリの [作成] 画面が表示されます。[最近] ボタンをタップすると、Chrome と Gmail が別々のタスクとして実行されているのがわかります。

[最近] 画面に、個別のタスクとして実行されている Chrome と Gmail が表示されています。

通常、タスクとアクティビティの [最近] 画面での表示方法は、システムが定義した方法に従います。この動作を変更する必要はありません。ただし、[最近] 画面でアクティビティを表示する方法とタイミングについては、アプリで定めることができます。

ActivityManager.AppTask クラスを使用するとタスクの管理ができ、Intent クラスの Activity フラグを使用すると、[最近] 画面に Activity を追加するタイミング、あるいはそこから削除するタイミングを指定できます。また、<activity> 属性を使用するとマニフェストで動作を設定できます。

[最近] 画面にタスクを追加する

Intent クラスのフラグを使用してタスクを追加すると、[最近] 画面でドキュメントを開いたり再度開いたりするタイミングと方法を自由に制御できます。<activity> 属性を使用する際に、常に新しいタスクでドキュメントを開くか、既存のタスクを再利用するかを選択できます。

インテント フラグを使用してタスクを追加する

アクティビティの新しいドキュメントを作成するとき、startActivity() メソッドを呼び出して、アクティビティを起動するインテントを渡します。論理的な区切りを挿入して、[最近] 画面でアクティビティが新しいタスクとして扱われるようにするには、アクティビティを起動する IntentaddFlags() メソッドに FLAG_ACTIVITY_NEW_DOCUMENT フラグを渡します。

新しいドキュメントを作成するときに FLAG_ACTIVITY_MULTIPLE_TASK フラグを設定すると、ターゲット アクティビティをルートとする新しいタスクが常に作成されます。この設定により、同じドキュメントを複数のタスクで開くことができます。次のコードは、これをメイン アクティビティで行い、コンポーザブルから新しいアクティビティを開始する方法を示しています。

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 が OS レベルのルーティング(onCreateonNewIntent)を処理し、@Composable 関数は提供された状態に基づいて UI をレンダリングするだけです。

activity 属性を使用したタスクの追加

アクティビティでは、マニフェストで、<activity> 属性 android:documentLaunchMode を使用することにより、常に新しいタスクを開始するように指定することもできます。この属性には 4 つの値があり、ユーザーがアプリでドキュメントを開いたときに次のような効果をもたらします。

intoExisting
アクティビティがドキュメント用に既存のタスクを再利用します。これは、FLAG_ACTIVITY_MULTIPLE_TASK フラグを設定せずに FLAG_ACTIVITY_NEW_DOCUMENT フラグを設定した場合と同じです。インテント フラグを使用したタスクの追加セクションをご覧ください。
always
すでにドキュメントが開いている場合でも、アクティビティがドキュメント用に新しいタスクを作成します。この値の使用は、FLAG_ACTIVITY_NEW_DOCUMENT フラグと FLAG_ACTIVITY_MULTIPLE_TASK フラグの両方を設定する場合と同じです。
none
アクティビティは、ドキュメント用に新しいタスクを作成しません。履歴画面では、アクティビティはデフォルトとして扱われます。ユーザーが最後に呼び出したアクティビティから再開するアプリに対して、タスクを 1 つ表示します。
never
アクティビティは、ドキュメント用に新しいタスクを作成しません。この値を設定すると、FLAG_ACTIVITY_NEW_DOCUMENT フラグと FLAG_ACTIVITY_MULTIPLE_TASK フラグの動作がオーバーライドされます。いずれかがインテントに設定されている場合、履歴画面にアプリのタスクが 1 つ表示され、ユーザーが最後に呼び出したアクティビティから再開します。

タスクを削除する

デフォルトでは、ドキュメント タスクはアクティビティの終了時に [最近] 画面から自動的に終了します。この動作は、ActivityManager.AppTask クラス、Intent フラグ、または <activity> 属性を使ってオーバーライドできます。

<activity> 属性の android:excludeFromRecentstrue に設定することで、いつでも [最近] 画面からタスクを完全に除外できます。

<activity> 属性の android:maxRecents に整数値を設定すると、[最近] 画面に含めることができるタスク数の上限を指定できます。タスク数が上限に達すると、直近で最も使用されていない(LRU)タスクが履歴画面から消えます。デフォルトは 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")
    }
}

完了したタスクを保持する

アクティビティが終了した後もタスクを [最近] 画面に保持したい場合、そのアクティビティを起動するインテントの addFlags() メソッドに FLAG_ACTIVITY_RETAIN_IN_RECENTS フラグを渡します。

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:autoRemoveFromRecentsfalse に設定します。デフォルト値は、ドキュメント アクティビティの場合は true、通常のアクティビティの場合は false です。この属性を使用すると、FLAG_ACTIVITY_RETAIN_IN_RECENTS フラグがオーバーライドされます。

最近アクセスした URL の共有を有効にする(Pixel のみ)

Android 12 以降を搭載した Pixel デバイスでは、最近表示したウェブ コンテンツへのリンクを [最近] 画面から直接共有できます。ユーザーはアプリ内のコンテンツにアクセスした後で [最近] 画面をスワイプして、コンテンツを表示したアプリを見つけ、リンクボタンをタップして URL をコピーまたは共有できます。

最近閲覧したウェブ コンテンツを共有するためのリンクが表示された最近の項目画面。

次の例に示すように、ウェブ UI を提供し、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)
    }
}