はじめに
- Qiita初投稿です。読みづらい箇所、不適切な箇所があるかもしれません。その際はコメントで指摘していただけると幸いです。
- CSV出力をする際に結構苦戦したので、自分用のメモとして残します。
環境
Android Studio Chipmunk | 2021.2.1 Patch 2
対象OS
Android 9,10,11
android {
compileSdk 31
defaultConfig {
// ~省略~
minSdk 26
targetSdk 31
// ~省略~
}
// ~省略~
}
不具合内容
Androidアプリで、DBの内容をスマホのストレージへCSV出力をしたいが、エラーが発生し、機種によっては出力できない。
今回はDownloadフォルダへアクセスしたいが、できない。
エラーメッセージ
/jp.hogehoge W/System.err: java.nio.file.AccessDeniedException: /storage/emulated/0/Download/test.csv
原因
- OSによって、外部ストレージへのアクセス権限取得方法が異なるため。
- 10から、「対象範囲別ストレージ」という概念が適用されセキュリティ対策が強化された。
- ただし、10と11ではその回避方法が異なる。
今回の対策
今回、私は、ディレクトリ名を直接取得してそこにCSVファイルを書き込む手法をとっていました。
そのため、下記の方法でエラーを回避しました。
(1)Android9以前
「対象範囲別ストレージ」の制限なし。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
があればOK。(当然ですが、9,10,11共通事項として、アプリの中でパーミッションの取得は必要です。)
パーミッション取得サンプルコード
// 外部ストレージへの書き込み・読み取りのパーミッションを確認、取得する
private fun checkPermission() {
// パーミッションが許可されていない場合
// 今回は外部ストレージへの書き込み、読み取りのパーミッション
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
// パーミッションのリクエストを表示する
ActivityCompat.requestPermissions(
this, arrayOf(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
android.Manifest.permission.READ_EXTERNAL_STORAGE
),
PERMISSION_WRITE_EX_STR
)
}
}
(2)Android10
10から対象範囲別ストレージという概念が適用された。外部ストレージへのアクセスはできない。アクセスしたい場合はマニフェストファイルに
<application
android:requestLegacyExternalStorage="true"
>
を適用する必要がある。これがtrueだと、外部ストレージへのアクセス権限はAndroid9以前の挙動をする。つまり9で動くコードを書いておけばOK。
(3)Android11以降
10と同様に対象範囲別ストレージの概念あり。
ただし、 AndroidManifest.xml
に android:requestLegacyExternalStorage="true"
を書いていても強制的に無視される(=false扱い)。そのため、9で動くコードを書いていてもダメ。
今回の場合は、直接ディレクトリを所得してそこに書き込むので
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
を書いておけばOK。
※11以降での非推奨の方法(力技)※
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
を書いておけば、どこでもアクセスできるので、この方法でもOK。
ただし、このコードを書いておくと、GooglePlayでの公開時に、適切な理由を書いておかないとリジェクトされます。(私はリジェクトされました)
今回も私はこのMANAGE_EXTERNAL_STORAGEは使っていません。
推奨はしませんがこんな方法もあるよ、という情報です。
参考記事
下記リンクが非常に参考になりました。ありがとうございます。
公式ドキュメント
Android ストレージのユースケースとおすすめの方法リンクタイトル
「すべてのファイルへのアクセス」(MANAGE_EXTERNAL_STORAGE)権限の使用
他の方の記事
【Qiita】Android: Android 10 からの対象範囲別ストレージ (Scoped Storage) とメディアファイルアクセスのまとめ
【Qiita】Android10,Android11のファイルパーミッション
【Android】 「5 月 5 日より、アプリがストレージへの広範なアクセスを必要とする理由をお知らせいただく必要があります」について