Android 11 のデベロッパー プレビューが公開されました。ぜひお試しのうえ、フィードバックをお寄せください

外部ストレージ内のメディア ファイルを操作する

MediaStore API は、明確に定義された以下のタイプのメディア ファイルにアクセスするためのインターフェースを提供します。

また、MediaStore には、MediaStore.Files と呼ばれるコレクションも含まれています。このコレクションは、すべてのタイプのメディア ファイルに対するアクセスを提供します。

このガイドでは、通常は外部ストレージ デバイスに保存されているメディア ファイルにアクセスして共有する方法について説明します。

ファイルにアクセスする

メディア ファイルをロードするには、ContentResolver から以下のいずれかのメソッドを呼び出します。

  • 単一のメディア ファイルの場合、openFileDescriptor() を使用します。
  • 単一のメディア ファイルのサムネイルの場合、loadThumbnail() を使用して、ロードするサムネイルのサイズを渡します。
  • メディア ファイルのコレクションの場合、query() を使用します。

メディア ファイルにアクセスする方法を次のコード スニペットに示します。

    val resolver = context.getContentResolver()

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }

    // Publish a video onto an external storage device.
    val values = ContentValues().apply {
        put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
        put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
    }
    val item = resolver.insert(collection, values)
    

ネイティブ コードからアクセスする

別のアプリと共有しているファイルや、ユーザーのメディア コレクション内のメディア ファイルなど、アプリのネイティブ コード内で特定のメディア ファイルを処理することが必要になる場合があります。このような場合、まず Java ベースまたは Kotlin ベースのコード内でそのメディア ファイルを探してから、そのファイルに関連付けられたファイル記述子をネイティブ コードに渡します。

メディア オブジェクトのファイル記述子をアプリのネイティブ コードに渡す方法を次のコード スニペットに示します。

Kotlin

    val contentUri: Uri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(BaseColumns._ID))
    val fileOpenMode = "r"
    val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
    val fd = parcelFd?.detachFd()
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
    

Java

    Uri contentUri = ContentUris.withAppendedId(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            cursor.getLong(Integer.parseInt(BaseColumns._ID)));
    String fileOpenMode = "r";
    ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
    if (parcelFd != null) {
        int fd = parcelFd.detachFd();
        // Pass the integer value "fd" into your native code. Remember to call
        // close(2) on the file descriptor when you're done using it.
    }
    

ネイティブ コード内でファイルにアクセスする方法については、Android Dev Summit 2018 の Files for Miles の講演(15 分 20 秒から)をご覧ください。

コンテンツ クエリの列名

mime_type AS MimeType など、列名の代替表記法をアプリのコード内で使用している場合、Android 10(API レベル 29)以降を搭載しているデバイスでは、MediaStore API 内で定義されている列名が必要になります。

アプリ内の子ライブラリが、MimeType など、Android API 内で定義されていない列名を想定している場合は、CursorWrapper を使用して、アプリのプロセス内で列名を動的に変換してください。

保存されているメディア ファイルのステータスを「保留中」にする

Android 10(API レベル 29)以降を搭載しているデバイスの場合、アプリは IS_PENDING フラグを使用することで、ディスクへの書き込み時にメディア ファイルに排他的にアクセスすることができます。

IS_PENDING フラグの使用方法を以下のコード スニペットに示します。このコード スニペットでは、MediaStore.Images コレクションに対応するディレクトリに画像を保存するときに、このフラグを使用しています。

Kotlin

    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val resolver = context.getContentResolver()
    val collection = MediaStore.Images.Media
            .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val item = resolver.insert(collection, values)

    resolver.openFileDescriptor(item, "w", null).use { pfd ->
        // Write data into the pending image.
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    resolver.update(item, values, null, null)
    

Java

    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.IS_PENDING, 1);

    ContentResolver resolver = context.getContentResolver();
    Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    Uri item = resolver.insert(collection, values);

    try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
        // Write data into the pending image.
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now that we're finished, release the "pending" status, and allow other apps
    // to view the image.
    values.clear();
    values.put(MediaStore.Images.Media.IS_PENDING, 0);
    resolver.update(item, values, null, null);
    

他のアプリのメディア ファイルを更新する

対象範囲別ストレージを使用しているアプリの場合、通常は、別のアプリがメディアストアに提供したメディア ファイルを更新することはできません。ただし、プラットフォームがスローする RecoverableSecurityException をキャッチすることで、ファイルを編集することに関してユーザーの同意を得ることが可能です。そして、以下のコード スニペットに示すように、アプリにそのアイテムへの書き込みアクセス権限を付与するようユーザーにリクエストします。

Kotlin

    try {
        // ...
    } catch (rse: RecoverableSecurityException) {
        val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null)
    }
    

Java

    try {
        // ...
    } catch (RecoverableSecurityException rse) {
        IntentSender requestAccessIntentSender = rse.getUserAction()
                .getActionIntent().getIntentSender();

        // In your code, handle IntentSender.SendIntentException.
        startIntentSenderForResult(requestAccessIntentSender, your-request-code,
                null, 0, 0, 0, null);
    }
    

ファイルの場所に関するヒントを提供する

Android 10(API レベル 29)を搭載しているデバイス上にアプリがメディアを保存する場合、デフォルトでは、メディアはタイプ別に整理されます。たとえば、新しい画像ファイルはデフォルトで「Environment.DIRECTORY_PICTURES」ディレクトリに保存されます。このディレクトリは、MediaStore.Images コレクションに対応しています。

アプリがファイルの特定の保存場所(Pictures/MyVacationPictures など)を認識している場合、MediaColumns.RELATIVE_PATH を設定することで、新規作成ファイルの保存場所に関するヒントをシステムに提供することができます。同様に、update() の呼び出し中にディスク上のファイルを移動することができます(MediaColumns.RELATIVE_PATH または MediaColumns.DISPLAY_NAME を変更します)。

一般的なユースケース

このセクションでは、メディア ファイルに関連する一般的なユースケースを実現する方法について説明します。

メディア ファイルを共有する

一部のアプリでは、ユーザーはメディア ファイルを互いに共有できます。たとえば、ソーシャル メディア アプリを使用すると、写真や動画を友だちと共有できます。

ユーザーが共有するメディア ファイルにアクセスするには、ファイルにアクセスする方法一意の名前を使用してボリュームにアクセスする方法で説明されているプロセスを使用します。

メッセージ アプリやプロファイル アプリなど、コンパニオン アプリを提供する場合は、content:// URI を使用してファイル共有をセットアップします。このワークフローは、セキュリティに関するベスト プラクティスとしても推奨されています。

ドキュメントを使用する

一部のアプリは、ユーザーが同僚と共有したり他のドキュメントに読み込んだりするデータを入力するストレージ ユニットとしてドキュメントを使用します。たとえば、ユーザーがビジネス生産性向上マニュアルを開く場合や、EPUB ファイルとして保存された書籍を開く場合などが該当します。

このような場合、システムのファイル選択アプリを開く ACTION_OPEN_DOCUMENT インテントを呼び出して、ユーザーが対象ファイルを選択できるようにします。アプリがサポートしているファイル形式だけを表示するには、Intent.EXTRA_MIME_TYPES エクストラをインテント内に組み込みます。

ユーザーの同意を得た後に ACTION_OPEN_DOCUMENT を使用してファイルを開く方法については、GitHub の ActionOpenDocument サンプルをご覧ください。

ファイルのグループを管理する

ファイル管理アプリやメディア作成アプリは通常、ディレクトリ階層内のファイルのグループを管理します。このようなアプリの場合、ACTION_OPEN_DOCUMENT_TREE インテントを呼び出すことで、ディレクトリ ツリー全体へのアクセス権限をユーザーから付与してもらうことができます。このアクセス権限を付与されたアプリは、選択されたディレクトリとそのサブディレクトリ内の任意のファイルを編集できるようになります。

このインターフェースを使用することで、ユーザーは、ローカルベースやクラウドベースのソリューションがサポートしている DocumentsProvider のインスタンスからファイルにアクセスすることができます。

ユーザーの同意を得た後に ACTION_OPEN_DOCUMENT_TREE を使用してディレクトリ ツリーを開く方法については、GitHub の ActionOpenDocumentTree サンプルをご覧ください。