メディアデータはアプリ間で共有されるので、アプリがデータへアクセスするには、アクセス許可が必要です。
この許可の権限の範囲は、プライバシー保護の観点から、徐々に狭められてきました。
そして、対象範囲別ストレージにおいて、他アプリが所有するファイルへの書き込みは、URI(ファイル)毎の許可が必要になっています。
許可の取得は、ユーザと対話をする形式(ダイアログ)で行われます。手順が少し複雑です。
ここに「他アプリが所有するファイルへ書き込み」する方法を、まとめます。
※環境:Android Studio Narwhal Feature Drop | 2025.1.2 Patch 1
目次
アクセス許可
対象範囲別ストレージにおいて、他のアプリが所有するファイルへアクセスする場合の許可は、図のようになっています。

読み出し
通常のパーミッションシステムで、メディアデータへアクセスする許可を取得できます。
※詳細は「外部ストレージへアクセスするパーミッションと権限の範囲」を参照
許可を得たアプリは、ファイルを読み出し可能です。
書き込み
後述する方法で、URI(ファイル)へアクセスする許可を取得できます。
許可を得たアプリは、ファイルへ書き込み可能です。
注意「URIに出されるアクセス許可」と「パーミッションシステムの出すアクセス許可」は独立しています。両者の間に関係性は無く、全くの別物です。
許可の取得手順
アクセス許可の取得は、次のような手順で行います。

URIのアクセス許可をチェック。許可が無い場合はClientアプリを起動します。
手順2Clientアプリはダイアログを表示して、ユーザにアクセス許可・拒否の判定を求めます。
手順3ダイアログによる判定の結果をコールバックで返します。
手順1~3はアプリケーションコンポーネント(Activity)の連携と同じ動作です。
※詳細は「App component:Activity」を参照
書き込みの例
書き込みの具体的な例です。※Activity Result APIで記述しています。
コールバックの定義と登録
registerForActivityResultはコールバックの定義と登録を行います。
ラムダ式がコールバックです。「許可・拒否」はresultCodeで判断できます。
そして、このコールバックを利用するコンポーネントのランチャー(_MediaWriteDialog : ActivityResultLauncher)を返します。
private lateinit var targetUri: Uri
private lateinit var targetData: ByteArray
...
val _MediaWriteDialog = registerForActivityResult(
ActivityResultContracts.StartIntentSenderForResult()
) { result ->
if(result.resultCode == RESULT_OK) { // アクセス許可
writeToUri_Resolver(targetData, targetUri)
}
else { } // アクセス拒否
}
registerForActivityResultは、LifecycleOwnerのcurrentStateが「STARTED」より前のタイミングで実行して下さい。Activityのライフサイクルと密接に関係しています。
※LifecycleOwnerについては「ライフサイクル対応コンポーネント作成」を参照
許可チェックとダイアログ起動
URIのアクセス許可をチェックします。許可の無い場合はClientアプリを起動(launch)し、ダイアログを表示させます。
launchの引数は取得したい許可のリクエスト(MediaWriteReq : IntentSenderRequest)です。書き込みを行いたいのでMediaStore.createWriteRequestを使って、リクエストを構築しています。
@RequiresApi(Build.VERSION_CODES.R)
fun onWrite(uri: Uri, id: Long, data: ByteArray) {
try {
val _uri = ContentUris.withAppendedId(uri, id)
val _chkUriPerm = checkUriPermission( // 許可チェック
_uri, Process.myPid(), Process.myUid(),
FLAG_GRANT_WRITE_URI_PERMISSION
)
if (_chkUriPerm == PackageManager.PERMISSION_GRANTED) {
writeToUri_Resolver(data, _uri)
} else {
targetUri = _uri
targetData = data // ↓↓ダイアログを起動
_MediaWriteDialog.launch(MediaWriteReq(contentResolver, _uri))
}
} catch (e: Exception) { Log.i(TAG, "[onWrite] ${e}") }
}
@RequiresApi(Build.VERSION_CODES.R)
fun MediaWriteReq(resolver: ContentResolver, uri: Uri): IntentSenderRequest {
val _pendingIntent = MediaStore.createWriteRequest(resolver, listOf(uri))
val _intentSenderRequest = IntentSenderRequest.Builder(
_pendingIntent.intentSender
).build()
return _intentSenderRequest
}
MediaStore.createWriteRequestはAPI≧30から利用が可能です。
書き込みの実行
書き込みの実行(onWrite)を行うと、ダイアログが表示されて、ユーザに「許可・拒否」の判定を求めます。
val _uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI val _id = 1000000033 // val _uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI // val _id = 1000000034 // val _uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI // val _id = 1000000035 onWrite(_uri, _id, _data)
なお、ダイアログに表示される画像はサムネイル(.thumbnail/メディア情報のID.jpg)です。



「許可」となったURIは、このアプリによる書き込みが可能です。
例えば上書きするのであれば、通常のアクセスと同様に、下記の拡張関数で行えます。
※メディアファイルの書き込みについては「ImagesデータへMedia Storeでアクセス(API≧29)」を参照
fun Context.writeToUri_Resolver(data: ByteArray, uri: Uri) {
try {
contentResolver.openOutputStream(uri, "w")?.use { outputStream ->
outputStream.write(data)
outputStream.flush()
}
} catch (e: Exception) { Log.i(TAG, "[writeToUri] ${e}") }
}
「既存のファイルへ書き込み」と考えるのが妥当
対象範囲別ストレージにおいて、既存のファイルへ書き込み(更新)を行う場合、アクセス許可の必要性は、次のようになります。
自アプリのファイル ⇒ 許可が不要
他アプリのファイル ⇒ 許可が必要
ファイルの所有者により、必要性の有無が変わります。
ファイルの所有者を判別し、必要な場合のみに許可の取得を行いたいところです。しかし、API内に所有者を判別する手段が準備されていません。
ですので、所有者に関係なく、全ての既存ファイルにおいて、許可の取得を行うしかありません。
既存のファイルへ書き込むと、古いデータは消失します。
不本意な書き込み(間違った書き込み、悪意のあるアプリによる改ざん)は大問題なので、書き込みを行う前に、所有者に関係なく確認を行う行為は理に適ています。

そう考えると、アクセス許可を取得する場面は、「他アプリが所有するファイルへ書き込み」よりも「既存ファイルへ書き込み」する時と考える方が、妥当かも知れません。
Appendix:その他のリクエスト
createWriteRequest以外のリクエストも準備されています。
| リクエスト (MediaStore.***) | 概要 | 許可後の動作 |
|---|---|---|
| createWriteRequest | 書き込み許可を要求 | URIへ書き込み(更新)可能 |
| createDeleteRequest | 削除する許可を要求 | 即時に削除 |
| createTrashRequest | 廃棄する許可を要求 | ゴミ箱へ廃棄したと見なされる (is_trashedカラムを設定) 一定時間経過後にシステムが削除 |
| createFavoriteRequest | お気に入り登録する許可を要求 | お気に入り登録したと見なされる (is_favoriteカラムを設定) |
| ※許可はアプリに対して与えられる | ||
いずれも、アプリが既存ファイル(自アプリ・他アプリが所有するファイル)へアクセスする許可を取得するためのリクエストです。
関連記事:
