Android10、Pictures下のファイルはMedia API経由でアクセス

AVDや手持ちの実機では、従来の方法(pathからFileOutputStream生成)でアクセスできるから、気づかなかったよ

一部の実機では、従来の方法では保存も読み込みもできないわ

パーミッション

AndroidManifest.xml で、READ_EXTERNAL_STORAGE権限、WRITE_EXTERNAL_STORAGE権限を宣言しておきます。

また、パーミッションダイアログを表示するコードを実装しておきます。(この記事では省略します)

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Code language: HTML, XML (xml)

android:maxSdkVersion="28"について

https://developer.android.com/training/data-storage/shared/media?hl=ja を見ると、ターゲットがAndroid10以上の場合、android:maxSdkVersion="28"を指定するようにとあります。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
Code language: HTML, XML (xml)

ところが、この指定をすると、ターゲットがAndroid10、実行デバイスがAVDのAndroid10で、新規保存しようとすると、Permission不足で書き込めませんでした。

そのため、ターゲットがAndroid10でも、android:maxSdkVersion="28"を指定せず、パーミッションダイアログを表示することにしました。

筆者の実装コードがよくないのかもしれないから、鵜呑みにしないでね

新規保存

Bitmap bmpをString pathに保存する例です。

pathは従来と同じように組み立てます。例えば、/storage/emulated/0/Pictures/MyApp/foo.jpg です。

String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/MyApp/foo.jpg";
Code language: Java (java)

保存処理をクラスやメソッドに分けることを想定しています。ContentResolverはコンストラクタで渡し、Bitmap bmpとString pathはメソッドの引数で渡します。

pathその他のデータをContetResolverに登録して、imageUriを取得します。

SDK_INT >= 29の場合は、RELATIVE_PATHを設定して、IS_PENDING を オンにします。

final String name = new File(path).getName(); final ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, name); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.DATA, path); if (Build.VERSION.SDK_INT >= 29) { final String relativeDir = getRelativeDir(path); values.put(MediaStore.Images.Media.RELATIVE_PATH, relativeDir); values.put(MediaStore.Images.Media.IS_PENDING, true); } final Uri externalStorageUri = getExternalStorageUri(Build.VERSION.SDK_INT); Uri imageUri = contentResolver.insert(externalStorageUri, values);
Code language: Java (java)
public Uri getExternalStorageUri(int sdk_int) { if (sdk_int < 29) { return MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else { return MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); } }
Code language: Java (java)
path/storage/emulated/0/Pictures/MyApp/foo.jpg
RELATIVE_PATHPictures/MyApp

pathが、/storage/emulated/0/Pictures/MyApp/foo.jpg なら、
RELATIVE_PATHは、/storage/emulated/0からの相対パス Pictures/MyApp です。RELATIVE_PATHに foo.jpgは含めません。

ディレクトリ Pictures/MyApp が存在しなくても、自前でmkdirする必要はありません。contentResolver.insert内で、自動でmkdirされます。

public String getRelativeDir(final String path) { final String target = Environment.getExternalStoragePublicDirectory("").getPath() + "/"; final String relativePath = path.replace(target, ""); final File file = new File(relativePath); final String relativeDir = file.getParent(); return relativeDir; }
Code language: Java (java)

imageUriからOutputStreamを取得して、Bitmap.compressで保存します。

final OutputStream outputStream = contentResolver.openOutputStream(imageUri); bmp.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
Code language: Java (java)

SDK_INT >= 29の場合は、最後に IS_PENDING を オフにします。

if (Build.VERSION.SDK_INT >= 29) { final ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.IS_PENDING, false); contentResolver.update(imageUri, values, null, null); }
Code language: Java (java)

削除

ContentResolverのdeleteメソッドで削除します。ContentResolverからデータが削除されて、画像ファイル自体も削除されます。

public void delete(final long id) { final Uri externalStorageUri = getExternalStorageUri(Build.VERSION.SDK_INT); final String selection = "_id = ?"; final String[] selectionArgs = { String.valueOf(id) }; contentResolver.delete(externalStorageUri, selection, selectionArgs); } public void delete(final String path) { final Uri externalStorageUri = getExternalStorageUri(Build.VERSION.SDK_INT); final String selection = "_data = ?"; final String[] selectionArgs = { path }; contentResolver.delete(externalStorageUri, selection, selectionArgs); }
Code language: Java (java)

Bitmapの読み込み

FileDescriptorバージョンです。

ContentResolverのidから、画像uriを作ります。

画像uriからFileDescriptorを取得します。

BitmapFactory.OptionsのinSampleSizeなどを指定して、FileDescriptorでBitmapを読み込みます。

final Uri externalStorageUri = getExternalStorageUri(Build.VERSION.SDK_INT); final imageUri = ContentUris.withAppendedId(externalContentUri, id); final FileDescriptor fd = context.getContentResolver().openFileDescriptor(imageUri, "r").getFileDescriptor(); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 4; Bitmap bmp = BitmapFactory.decodeFileDescriptor(fd, null, opts);
Code language: Java (java)

InputStreamバージョンです。

画像uriからInputStreamを取得します。

InputStreamでBitmapを読み込みます。

final Uri externalStorageUri = getExternalStorageUri(Build.VERSION.SDK_INT); final imageUri = ContentUris.withAppendedId(externalContentUri, id); final InputStream inputStream = context.getContentResolver().openInputStream(imageUri); Bitmap bmp = BitmapFactory.decodeStream(inputStream);
Code language: Java (java)

関連記事

このセクションでは、プライバシーに関連する Android 10 の主な変更点をご紹介します。
アプリのファイルとメディアを対象とする外部ストレージ アクセス
デフォルトでは、Android 10 以降をターゲットとするアプリには外部ストレージに対するスコープ アクセス、つまり対象範囲別ストレージが付与されます。

https://developer.android.com/about/versions/10/privacy/changes?hl=ja#scoped-storage

外部ストレージからアクセスする

https://developer.android.com/training/data-storage/app-specific?hl=ja#external

共有ストレージからメディア ファイルにアクセスする

https://developer.android.com/training/data-storage/shared/media?hl=ja
タイトルとURLをコピーしました