Android内部ストレージのファイルを別アプリで開かせるためのFileProvider

Androidアプリで適当な内部ストレージのディレクトリに出力ファイルなどを保存したとする。そのファイルをインテント飛ばして適当なアプリで開かせる場合の最小限のコードを紹介、まとめ。

具体的にはFileProviderというContentProviderの枠組みのひとつを使ってファイル共有という形でファイルを別アプリに渡す。

ここでいう内部ストレージはユーザとして使う時の内部ストレージで、リファレンスなどではExternal Storageといわれているもの。Internal Storageの方はアプリ固有のデータ領域と表現する。まあわかるだろうか。このあたりユーザに向けて使うtermと開発者向けtermの訳語にずれあり。

なぜFileProviderなのか

そもそも内部ストレージのファイルを開くにはREAD_EXTERNAL_STORAGEパーミッションかWRITE_EXTERNAL_STOREAGEパーミッションが必要。自分のアプリがその権限を持っていてもそのファイルを開くアプリがその権限を持っているとは限らないのでこういう回りくどいやり方が必要。同じ方法でアプリのデータ領域のファイルを開かせることもできるが、この場合も本来は別アプリが自分のアプリのデータ領域にアクセスすることはできないのでこの仕組みの必要性がわかると思う。このケースの場合、他の方法もあるにはあるがdepricatedである。

最短実装コードスニペット

まずはアプリのbuild.gradleにライブラリを追加する。他のandroidx系のライブラリが追加されていれば依存関係で勝手に読み込まれているので無しでやってみてもいい。例によってdependenciesの他の行は略。

dependencies {
    implements 'androidx.core:core:1.0.0-rc01'
}

次にXMLリソースres/xml/filepaths.xmlをリソースに追加して以下を記入する。次にあげるAndroidManifest.xmlにタグ追加するときに使う。多分name属性は何でもいい。UriをtoStringしたときなどに接頭辞としてつけれられる文字列になるような感じだったと思う。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <external-path name="external_storage_directory" path="." />
</resources>

次にAndroidManifest.xmlのapplication要素の中に次のタグを追加する。

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:grantUriPermissions="true"
    android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths"/>
</provider>

最後、アプリの適当な場所でインテントを飛ばす。内部ストレージの何かしらのファイルを示すFileオブジェクトfileが得られているとして、次のコードでインテントを飛ばせる。importは省略、適当に補ってくださいと。

Uri fileUri;
try {
    fileUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName()+".fileprovider", file);
} catch(IllegalArgumentException e) {
    fileUri = null;
}

if(fileUri != null) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(fileUri, getContext().getContentResolver().getType(fileUri));
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    if(intent.resolveActivity(getContext().getPackageManager()) != null) {
    startActivity(intent);
}

以上が最小限のコードでした。これで現時点では動きます。YEAH