特定のディレクトリへのアクセス

写真アプリなどは通常、外部ストレージの特定のディレクトリ(Pictures ディレクトリなど)のみにアクセスする必要があります。しかし従来の外部ストレージへのアクセス方法は、こうしたアプリが目的のディレクトリに容易にアクセスできる設計にはなっていませんでした。次に例を示します。

  • マニフェストで READ_EXTERNAL_STORAGE または WRITE_EXTERNAL_STORAGE をリクエストすると、外部ストレージ上のすべての公開ディレクトリにアクセスできますが、この場合、アプリは不要な場所にもアクセスできます。
  • ストレージ アクセス フレームワークを使用すると、通常、ユーザーはシステム UI を使用してディレクトリを選択できますが、アプリが常に同じ外部ディレクトリにアクセスする場合、この選択は不要です。

Android 7.0 では、一般的な外部ストレージ ディレクトリにアクセスできるシンプルな API が提供されています。

外部ストレージ ディレクトリへのアクセス

StorageManager クラスを使用して、適切な StorageVolume インスタンスを取得します。次に、そのインスタンスの StorageVolume.createAccessIntent() メソッドを呼び出して、インテントを作成します。このインテントを使用して、外部ストレージ ディレクトリにアクセスします。リムーバブル メディア ボリュームなど、使用できるすべてのボリュームのリストを取得するには、StorageManager.getStorageVolumes() を使用します。

特定のファイルの情報がある場合は、StorageManager.getStorageVolume(File) を使用して、そのファイルを含む StorageVolume を取得します。この StorageVolumecreateAccessIntent() を呼び出し、このファイルがある外部ストレージ ディレクトリにアクセスします。

外部 SD カードなどのセカンダリ ボリュームで、createAccessIntent() を呼び出すときに null を渡し、特定のディレクトリではなくボリューム全体へのアクセスをリクエストします。プライマリ ボリュームに null を渡すか、無効なディレクトリ名を渡すと、createAccessIntent() は null を返します。

次のコード スニペットは、プライマリ共有ストレージの Pictures ディレクトリを開く方法の例を示しています。

StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
StorageVolume volume = sm.getPrimaryStorageVolume();
Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
startActivityForResult(intent, request_code);

システムは外部ディレクトリへのアクセス権の付与を試み、必要に応じてシンプルな UI で、アクセスを許可するかユーザーに確認します。

図 1 Pictures ディレクトリへのアクセスをリクエストするアプリ

ユーザーがアクセス権を付与すると、RESULT_OK の結果コードと、URI を含むインテント データを指定して、onActivityResult() のオーバーライドが呼び出されます。提供された URI を使用して、ディレクトリの情報にアクセスします。これは、ストレージ アクセス フレームワークで返された URI を使用する場合と同様です。

ユーザーがアクセスを付与しなかった場合は、RESULT_CANCELED の結果コードと、null のインテント データを指定して、onActivityResult() のオーバーライドが呼び出されます。

特定の外部ディレクトリへのアクセスを取得すると、そのディレクトリ内のサブディレクトリへのアクセスも取得します。

リムーバブル メディアのディレクトリへのアクセス

特定のディレクトリへのアクセスを使用してリムーバブル メディア上のディレクトリにアクセスするには、まず MEDIA_MOUNTED 通知をリッスンする BroadcastReceiver を追加します。次に例を示します。

<receiver
    android:name=".MediaMountedReceiver"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>

ユーザーが SD カードなどのリムーバブル メディアをマウントすると、システムは MEDIA_MOUNTED 通知を送信します。この通知は、インテント データ内の StorageVolume オブジェクトを提供します。このオブジェクトを使用して、リムーバブル メディア上のディレクトリにアクセスできます。次の例では、リムーバブル メディア上の Pictures ディレクトリにアクセスします。

// BroadcastReceiver has already cached the MEDIA_MOUNTED
// notification Intent in mediaMountedIntent
StorageVolume volume = (StorageVolume)
    mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
startActivityForResult(intent, request_code);

ベスト プラクティス

外部ディレクトリのアクセス URI はできる限り保持してください。そうすれば、ユーザーに何度もアクセス権の付与を求める必要がなくなります。ユーザーがアクセス権を付与したら、getContentResolver() を呼び出し、返された ContentResolver で、ディレクトリのアクセス URI を指定して takePersistableUriPermission() を呼び出します。システムが URI を保持し、以降のアクセス リクエストでは RESULT_OK を返して、ユーザーに確認の UI を表示しません。

ユーザーが外部ディレクトリへのアクセスを拒否した直後に、アクセスを再度リクエストしないようにしてください。何度もアクセスをリクエストすると、ユーザー エクスペリエンスが低下します。リクエストがユーザーによって拒否され、アプリがアクセスを再度リクエストすると、UI に [Don't ask again] チェックボックスが表示されます。

図 1 リムーバブル メディアへのアクセスを再度リクエストしているアプリ。

ユーザーが [Don't ask again] を選択してリクエストを拒否すると、特定のディレクトリに対するアプリからのリクエストは、今後すべて自動的に拒否され、リクエストに関する UI は表示されなくなります。