タブレット用プログラムの書き止め

android OS & iPadOS の記録。

【kotlin】 サムネイル表示で非同期処理を使いたい

2024-03-16 02:37:56 | Android studio 日記

サムネイル表示のフラグメントをJavaからKotlinへ変換中なんだけど、
Javaの時に思い付きでコードを組んでいたのでもう少しまともの形にしたい。

サムネイル画像を非同期で別々に単独で作成処理をしてキャッシュフォルダに保存する。
フラグメントが終了するとき、未完了の非同期処理を一斉にキャンセルさせる。

コルーチンの説明でキャンセルがあったからその辺を攻めてみる。

気が重い...。

 


Javaの時は
スレッドプールにスレッドを積んでいき、指定の制限数のスレッドを実行してきた。
実行場所もUIかバックかをいつも意識してデータのやり取りも注意が必要だった。

Kotlinのコルーチンは、
スコープでUIかバックかを記載できる。同一メソッドの中でOK?
ビューにアクセスするときはUI部分で記載するのは同じ。コルーチンでは"post()"を使用した。
コルーチンのキャンセルもフラグメントの消滅に合わせてフラグメント内のすべてにキャンセルが実行されるみたい。便利。


RecyclerViewで使うサムネイルを作成してキャッシュファイルに保存する。
システムからサムネイルを呼び出せばいいんだけれど、対象が古いAPIなので自分で作ってキャッシュに入れる。

サムネイルFragment 専用になるからインナークラスでも良いのかな?
とりあえず、やってみて対応しよう。

class MyFragment : Fragment() {
    /* 色々省略 * /

    inner class MyAdapter() : RecyclerView.Adapter() {
        /* 色々省略 * /

        private fun loadImage(position: Int): Bitmap? {
            /* 色々省略 * /

            /* 受け取ったポジションから絶対パスネームを取り出し、キャッシュファイルの絶対パスを調べる。 * /
            if (!cf.exists()) {
                /* 色々省略 * /

                /* キャッシュファイルが無い時はコルーチンへ * /
                runBlocking {
                    asyncTask(imageAbsPaths!!.get(position), recyclerView!!, position)
                }

                /* 一旦リソースの画像をセットしておく。 * /
                return BitmapFactory.decodeResource(
                    requireContext().getResources(),
                    R.drawable.img_file
                )
            }
            return BitmapFactory.decodeFile(cf.absolutePath) /* キャッシュファイルがあれば、Bitmapを返す * /
        }
        /* 色々省略 * /
    }

    private val parentJob = Job()  /* 一度に全部にキャンセルを通知する小細工 * /

    suspend fun asyncTask(path:String, rv: RecyclerView, vID: Int) {  /* 引数はアダプター内の値を受け取っている * /

        val saveSize = requireContext().getResources()
                .getDimension(R.dimen.thumbnail_save_size).toInt()  /* 画像の大きさをResに宣言している * /
        val souFile: File = File(path)
        if (!souFile.exists() || !souFile.isFile) {
            return
        }
        val cacheDir: File? = viewModel.getCacheDir_thumbnail()  /* キャッシュディレクトリを取得 * /
        if (cacheDir == null || !cacheDir.exists() && !cacheDir.mkdirs()) {
            return
        }
        val cacheFile = File(cacheDir, souFile.name)  /* キャッシュファイルのパスを作成 * /
        if (cacheFile.exists()) return  /* キャッシュファイルが存在してたら帰る。無いから来たんだけども * /

        runBlocking {  /* コルーチンへ * /
            val childJob = lifecycleScope.async(parentJob) {  /* 返り値を受け取りたいのでasync() * /
                MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, saveSize)  /* 外部発注、キャッシュ保存 * /
            }

            val result: Int = childJob.await()

            when (result) {
                0 -> if (vID < rv.adapter!!.itemCount) {

                   /* 直接Viewにアクセスすると例外が発生するので更新はpost()を使用 * /
                    rv.post(Runnable { 
                        rv.adapter!!.notifyItemChanged(vID)  /* 保存完了後にポジションを更新させる * /
                    })
                }

                /* 色々省略 * /
            }
        }
    }

    override fun onDestroy() {
        parentJob.cancel()     /* 親をキャンセルすると子供全部にキャンセル通知してくれる * /
        super.onDestroy()
    }
}

lifecycleScopeなのでフラグメントが終了すれば、キャンセルになるのだけれど念のため。
ベースはできていたので、コルーチンの部分だけで数時間ほど検索&トライで完成できました。

自分の構造に合う書式説明が無かったので色々と組み合わせた後に例外を取り除く感じでした^^;
意外とあっさり動いた気がする。
内容もスッキリだし、Javaの AsyncTask()って何だったの準備とか手順が多すぎ。

次はディレクトリ選択フラグメントかな。