2014/04/12

Android:FileProviderでファイル共有

他アプリとファイル共有したい場合下記の方法があります。
  • StorageAccessFramework
  • ContentProvider
  • MODE_WORLD_READABLE/WRITABLE (非推奨)

もし、アプリ連携の間だけファイルを共有したい場合は
一時許可ContentProviderのイディオムに従ったFileProviderが便利です。

FileProviderはContentProviderを継承したクラスです。
AndroidManifestに定義する際、一時許可ContentProviderのための設定と、
共有ディレクトリを指定します。
<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.example.myapp.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
    <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
</provider>
コンテンツへのアクセス権限を付与するために、grantUriPermissionsをtrueに。
また、プロバイダ自身は非公開(exported=false)に設定します。

共有ディレクトリの指定はmeta-dataとして定義します。
共有ディレクトリリストのフォーマットが下記。
共有”ファイル”ではなく”ディレクトリ”であることに注意が必要です。
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="images/"/>
    ...
</paths>
files-path
 共有コンテンツがアプリ内領域のコンテンツである場合の要素として指定します。
 Context.getFilesDir()で取得できるfiles/に共有コンテンツがある場合に使用します。

files-path/@name
 この値は共有コンテンツURIのパスセグメントとして利用されます。
 実パスと一致している必要はなく、アプリ内部のファイル構造を隠すことができます。

files-path/@path
 パスにはfiles/のサブディレクトリ名を指定します。
 特定のファイル名ではなくサブディレクトリであることに注意が必要です。

例えば、下記の定義である場合、
<files-path name=“image" path=“share_image" />
共有コンテンツのURIは
 content://com.example.myapp.fileprovider/image/hoge.jpg
実際に共有されるコンテンツは
 com.example.myapp/files/share_image/hoge.jpg

共有コンテンツがアプリ内領域ではなく、Context.getExternalFilesDir()で取得できる
外部領域にある場合はexternal-pathを要素名に定義します。
<external-path name="name" path="path" />
あるいは、Context.getCacheDir()で取得できるキャッシュ領域にある場合は
cache-pathを要素名として定義します。
<cache-path name="name" path="path" />
複数の共有ディレクトリを定義することも可能です。
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name=“share_images" path="images/"/>
    <cache-path name=“share_docs" path="docs/"/>
</paths>

使ってみる

FileProviderはContentProviderのように独自クラスとして拡張する必要がなく、
そのまま使用できるのが便利です。
アプリは前述の共有ディレクトリの情報さえ指定すれば準備完了です。

コンテンツ共有の方法は基本的に一時許可コンテンツプロバイダと同じですが、
FileProviderの場合は共有するコンテンツのURIを生成しておく必要があります。
File shareImgPath = new File(Context.getFilesDir(), “images");
File shareImg = new File(shareImgPath, “share.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.example.myapp.fileprovider", shareImg);
// content://com.example.myapp.fileprovider/share_images/share.jpg
FileProvider.getUriForFileが共有コンテンツURIを作成してくれます。
あとは、 Intent.setFlags()にFLAG_GRANT_READ_URI_PERMISSION,
FLAG_GRANT_WRITE_URI_PERMISSIONを指定してアプリ連携すれば、
アクセスを一時的なものに限定したコンテンツの共有が実現できます。
Uri contentUri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", shareImg);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, getContentResolver().getType(contentUri));
intent.addFlags(
    Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 0/*request code*/);
FileProviderには、次のメソッドが備えられています。

FileProvider.query()
 共有コンテンツのファイル名とファイルサイズを格納したCursorを返します。
 カラム名には下記が指定されています。
  OpenableColumns.DISPLAY_NAME:ファイル名
  OpenableColumns.SIZE:ファイルサイズ

FileProvider.delete()
 指定の共有コンテンツを削除します。

FileProvider.openFileDescriptor()
 ファイルディスクリプタを取得できます。

updateやinsertのメソッドはデフォルトでサポートされないため、
UnsupportedOperationExceptionがスローされます。

参考リンク:

以上です。