[Android] ActivityのTaskについて

## はじめに
画像シェア機能を扱う際にTaskやRecentについて色々と調査した結果を書き残しておきます。

## taskとは
Taskはアプリで使用する一連のActivityの集まりで、内部的にはBackstackを用いてActivityを管理している。
## task内のbackstackの挙動操作
ActivityのlaunchModeを指定することにより、Backstackの挙動を制御できます。設定方法は主に二つある：

1. AndroidManifest.xmlでの設定
2. Intentのフラグを使用した設定
### AndroidManifest.xmlでの設定
AndroidManifest.xml内で以下のように設定する：
```jsx
<activity android:launchMode="...">
</activity>
```
以下はlaunchModeのオプション：
* standard
    * Backstackの最上部に新しいActivityインスタンスを追加する
    * 異なるTask上に新しいActivityインスタンスを生成することができる
* singleTop
    * 既にbackstackのトップにある場合はonNewIntent()を呼ぶだけ
    * backstackの一番上でなければ新しいインスタンスを生成
* singleTask
    * 新しいBackstackのルートとしてインスタンスを作成する
    * 既存のインスタンスがある場合は、そのインスタンスにonNewIntent()を呼び出し、新しいルートにする

### Flagでの設定

**FLAG_ACTIVITY_NEW_TASK**

- `launchMode`の`singleTask`と同じ

**FLAG_ACTIVITY_SINGLE_TOP**

- `launchMode`の`singleTop`と同じ

**FLAG_ACTIVITY_CLEAR_TOP**

- `launchMode`に該当するものはない
- 既に存在するActivityのインスタンスがある場合、Backstack上でこのActivityの上にあるインスタンスをすべて削除し、onNewIntent()を呼び出す
- `FLAG_ACTIVITY_CLEAR_TOP`と`FLAG_ACTIVITY_NEW_TASK`は一緒に使われることが多い
    - 新しいタスクでインスタンスを生成した時にBack Stackをclearできるから

## task自体の操作
### Recentから消したい場合
最近開いたアプリの一覧を見ることができると思いますが、それをRecentと呼びます。
Activityで`finish()`を呼び出すだけではTaskが削除されないので、Recentに残り続けてしまいます。これを回避したい場合は`android:excludeFromRecents=”true”`をmanifestに設定するか、最後のactivityを`finishAndRemoveTask()` で終了させる必要があります。
### taskのinstance管理
backstackの管理で使ったlaunchModeはActivityの生成を管理していて、taskを生成することに関しては何もできない。
そこでdocumentLaunchModeを使うことで、taskを生成したりすることができる。これを活用すると、一つのアプリで複数のTaskを作成し同時に二つのActivityの操作などが可能になります。
[What is the difference between android:launchMode and android:documentLaunchMode attribute?](https://stackoverflow.com/questions/32154059/what-is-the-difference-between-androidlaunchmode-and-androiddocumentlaunchmode)

以下`documentLaunchMode`で使えるオプションの引用(https://developer.android.com/guide/components/activities/recents?hl=ja#attr-doclaunch)
- `intoExisting`
アクティビティがドキュメント用に既存のタスクを再利用します。これは、`FLAG_ACTIVITY_MULTIPLE_TASK` フラグを設定せずに `FLAG_ACTIVITY_NEW_DOCUMENT` フラグを設定した場合と同じです。上記の[インテント フラグを使用したタスクの追加](https://developer.android.com/guide/components/activities/recents?hl=ja#flag-new-doc)をご覧ください。

- `always`
すでにドキュメントが開いている場合でも、アクティビティがドキュメント用に新しいタスクを作成します。この値の使用は、`FLAG_ACTIVITY_NEW_DOCUMENT` フラグと `FLAG_ACTIVITY_MULTIPLE_TASK` フラグの設定と同じです。

- `none` ← **これがデフォルト**
アクティビティは、ドキュメント用に新しいタスクを作成しません。Recentでは、アクティビティがデフォルトとして扱われます。つまり、ユーザーが最後に実行したアクティビティから再開するアプリに対してタスクが 1 つ表示されます。

- `never`
アクティビティは、ドキュメント用に新しいタスクを作成しません。この値を設定すると、`FLAG_ACTIVITY_NEW_DOCUMENT` フラグと `FLAG_ACTIVITY_MULTIPLE_TASK` フラグの動作がオーバーライドされます。いずれかがインテントに設定されている場合、Recentでは、ユーザーが最後に実行したアクティビティから再開するアプリに対してタスクが 1 つ表示されます。

## 画像シェアの際の細かい知見
他アプリから画像シェアなどで自アプリを開く場合のちょっとした知見です。
singleTask vs singleInstance

- `launchMode=singleTask`では、既存のTaskがある場合にその中のBackstackに追加されますが、singleInstanceでは既存のTaskのActivityをすべてクリアして唯一のActivityとなる。そのため、シェア画面などでは、シェア終了後に元のActivityに戻れるsingleTaskが理想的

Google files vs その他

- Filesはおそらく独自定義のせいでシェアした際のactivityがGoogle FilesのTask上に重なってしまう
- 以下を参考に、新しいintentをonCreateで呼ぶと通常通りFilesではなくこちらのアプリの上にactivityが作られる

```kotlin
@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);
    setContentView(R.layout.main);

    if (!isTaskRoot()) { // Or check getIntent().getFlags() for SINGLE_INSTANCE
        Intent newIntent = new Intent(getIntent());
        newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(newIntent);
        finish();
    }
}
```

https://github.com/facebook/react-native/issues/39553

## さいごに
間違いなどありましたらコメントお願いします。