対象範囲別外部ストレージ アクセスを管理する

ユーザーがファイルを詳細に管理して整理できるように、Android 10(API レベル 29)以降をターゲットとするアプリには、外部ストレージ デバイスに対する特別アクセス権限がデフォルトで付与されます(対象範囲別ストレージ)。このようなアプリで認識できるのは、Context.getExternalFilesDir() を使用してアクセスするアプリ固有ディレクトリと、特定のメディアタイプだけに限られます。アプリ固有ディレクトリ内や MediaStore 内に存在していないファイルにアクセスする必要がない限り、対象範囲別ストレージを使用することをおすすめします。

対象範囲別ストレージがファイル アクセスに与える影響の概要を以下の表に示します。

ファイルの場所 必要なパーミッション アクセス方法(*) アプリをアンインストールするとファイルも削除されるか?
アプリ固有ディレクトリ なし getExternalFilesDir()
メディア コレクション
(写真、動画、音声)
READ_EXTERNAL_STORAGE

他のアプリのファイルにアクセスする場合のみ
MediaStore ×
ダウンロード
(ドキュメント、
電子書籍)
なし ストレージ アクセス フレームワーク
(システムのファイル選択ツールを読み込みます)
×

* ストレージ アクセス フレームワークを使用すると、パーミッションをリクエストすることなく、上記の表に示されている各場所にアクセスすることができます。

このページでは、対象範囲別ストレージを使用しているときにアプリがアクセスできるファイルについて整理し、外部ストレージ デバイス上に保存されている他のファイルの共有、アクセス、編集を引き続き行うことができるようにアプリを更新する方法について説明します。

ファイル アクセスに必要なパーミッション

対象範囲別ストレージを使用するアプリは、アプリ固有ディレクトリの内部と外部の両方で、作成したファイルに対する読み書きアクセス権限を常に有します。そのため、保存やアクセスの対象となるファイルが、そのアプリ自身の作成したファイルだけに限られる場合、READ_EXTERNAL_STORAGE パーミッションや WRITE_EXTERNAL_STORAGE パーミッションをリクエストする必要はありません。

他のアプリが作成したファイルにアクセスするには、以下の両方の条件が満たされている必要があります。

  1. アプリに READ_EXTERNAL_STORAGE パーミッションが付与されていること。
  2. 対象ファイルが、明確に定義された次のいずれかのメディア コレクション内にあること。

別のアプリが作成した他のファイル(「ダウンロード」ディレクトリ内のファイルを含む)にアクセスできるようにするには、アプリがストレージ アクセス フレームワークを使用する必要があります。ストレージ アクセス フレームワークにより、ユーザーは特定のファイルを選択できます。

READ_EXTERNAL_STORAGE パーミッションが付与されていたとしても、外部ストレージ デバイスの未加工ファイル システム ビューにアクセスするアプリの場合、アクセスできるのは、アプリ固有ディレクトリに限られます。アプリが未加工ファイル システム ビューを使用してアプリ固有ディレクトリの外部にあるファイルを開こうとすると、エラーが発生します。

  • マネージコードの場合、FileNotFoundException が発生します。
  • ネイティブ コードの場合、EPERM カーネルエラーが発生します。

メディアデータに関する制限

対象範囲別ストレージの場合、メディア関連データに関して以下の制限が適用されます。

  • アプリに ACCESS_MEDIA_LOCATION パーミッションが付与されていない場合は、画像ファイル内の Exif メタデータが編集されます。詳細については、写真内の位置情報にアクセスする方法をご覧ください。
  • MediaStore.Files テーブル自体もフィルタリングされ、写真、動画、オーディオ ファイルだけが表示されます。たとえば、PDF ファイルはこのテーブルには表示されません。
  • メディア ファイルへのアクセスは、Java ベースまたは Kotlin ベースのコードで開始する必要があります。Java ベースまたは Kotlin ベースのコード内で MediaStore を使用します。詳細については、ネイティブ コードからメディア ファイルにアクセスする方法をご覧ください。

メディア ファイル操作ガイドには、MediaStore 内の個々のドキュメントやドキュメント ツリーにアクセスするためのベスト プラクティスが説明されています。対象範囲別ストレージを使用するアプリの場合、メディアにアクセスするには、ここに記載されている方法を使用する必要があります。

写真内の位置情報

写真によっては、Exif メタデータ内に位置情報が含まれており、写真の撮影場所をユーザーが確認できる場合があります。ただし、この位置情報は機密情報であるため、対象範囲別ストレージを使用しているアプリの場合、Android 10 では、この情報はデフォルトで非表示になっています。

写真の位置情報にアクセスする必要のあるアプリの場合、次の手順を行ってください。

  1. アプリのマニフェスト内で、ACCESS_MEDIA_LOCATION パーミッションをリクエストします。
  2. 下記のコード スニペットのように、MediaStore オブジェクトから setRequireOriginal() を呼び出して、写真の URI を渡します。

    Kotlin

        // Get location data from the ExifInterface class.
        val photoUri = MediaStore.setRequireOriginal(photoUri)
        contentResolver.openInputStream(photoUri).use { stream ->
            ExifInterface(stream).run {
                // If lat/long is null, fall back to the coordinates (0, 0).
                val latLong = ?: doubleArrayOf(0.0, 0.0)
            }
        }
        

    Java

        Uri photoUri = Uri.withAppendedPath(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                cursor.getString(idColumnIndex));
    
        final double[] latLong;
    
        // Get location data from the ExifInterface class.
        photoUri = MediaStore.setRequireOriginal(photoUri);
        InputStream stream = getContentResolver().openInputStream(photoUri);
        if (stream != null) {
            ExifInterface exifInterface = new ExifInterface(stream);
            double[] returnedLatLong = exifInterface.getLatLong();
    
            // If lat/long is null, fall back to the coordinates (0, 0).
            latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
            // Don't reuse the stream associated with the instance of "ExifInterface".
            stream.close();
        } else {
            // Failed to load the stream, so return the coordinates (0, 0).
            latLong = new double[2];
        }
        

対象範囲別ストレージをオプトアウトする

アプリが対象範囲別ストレージに完全に対応するまでは、アプリのターゲット SDK レベルや requestLegacyExternalStorage マニフェスト属性に基づいて、一時的に対象範囲別ストレージ機能を無効にすることができます。

  • Android 9(API レベル 28)以前をターゲットにします。
  • Android 10 以降をターゲットにしている場合は、アプリのマニフェスト ファイル内で requestLegacyExternalStorage の値を true に設定します。

        <manifest ... >
          <!-- This attribute is "false" by default on apps targeting
               Android 10 or higher. -->
          <application android:requestLegacyExternalStorage="true" ... >
            ...
          </application>
        </manifest>
        

Android 9 以前をターゲットとしているアプリが対象範囲別ストレージを使用したときにどのように動作するのかテストするには、requestLegacyExternalStorage の値を false に設定して、対象範囲別ストレージ機能を有効にします。