Androidでカメラアプリとかから画像取得

なんかやたらと intent.extras.get("data") で画像が取れるってブログが引っかかるので
ちゃんと撮影した画像のサイズのままのBitmapとFileが取れる方法をメモっとこうかと思います。

 

まずこんな関数を用意します

/**
 * @param ctx 適当なコンテキスト
 * @param directory ディレクトリの名前 例 "hoge/fuga"
 * @param fileName 拡張子抜きのファイルの名前 例 "piyo"
 * @param mimeType mimeType 例 "image/png"
 * @return 確保したUriとそのパスのPair
 */
fun reserveContentUri(ctx: Context,
                      directory: String,
                      fileName: String,
                      mimeType: String): Pair<Uri, String> {

    val extension = MimeTypeMap.getSingleton()
            .getExtensionFromMimeType(mimeType)//mimeTypeから拡張子を得る
    val name = fileName + ".$extension"
    val directoryNames = directory.split("/")
    var directoryPath = Environment.getExternalStorageDirectory().absolutePath
    //ディレクトリを作るのと存在確認
    directoryNames.forEach { directoryName ->
        directoryPath += "/$directoryName"
        val file = File(directoryPath)
        if (!file.exists() && !file.mkdir()) throw IOException("failed directory creation")
    }
    
    //contentResolverにinsert
    val path = "${File(directoryPath).absolutePath}/$name"
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.TITLE, name)
        put(MediaStore.Images.Media.DISPLAY_NAME, name)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
        put(MediaStore.Images.Media.DATA, path)
    }
    return ctx.contentResolver
            .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) to path
}


上記のコードだと問題がありました。
これ、カメラアプリの起動キャンセルしたり失敗したるすると余計なものがcontentResolverにinsertされちゃいます。

下記に修正版を

fun createSaveUri(directory: String, fileName: String, mimeType: String): Pair<Uri, String> {
    val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
    val name = fileName + ".$extension"
    val directoryNames = directory.split("/")
    var directoryPath = Environment.getExternalStorageDirectory().absolutePath
    directoryNames.forEach { directoryName ->
        directoryPath += "/$directoryName"
        val file = File(directoryPath)
        if (!file.exists() && !file.mkdir()) throw IOException("directory creation failure")
    }
    val path = "${File(directoryPath).absolutePath}/$name"
    return Uri.fromFile(File(path)) to path
}

この関数で保存先を確保しておきます。

そしたらAndroidManifest.xmlには

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

のpermissionを追加します。

そしたらこんな感じで使う

class HogeActivity : AppCompatActivity() {

    private var mPath: String = ""
    private var mUri: Uri = Uri.EMPTY

    companion object {
        private val REQUEST_CODE_CAPTCHA = 100
        private val SAVED_LATEST_CAPTCHA_IMAGE_PATH = "SAVED_LATEST_CAPTCHA_IMAGE_PATH"
        private val SAVED_LATEST_CAPTCHA_IMAGE_URI = "SAVED_LATEST_CAPTCHA_IMAGE_URI"
    }

    override fun onSaveInstanceState(outState: Bundle) {
        outState.putString(SAVED_LATEST_CAPTCHA_IMAGE_PATH, mPath)
        outState.putParcelable(SAVED_LATEST_CAPTCHA_IMAGE_URI, mUri)
        super.onSaveInstanceState(outState)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        mPath = savedInstanceState.getString(SAVED_LATEST_CAPTCHA_IMAGE_PATH)
        mUri = savedInstanceState.getParcelable(SAVED_LATEST_CAPTCHA_IMAGE_URI)
    }

    //このメソッドを適当に呼ぶ
    private fun executeCaptchaApp() {
        //val (uri, path) = reserveContentUri(ctx, "numetter/captcha", "${System.currentTimeMillis()}", "image/jpeg")
        val (uri, path) = createSaveUri("numetter/captcha", "${System.currentTimeMillis()}", "image/jpeg")
        mPath = path
        mUri = uri
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            putExtra(MediaStore.EXTRA_OUTPUT, uri)
        }
        startActivityForResult(intent, REQUEST_CODE_CAPTCHA)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != Activity.RESULT_OK) return
        when (requestCode) {
            REQUEST_CODE_CAPTCHA -> {
                val bitmap = BitmapFactory.decodeFile(mPath)//撮影した画像のBitmap
                val imageFile = File(mPath)//撮影した画像のFile
                val imageView = findViewById<ImageView>(R.id.image)
                imageView.setImageURI(mUri)//Uriを元に画像をセット
            }
        }
    }
}

取得された画像のサイズとか考慮してなかったり、パーミションのチェックと要求をしていなかったり雑ではありますが 大方こんな感じのコードでカメラアプリ等で撮影された画像を取得できます。

AndroidN以降はFileProviderを使用してFileからUriを取得する必要があります。