Android端末内の「画像ファイルを含むフォルダ」を表示する際にキャッシュを利用して高速化する

# 背景
画像ギャラリーアプリなどのフォルダ一覧表示機能は端末内の「画像ファイルを含むフォルダ」を検知し表示している
自身で作成してみたが [（前回の記事）](https://qiita.com/tf_2018/items/19b5da43b63c1f885a4e)、しかしフォルダ探査に時間がかかりアプリ起動からファイル一覧表示まで時間がかかる
そこで「画像ファイルを含むフォルダ」のリストをキャッシュし表示を高速化する
# やったこと・フロー図
前回は、アプリ起動→端末内の画像を含むフォルダ探査→リストビュー表示としていた
しかし、これだとフォルダ探査に時間がかかりフォルダリストを画面にスムーズに表示できないので以下のフロー図のようにキャッシュを作成することでアプリ起動2回目以降での表示を高速化
![android_app_cache.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296022/a503fc9d-2596-a0f5-c9f5-6655a8379ed4.png)

#コード(全体の流れ)
キャッシュありの場合は「キャッシュ読み込み→リストビュー表示」と「キャッシュの更新」を行う、キャッシュ更新時のフォルダ探索に時間がかかるため更新はルーチンで非同期処理する
キャッシュなしの場合は「フォルダ探索→キャッシュ書き込み→キャッシュ読み込み→リストビューに表示」を行う

※　File()の第一引数に**cacheDir**を指定するとアプリ内のキャッシュディレクトリ
を指定できる、第二引数にはキャッシュ記録用のファイル名(なんでもいい)を指定、**cacheDirはMainActivityでしか使えないので注意（つまりキャッシュファイルオブジェクトはMainActivity内でしか取得できない）**
キャッシュファイルの操作を別クラス（Cachefile）として実装したかったのでMainActivity内でキャッシュファイルのオブジェクトを取得してからCachefileのメソッドに渡す事にした

```ruby:MainActivity.kt
        /*キャッシュ用の配列*/
        var memolist:ArrayList<String>

        /*キャッシュファイルのパス*/
        val cache_path = File(cacheDir, "dirurlcache00.txt")

        /*キャッシュファイル読み込み*/
        memolist=Cachefile().cacheread(cache_path)


        if(!memolist.isEmpty()) {/*キャッシュファイル中身が存在する時*/
            println("キャッシュファイルあり")
            
            /*画面出力*/
            displey(memolist)
            
            /*非同期処理*/
            GlobalScope.launch { 
                if (Cachefile().cacheupdate(memolist , cache_path)) {
                } else { println("キャッシュ更新なし") }
              }
            }
        else{/*キャッシュなしの時*/
            println("キャッシュファイルなし")

            /*キャッシュ書き込み処理*/
            Cachefile().cachewrite(cache_path)
            
            /*キャッシュ再読み込み*/
            memolist=Cachefile().cacheread(cache_path)
            
            /*画面出力*/
            displey(memolist)
        }

```
#キャッシュファイルクラス:（class Cachefile）
下記の４つのメソッドを作成した。

search_common():共通処理(フォルダ探査)
cachewrite():キャッシュファイル作成
cacheread():キャッシュファイル読み込み
cacheupdate():キャッシュファイル更新

## 共通処理(フォルダ探索):search_common()
キャッシュファイル作成更新する上で２つ以上のメソッドで重複した処理を一つのメソッドにまとめた。
今回はcachewrite()とcacheupdate()のフォルダ探索処理が重複していたので一つにまとめた

ImgFileconは[前回の記事](https://qiita.com/tf_2018/items/19b5da43b63c1f885a4e)で作成

```ruby:Cachefile.kt

      /*共通処理（フォルダ探査など）*/
      fun search_common():ImgFilecon{

          /*フォルダ探索インスタンス生成*/
          val imgserch = ImgFilecon()

          /*内部ストレージパス取得*/
          val in_storage_path=imgserch.in_storagepath_serch()

          /*フォルダ探索開始*/
          imgserch.extract(in_storage_path)

          return imgserch
      }
```
 
## キャッシュファイル作成:cachewrite()
共通処理（フォルダ探索）の結果をImgFileconオブジェクトから取得
ImgFileconは[前回の記事](https://qiita.com/tf_2018/items/19b5da43b63c1f885a4e)で作成
・dirurllist()は画像を含むフォルダのリストを返す

```ruby:Cachefile.kt

  /*キャッシュファイル作成メソッド*/
    fun cachewrite(filew:File) {

        /*共通処理(フォルダ探索処理)*/
        val imgserch=search_common()

        try{
            filew.createNewFile()
            if(filew.exists()){
                val src =filew.absoluteFile
                imgserch.dirurllist().forEach { src.appendText(it.imgurl+"\n") }
                println("書き込み完了")
            }
        }catch (e:IOException){
            println("キャッシュファイル書き込みエラー")
        }
    }
```
## キャッシュファイル読み込み:cacheread()
cacheread()はキャッシュがあればキャッシュリストを返す

```ruby:Cachefile.kt

    /*キャッシュファイル読み込みメソッド*/
    fun cacheread(filer:File): ArrayList<String> {

        /*
        readlist:キャッシュデータ格納変数
        filer:キャッシュファイルオブジェクト
        */

        /*戻り値：キャッシュデータのリスト*/
        val readlist: ArrayList<String> = ArrayList<String>()

        try {/*キャッシュファイル読み込み*/
            if (filer.exists()) {
                /*ファイルから一行ずつ読み込んで空白でなくNuLLでない文字列をlistに*/
                val lines=filer.absoluteFile.readLines().filter(String::isNotBlank).toList()
                lines.forEach {readlist.add(it)}
                println("読み込み完了")
            }
            return readlist
        } catch (e: IOException) {
            println("キャッシュファイル読み込み失敗")
            return readlist
        }
    }

```
## キャッシュファイル更新:cacheupdate()
キャッシュを読み込むだけでは端末内のフォルダが更新された時に整合性が取れないのでキャッシュの更新処理を行う
cacheupdate()は旧キャッシュリストとキャッシュファイルオブジェクトを引数として受け取り、最新キャッシュリストと旧キャッシュリストが違う時にキャッシュファイルを更新してtrueを返す

```ruby:Cachefile.kt
    /*キャッシュファイルの更新判定と更新を行うメソッド*/
    fun cacheupdate(cachelist:ArrayList<String>,fileup:File):Boolean{

        /*共通処理(フィルダ探索)*/
        val imgserch=search_common()

        /*最新キャッシュ用のリスト*/
        var updatelist:ArrayList<String> =ArrayList<String>()

        /*フォルダ探索結果をupdate用リストにadd（最新のキャッシュリストの作成）*/
        imgserch.dirurllist().forEach { updatelist.add(it.imgurl) }

        /*新旧のキャッシュを比較
        updatelist：新キャッシュリスト
        cachelist:旧キャッシュリスト*/
        if(cachelist.containsAll(updatelist) && updatelist.containsAll(cachelist)){return false}
        else{
            try {
                /*キャッシュファイルを削除*/
                fileup.delete()
                
                /*新しいキャッシュファイル作成*/
                fileup.createNewFile()

                /*新しいキャッシュファイルに書き込み*/
                if(fileup.exists()){
                    val src =fileup.absoluteFile
                    updatelist.forEach { src.appendText(it+"\n")
                        println("ファイルに書き込む内容"+it)
                    }
                    println("書き込み完了")
                    return true
                }
            }
            catch (e:IOException){println("キャッシュ更新エラー")}
        }
        return false
    }

```
# デモ
画像を含むフォルダ一覧を検知してフォルダ内の最新の画像ファイルのURLを表示、詳細を以下の画像に示す
<img width="300" alt="アプリスクショ" src="https://qiita-image-store.s3.amazonaws.com/0/296022/ef132c75-d4fd-1e43-aef8-383ebd696fbc.png">
# 結果
キャッシュを用いることでキャッシュなしの場合と比較して表示速度が3倍程度早くなった

#参考文献
[kotlin公式リファレンス](http://dogwood008.github.io/kotlin-web-site-ja/docs/reference/)
[Kotlinのコーディングが捗る標準ライブラリ](https://qiita.com/droibit/items/75416c0955b797931bb8)
[kotlinでandroid入門 内部ストレージ](https://qiita.com/folivora/items/430ee784a5a28f1cd776)