14
8

More than 1 year has passed since last update.

Android: 画像ファイルの共有は FileProvider と ShareCompat を使う

Last updated at Posted at 2021-09-25

Android で画像ファイルを他のアプリへ共有するには、ACTION_SEND Intent または ShareCompat を使います。

共有するファイルの Uri は FileProvider で生成します。

この記事では、FileProvider + ShareCompat での実装についてまとめています。

画像のシェアについては以下が公式のドキュメントとなります。

前提

AndroidX Core 1.5.0 以上を利用していること。

AndroidX Core 1.5.0-beta01 から、後述の Android 10 ShareSheet でのプレビュー問題が修正されています

実装: FileProvider の設定

画像ファイルを cacheDir へ保存し、Intent 発行時に一時的に外部のアプリに読み取り権限を与えられるようにします。

AndroidManixest.xml を設定します。${applicationId}もそのまま記述します。

app/src/main/AndroidManifest.xml

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

provider_path.xml を設置します。

app/src/main/res/xml/provider_path.xml

<paths>
    <cache-path name="cache" path="." />
</paths>

今回は cacheDir 以下へ共有画像ファイルを一時保存するため、cache-path タグを設定しています。FileProvider タグ一覧は以下のドキュメントを確認してください。

ShareCompat の実装

ShareCompat により画像ファイルを共有します。ShareCompat は内部的に ACTION_SEND Intent や ACTION_SEND_MULTIPLE Intent を用いてアプリ間データ共有を実現します。

サンプルコードでは、res/raw/xml/image.png が設置されている前提です。

class MyActivity: AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.bind(findViewById<ViewGroup>(android.R.id.content)[0])
        binding.button.setOnClickListener {
            val imageUri = resources.openRawResource(R.raw.image).use { input ->
                val file = File("$cacheDir/image.png")
                file.createNewFile()
                file.outputStream().use { output ->
                    input.copyTo(output)
                }
                FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
            }
            ShareCompat.IntentBuilder(this).apply {
                setChooserTitle("share title")
                setText("share message")
                setStream(imageUri)
                setType("image/png")
            }.startChooser()
        }
    }
//...

これを実行すると、以下のような表示となります。上部に大きく表示されている画像が共有しようとした画像のプレビュー、2段目が OS によってレコメンドされた共有相手のユーザー一覧、3段目が共有を受け取れるアプリ一覧です。

ShareSheet の表示は OS バージョンによって異なります。

OS バージョン ShareSheet シェア後の表示 (SMSアプリ)
Android 11 * 画像プレビューだけが表示されている
* ChooserTitle の表示がない
* Text の表示がない
(参考) Android 7 * ChooserTitle だけが表示されている

ShareCompat.IntentBuilder の設定項目

ShareCompat.IntentBuilder は以下の設定項目があります。

fun 説明
addEmailBcc() Intent.EXTRA_BCC
addEmailCc() Intent.EXTRA_CC
addEmailTo() Intent.EXTRA_EMAIL
setChooserTitle() Intent Chooser タイトル
setHtmlText() Intent.EXTRA_HTML_TEXT を設定し、
Intent.EXTRA_TEXT に Html.fromHtml() を適用した Spanned を設定する
setStream() Intent.EXTRA_STREAM
setSubject() Intent.EXTRA_SUBJECT
setText() Intent.EXTRA_TEXT
setType() Intent.type

ShareCompat を使わず、ACTION_SEND Intent を組み立てる実装

ShareCompat を使う実装がお勧めですが、ShareCompat を使わずに ACTION_SEND Intent を組み立てる実装は以下の通りとなります。

class MyActivity: AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.bind(findViewById<ViewGroup>(android.R.id.content)[0])
        binding.button.setOnClickListener {
            val imageUri = resources.openRawResource(R.raw.image).use { input ->
                val file = File("$cacheDir/image.png")
                file.createNewFile()
                file.outputStream().use { output ->
                    input.copyTo(output)
                }
                FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
            }
            startActivity(
                Intent.createChooser(
                    Intent(Intent.ACTION_SEND).apply {
                        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                        putExtra(Intent.EXTRA_TEXT, "share message")
                        putExtra(Intent.EXTRA_STREAM, imageUri)
                        clipData = ClipData.newRawUri(null, imageUri)
                        type = "image/png"
                    },
                    "share title"
                )
            )
        }
    }
//...

Android 10 以上では、ShareSheet 上で画像のプレビュー表示がありますが、EXTRA_STREAM に設定するだけでは以下のエラーが発生し、ShareSheet 上の画像プレビューが表示されません。

E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.exmaple.myapplication.fileprovider/cache/image.png from pid=8822, uid=1000 requires the provider be exported, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:820)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:684)
        at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:674)
        at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:548)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:327)
        at android.os.Binder.execTransactInternal(Binder.java:1154)
        at android.os.Binder.execTransact(Binder.java:1123)
E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.example.myapplication.fileprovider/cache/image.png from pid=8822, uid=1000 requires the provider be exported, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:820)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:684)
        at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:674)
        at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:548)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:327)
        at android.os.Binder.execTransactInternal(Binder.java:1154)
        at android.os.Binder.execTransact(Binder.java:1123)

このエラーを回避するために、FLAG_GRANT_READ_URI_PERMISSION と clipData を設定しています。

ShareCompat を使う場合は ShareCompat が上記処理をしてくれるため、Android 10 以上でも問題なく動作します。

14
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8