Android端末のDCIMに保存してギャラリーで表示させる方法

# 検証環境
この記事の内容は、以下の環境で検証しました。

* Java:open jdk 1.8.0_152
* Kotlin 1.2.0
* Android Studio 3.0.2
* CompileSdkVersion:26
* MinSdkVersion:21
* TargetSdkVersion:26
* BuildToolsVersion:26.0.2
* gradle:4.0.0

# 目標
本記事では、以下の2点を目標としています。
* アプリ内で画像をDCIMに保存する
* 保存した画像をギャラリーに表示させる

# アプリの動き
この記事で作成するアプリは、下記の仕様になっています。

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/99960/b99be443-81cb-09b0-c325-ba7bd01e695c.png)


# ファイル構成
この記事で作成するアプリは下記のファイルで構成しています。

|ファイル名|説明|
|:---:|:--|
|AndroidManifest.xml|マニフェストファイル|
|layout/activity_main.xml|画面のレイアウト|
|jp.co.casareal.filetodcim.MainActivity|ファイルの保存処理が記載されているActivity|
|drawable/pic.png|サンプル画像|

# 画像をDCIMに保存する
## AndroidManifest.xml
### コード
DCIMは外部ストレージの扱いになるため、外部ストレージへのパーミッションを追記します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.casareal.filetodcim">
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

## activity_main.xml
### コード
保存するためのボタンと、保存する画像のサンプルを表示するImageViewを配置しています。

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="下記の画像をDCIMに保存" />

    <ImageView
        android:src="@drawable/pic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
```

## jp.co.casareal.filetodcim.MainActivity
### コード
本記事のコードでは、下記の処理を実装しています。

* 外部ストレージへの書き込み権限を取得するために、ダイアログを表示しています。
* 外部ストレージのパスを取得し、Fileクラスを生成します。
* BitmapFactoryを利用してpic.jpegを読み込み、DCIMに保存します。
* ギャラリーからもアクセス出来るように、画像データとしてAndroidに登録します。

コードの全体像

```kotlin
package jp.co.casareal.filetodcim

import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileOutputStream

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()

        btn_save.setOnClickListener {
            if (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
                ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        1)
            } else {
                saveFile(createFile())
            }
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        // 絶対に許可される前提で記述
        saveFile(createFile())
    }

    private fun createFile(): File {
        val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)

        return File(dir, "pic.jpeg")
    }

    private fun saveFile(f: File) {

        val bit = BitmapFactory.decodeResource(resources, R.drawable.pic)

        val ops = FileOutputStream(f)

        bit.compress(Bitmap.CompressFormat.PNG, 100, ops)

        ops.close()

        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            put("_data", f.absolutePath)
        }

        contentResolver.insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

    }
}
```

外部ストレージへの書き込み権限を取得するためのダイアログ表示

```kotlin
 ActivityCompat.requestPermissions(this,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        1)
```

外部ストレージのパスを取得し、Fileクラスの生成

```kotlin
    private fun createFile(): File {
        val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)

        return File(dir, "pic.jpeg")
    }
```

BitmapFactoryを利用してpic.jpegを読み込み、DCIMに保存

```kotlin
        val bit = BitmapFactory.decodeResource(resources, R.drawable.pic)

        val ops = FileOutputStream(f)

        bit.compress(Bitmap.CompressFormat.PNG, 100, ops)

        ops.close()
```

ギャラリーからもアクセス出来るように、画像データとしてAndroidに登録

```kotlin
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            put("_data", f.absolutePath)
        }

        contentResolver.insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
```

もう一つ方法があります。ブロードキャストでOSに今一度スキャンをさせるという方法です。
ただし、アプリ内のフォルダに画像を保存した場合は、対象にならないので保存する場所に注意が必要です。

```kotlin
        val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
        val contentUri = Uri.fromFile(f)
        mediaScanIntent.data = contentUri
        this.sendBroadcast(mediaScanIntent)
```