Chrome からの共有で onNewIntent が呼ばれない問題 (Android アプリの documentLaunchMode の話)
Android の API level 21 で導入された documentLaunchMode
に関する Activity の挙動にバグっぽいところがあって、結構扱いに困るのでまとめておきます。 「documentLaunchMode
? 関係ないや」 って思ってる人でも、外部アプリからの Intent を扱うアプリを書くときに影響されるかもしれません。
まとめ
- Activity の
documentLaunchMode
としてintoExisting
が指定されており、既存の Activity が再利用される場合は、launchMode
がstandard
であっても Activity のonNewIntent
メソッドが呼ばれるべき *1 だが、実際には呼ばれない。- バグっぽい。
- AndroidManifext.xml で
documentLaunchMode
の値を指定していなくても、外部アプリが投げる Intent にintoExisting
相当のフラグ (FLAG_ACTIVITY_NEW_DOCUMENT
フラグ) が設定されていることがあるので、外部からの Intent を受け取る Activity を書いている場合には否が応でもこの問題に悩まされる。 - とりあえずの対処法は、AndroidManifest.xml で、Activity に
launchMode="singleTop"
を指定すること。- アプリ内部で使用することを考えて
singleTop
にしづらい場合は、内部で使用する Activity と外部からの Intent を受け取るための Activity を別に定義して (単純にサブクラスを作れば良いだろう)、外部からの Intent を受け取るための Activity のlaunchMode
をsingleTop
にする、などの対応が必要。
- アプリ内部で使用することを考えて
関連する発表資料
この記事の内容に関連した話を 2015 年 9 月 30 日の 「関西モバイル研究会 #6」 で発表しました。
背景
この問題に行きついた背景です。
- 外部アプリからテキスト (
Intent.EXTRA_TEXT
) を含む Intent を受け取って処理する Activity を含むアプリを開発していた。- Chrome アプリの 「共有」 で投げられる Intent も受け取れる。
- 次のようなユーザー操作を行うと、Activity が新しい Intent を扱えなくて困った。
- Chrome でとあるページ (例として 「http://example.com/1」) で 「共有」 し、Activity を起動。 → Activity では Intent から 「http://example.com/1」 というテキストを取りだせる。
- Activity を終了せずに、アプリを切り替えて Chrome に戻る。
- 別のページ (例として 「http://example.com/2」) で再度 「共有」 し、同じ Activity を起動。 → Activity が破棄されずに残っていた場合、
onStart
メソッドやonResume
メソッドが呼ばれるが、onNewIntent
メソッドが呼ばれず、getIntent
メソッドで取得できる Intent も前に開いたときの Intent になっている。 → 新しい Intent の情報が得られない!
documentLaunchMode
の話
documentLaunchMode
とはなんぞや、という話。
- 公式ドキュメントとしては以下のページを読むとわかりやすいです。
- API level 21 で導入。
- Activity 起動時の挙動を制御するためのもの。
- これを指定することで、「最近のタスク一覧」 に同じアプリケーションの複数ドキュメントを表示できる。
- AndroidManifest.xml で指定することもできるし、
startActivity
にフラグとして渡すこともできる。
intoExisting
documentLaunchMode
の値としてintoExisting
を指定すると、既存のすべてのタスクの中から Intent のコンポーネント情報とdata
の URI が同じものが探される。- あれば、そのタスクの中身がリセットされて、Activity が再利用されて Activity の
onNewIntent
メソッドが呼ばれる。 - なければ、新しいタスクが生成される。
- あれば、そのタスクの中身がリセットされて、Activity が再利用されて Activity の
- と、ドキュメントには書かれているが、
onNewIntent
メソッドが呼ばれるのはタスクの Activity のlaunchMode
としてsingleTop
などの値が指定されている場合で、standard では呼ばれなかった。 (まじかよ……) - DocumentCentricApps というサンプルコードがあるが、サンプルコードのコメントなどはドキュメント通りの挙動を期待しているが、実際の挙動はドキュメントどおりにはならなかった。 (Nexus 5; Android 5.1.1 で確認)
バグっぽい
- ドキュメントを読む限り、
documentedLaunchMode
がintoExisting
ならば、たとえlaunchMode
がstandard
でもonNewIntent
メソッドが呼ばれてもよさそうだけど、実際には呼ばれない。 - API level 23 (Android 6.0 Marshmallow) での挙動も見てみたけどやっぱり同じだった。
- もともと
launchMode
がstandard
だとonNewIntent
メソッドは呼ばれないものなので、仕様なのかもしれないけど仕様だとしたら糞仕様だしバグならつらい。 - ていうか 『Activities launched with the
FLAG_ACTIVITY_NEW_DOCUMENT
flag must have theandroid:launchMode="standard"
attribute value』 って書かれてるし……。 - StackOverflow でこの問題について書いてる人が居た : Android API guide > Overview Screen: onNewIntent() not called with FLAG_ACTIVITY_NEW_DOCUMENT - Stack Overflow
- バグ報告した : Issue 188033 - android - onNewIntent method not called with FLAG_ACTIVITY_NEW_DOCUMENT flag - Android Open Source Project - Issue Tracker - Google Project Hosting
Chrome アプリの 「共有」 で onNewIntent
が呼ばれない問題
原因
Chrome アプリの 「共有」 (メニューの 「共有」 の右側に表示されている、最後に起動したアプリアイコンをタップ) で投げられる Intentを見てみたところ、以下のフラグが設定されていました。
FLAG_ACTIVITY_PREVIOUS_IS_TOP
FLAG_ACTIVITY_FORWARD_RESULT
FLAG_GRANT_READ_URI_PERMISSION
FLAG_ACTIVITY_NEW_DOCUMENT
(API level 21;documentLaunchMode="intoExisting"
相当のフラグ)
上述のとおり、documentLaunchMode
が intoExisting
で、launchMode
が standard
だと、古い Activity が再利用されるもの onNewIntent
メソッドが呼ばれないのです。 つらいですね。
回避策
一つの解決策としては、Activity に launchMode="singleTop"
を設定する方法があります。 これだと onNewIntent
メソッドが呼ばれるようになります。
しかし、外部からの Intent を受け取るだけじゃなくてアプリ内部からも起動される Activity の場合は launchMode="singleTop"
を設定することができないことも多いでしょう。 そういう場合は、アプリ内部からしか起動されない Activity と外部からの Intent を受け取る Activity を別のクラスにしてしまって (片方をもう一方のサブクラスとするなど)、外部からの Intent を受け取る Activity にだけ launchMode="singleTop"
を設定するなどの方法を採ることになるでしょうか。 もうちょっといい方法があれば嬉しいですが、それぐらいしか思いつきませんでした。
終わり
この問題、とにかく辛いのですがあんまり困ってる人を見かけないので、もしいい感じの回避策があるのでしたら教えてください!!!!
*1:ドキュメントを読む感じだとそうだと思われる