ファイルを他のアプリで開きたい ▶︎Intentを勉強する
Intentを完全に理解した(していない)ので共有...何卒何卒
Intent とは
AndroidではActivityがアプリの画面の土台を表しますが、Activityから別のActivityへ移動したい、または他のActivityを使って何かデータを取得したいといったことをしたい時に使うのがIntentです。
Activityから別のActivityへ移動
他のActivityを使って何かデータを取得したい
この時移動先のActivityに何か情報を渡すことができます。
例えば「ブラウザからTwitterアプリへ移動するときにユーザ名
が渡されることで、さっきまでブラウザで表示されていたユーザのページをTwitterアプリで見れる。」といった具合です。
この移動元から渡される 「どのActivityへどのように(どのような情報を引数に)移動するか」 をしているするための依頼書が Intent です。
Intentには2種類ある
- 明示的Intent ... どのActivityへ移動したいか 具体的なActivity名 で指定する
例) Twitterアプリを開く - 暗黙的Intent ... そのActivityへ移動することで 何がしたいのか を指定することで一番最適なActivityを選んで移動する
例) 画像ビューアを開く (どのアプリが開かれるかはこちらから指定しない,画像が見れればなんでもいい)
これら二つを使い分けます。
実装方法
移動するとき
呼び出しもとのActivityからstartActivity()
で呼び出します。
val intent = Intent(移動元Activity, 移動先Activity::class.java)
移動元Activity.startActivity(intent)
val intent = Intent(Intent.移動内容)
移動元Activity.startActivity(intent)
移動内容には「何かを表示したい」時に使うIntent.ACTION_VIEW
や「ファイルなどを選択したい」時に使うIntent.ACTION_OpenDocument
などを指定します。また必要に応じてapply
スコープ関数で情報を追加できます。詳しい内容やサンプルについてはドキュメントを参照してください。
移動先から結果が欲しい時
例えばファイルを選択してくれるActivityがありますが、それから結果を取得するには一手間必要です。
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri :Uri? ->
// 選択結果を受け取った時にしたいこと
// 引数に選んだものが入ってくる
}
// 選択を実行したいところで
launcher.launch("image/*")
Android Viewでは...
Activity Result API
というものを使うそうです。
詳しくはこちらの方の記事がいいかと...!
特にこの章で解説されているActivityResultContractの解説はComposeでも役だったりします。
従来はActivity#onActivityResult
というメソッドをオーバーライドして実装していたようですが、これが非推奨になったり、そもそもこれではComposeで使えなかったりでこちらを使うことはなさそうですが、ググるとよく出てくるので注意。
僕が触ったことのあるよくやる使い方
ケース1 ファイルを選ぶ
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it == null) return@rememberLauncherForActivityResult
// ここ以降でit(Uri)を煮るなり焼くなり好きにする
Log.d("my-app", "uri:$it")
// 後述
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(it, takeFlags)
}
// ファイルを選択したいところで
launcher.launch("image/*") // ファイルの種類(MIME TYPE)を指定する 全ファイルの場合は"*/*"
GetContentとOpenDocumentの違い
ドキュメントではファイルなどの選択のためにOpenDocumentだけでなくGetContentも使っています。
アプリに返されるファイルへの参照は、アクティビティの現在のライフサイクル内でのみ利用できます
ドキュメントより
↑↑↑でも解説されているとおりGetContent
はアクティビティの現在のライフサイクルでのみ利用できるため、画面をひっくり返したりするとクリアされてしまいます。よってより長くファイルの参照を保持したいときはOpenDocument
を使用します。(MIME TYPEの指定も忘れずに...)
落とし穴
OpenDocument
で選択しても、アプリを閉じた後に再度同じUriを開こうとすると権限(パーミッション)の期限が切れるのか、SecurityExceptionが発生してしまいます。
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaDocumentsProvider uri content://com.android.providers.media.documents/document/image%3A1000000026 from pid=2548, uid=10168 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
at android.os.Parcel.createExceptionOrNull(Parcel.java:3011)
at android.os.Parcel.createException(Parcel.java:2995)
...(略)...
これを回避するには Uriを取得したときに ContentResolver#takePersistableUriPermission() を呼び出す必要があります。
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it == null) return@rememberLauncherForActivityResult
// itを使う
+ val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ context.contentResolver.takePersistableUriPermission(it, takeFlags)
ケース2 ファイルをビューアで見る
Intentを呼び出すときは
val intent = Intent(Intent.ACTION_VIEW).apply {
type = "image/*"
data = uri // OpenDocumentなどで取得したUri
}
activity.startActivity(Intent.ACTION_VIEW)
また、uriを見ていい感じにtypeを推測してくれる関数も作っておくと便利です。
fun ContentResolver.getMimeType(uri: Uri): String? {
var mimeType: String? = null
this.query(
uri,
arrayOf(MediaColumns.MIME_TYPE),
null, null, null
).use { cur ->
cur ?: return@use
cur.moveToFirst()
mimeType = cur.getString(0)
Log.d("test", "mimeType${mimeType}")
}
return mimeType
}
これで送られてくるファイルの種類が不定でも対応できるはずです。
val intent = Intent(Intent.ACTION_VIEW).apply {
+ type = activity.contextResolver.getMimeType(uri)
data = uri // OpenDocumentなどで取得したUri
}
activity.startActivity(Intent.ACTION_VIEW)
他にもできること
本当にいろんなインテントがデフォルトであって
- アラーム・タイマーの作成、表示
- カレンダー
- カメラで画像・動画を撮って返す
- 連絡先関係
- メール
- ファイル操作
- タクシーを呼ぶ(?!)
- 電話をかける
- 特定の設定画面を開く
- Webブラウザを開く
のようなものが用意されています。一部APIはパーミッションが必要なのでAndroidManifest.xmlに権限の追加もお忘れなく...!
おのおの呼び出す際に指定しなければいけない項目が多いのでドキュメントをしっかり見ることをお勧めします...
自分のActivityを他のActivityにも暗黙的Intentで呼び出してもらえるようにするには?
しっかり調べてはいませんが、AndroidManifest.xml
のactivity.intent-filter
にタグを追加していく感じっぽいです。(有識者の方知識求む)
ドキュメントの通り見てみると以下の通りです。
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
あとはドキュメント見るなりググるなりしてください。
(TBStenは職務放棄した!)
Intent、(呼び出し方は)完全に理解した!
⭐️⭐️⭐️これであなたもAndroidレベルアップです⭐️⭐️⭐️
Discussion