アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

Bitmapから暗黙的Intentでのシェアまで

Android Q時点のコードです。

Bitmap->Uri

class ImageRepository(
    private val application: Application
) {
    private val IMAGE_PATH = "image"
    private val imagePath = application.getFileStreamPath(IMAGE_PATH).apply {
        // シェアのために一時的に保存するだけなので毎回消す
        deleteRecursively()
        mkdir()
    }

    // bitmapは適宜recycle()する
    suspend fun writeBitmap(fileNameWithoutExtension: String, bitmap: Bitmap): Uri = withContext(Dispatchers.IO) {
        // 重要: 画像拡張子は設定する(パーミッションエラーになる)
        val outFile = File(imagePath, "${fileNameWithoutExtension}.png").also { outFile ->
            FileOutputStream(outFile).use {
                it.write(bitmap.toByteArray())
            }
        }

        return@withContext FileProvider.getUriForFile(application, application.packageName, outFile)
    }

    private fun Bitmap.toByteArray(): ByteArray {
        return ByteArrayOutputStream().use {
            compress(Bitmap.CompressFormat.PNG, 100, it)
            it.toByteArray()
        }
    }
}

権限の宣言

追加するuses-permission

無し

AndroidManifest.xml

providerの部分が重要

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="your.app.package.name">

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="your.app.package.name"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>
</manifest>

provider_paths.xml

ImageRepository.IMAGE_PATHで設定したものと同じ物を設定する。
これで外部アプリが見れるディレクトリを制限できる。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="external_files"
        path="image" />
</paths>

暗黙的Intentで共有

provider_paths.xmlで外部からファイルを見られるようになりましたが、これだけではどんなアプリからでも見られてセキュリティ的によろしくないのでIntent.flagsに見られる権限を付与する事でセキュリティの問題が解決されています。

class Hoge(
    imageRepository : ImageRepository
) {
    suspend fun share(text: String) = withContext(Dispatchers.Main) {
        val bitmapUri = bitmap
            ?.let { imageRepository.writeBitmap("${System.nanoTime()}", it) }

        val intent = Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
            putExtra(Intent.EXTRA_TEXT, text)
            bitmapUri?.let { putExtra(Intent.EXTRA_STREAM, it) }
            type = "image/*"
        }, "シェア")

        // 後はこのIntentをstartActivity()の引数に与えるだけ
    }
}