Android
Kotlin

AsyncTaskとAsyncTaskLoaderのテスト

Androidで非同期処理を行うときに、今まではAsyncTaskを使ってました。でも処理後にUIに反映させたりコールバックをするときになんか面倒だなと考えながらやってたらAsyncTaskLoaderっていうのもあるっぽい。とりあえず両方のテストを兼ねてHttp通信を非同期で行った時のコードを備忘録として残しておきます。
それぞれの詳しい解説は他の方の素晴らしい解説記事などを参考にしてください。

今回のコード
https://github.com/raibito/AndroidCodeBowl/tree/master/Async

AsyncTask

言わずと知れたAndroidの非同期処理。今までは非同期するときにはこれやろって感じで使ってた。

HttpAsyncTask.kt
class HttpAsyncTask(private val textView: TextView) : AsyncTask<Void,Void,String>() {

    override fun onPreExecute() { // 前処理
        super.onPreExecute()
    }

    // ここが非同期で行いたい処理
    override fun doInBackground(vararg p0: Void?) : String {
        val strUrl = "URLの文字列"
        val url = URL(strUrl)
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "GET"
        connection.connect()
        val reader = BufferedReader(InputStreamReader(connection.inputStream))
        val stringBuilder = StringBuilder()
        for (line in reader.readLines()){
            line.let { stringBuilder.append(line) }
        }
        reader.close()
        return stringBuilder.toString()
    }

    override fun onPostExecute(result: String?) { // 後処理
        super.onPostExecute(result)
        textView.text = result
    }
}

コンストラクタにTextViewとかを投げるとWarningが出るんですが簡単のため今回はこれで良しとします。言いたいのはdoInBackgroundで非同期で行いたい処理を書くことと、UI反映どうすんのってことです。ちゃんとやるときはコールバックしてって感じだと思うんですが。

AsyncTaskLoader

ActivityやFragmentのライフサイクルと無関係に処理させたい場合はこれらしい。動作にUIスレッドを要求しないことがAsyncTaskとの違いだそうです。
初めてだったので以下の記事を参考にさせて頂きました。
正しいAsyncTaskLoaderの使い方
流れとしてはとりあえず抽象クラスで非同期で実際にしたい処理以外を書いちゃって、という感じでした。

AbstractAsyncTaskLoader.kt
abstract class AbstractAsyncTaskLoader<D>(context: Context) : AsyncTaskLoader<D>(context) {

    private var result : D? = null
    private var isStart : Boolean = false

    override fun onStartLoading() {
        if (result != null){
            deliverResult(result)
            return
        }
        if (!isStart || takeContentChanged()) { // forceLoadが複数回呼ばれないようにする
            forceLoad()
        }
    }

    override fun onForceLoad() {
        super.onForceLoad()
        isStart = true
    }

    override fun deliverResult(data: D?) {
        result = data
        super.deliverResult(data)
    }

}

deliverResultは結果の返却を行うらしい。
ここからは実際の処理を書くクラス

HttpAsyncTaskLoader.kt
class HttpAsyncTaskLoader(context: Context) : AbstractAsyncTaskLoader<String>(context) {

    // ここに非同期処理
    override fun loadInBackground(): String {
        val strUrl = "URLの文字列"
        val url = URL(strUrl)
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "GET"
        connection.connect()
        val reader = BufferedReader(InputStreamReader(connection.inputStream))
        val stringBuilder = StringBuilder()
        for (line in reader.readLines()) {
            line.let { stringBuilder.append(line) }
        }
        reader.close()
        return stringBuilder.toString()
    }
}

それぞれを動かすActivity

上記を実際に動かすActivityは以下の通りです。AsyncTaskはインスタンス作ってexcute()、AsyncTaskLoaderはinitLoader(id, bundle, callback)をする。
AsyncTaskLoaderにはUI反映とかさせたい場合のコールバックが用意されているのでViewをそのまま投げなくて良い。(AsyncTaskでもコールバック作ればいいんだけど)
あとは、AsyncTaskLoaderのimportには気をつけましょう。私はそこで謎の時間を使いました。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private var loaderResult: String = "NoData"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val atlButton = findViewById<Button>(R.id.atlButton) // AsyncTaskLoader
        val atButton = findViewById<Button>(R.id.atButton) // AsyncTask
        val textView = findViewById<TextView>(R.id.textView)

        // AsyncTaskLoaderの起動
        atlButton.setOnClickListener {
            if (loaderResult == "NoData"){
                val bundle = Bundle()
                bundle.putString("TEST", "Default文字列")
                supportLoaderManager.initLoader(1, bundle, callback)
            } else {
                textView.text = loaderResult
            }
        }

        // AsyncTaskの起動
        atButton.setOnClickListener {
            HttpAsyncTask(textView).execute()
        }


    }

    // Loaderのコールバック これでViewを別クラスに投げなくて済む
    private val callback : LoaderManager.LoaderCallbacks<String> = object : LoaderManager.LoaderCallbacks<String> {

        override fun onCreateLoader(id: Int, args: Bundle?) : Loader<String> {
            val param = args?.getString("TEST") // 今回は使わない URLをUIから指定とかしたいときは使う
            return HttpAsyncTaskLoader(this@MainActivity) // Loaderがv4なのでAsyncTaskLoaderもv4
        }

        override fun onLoadFinished(loader: Loader<String>?, data: String?) {
            supportLoaderManager.destroyLoader(loader?.id ?: 1)
            loaderResult = data.toString() // dataにloadInBackgroundのreturn値が入っている
            textView.text = loaderResult
        }

        override fun onLoaderReset(loader: Loader<String>?) {
            // 今回は何もしない
        }


    }
}

まとめ

AsyncTaskとAsyncTaskLoaderのテストをHttp通信を例題にやった。
結局どういうときにどっちを使うとかは要勉強。
まだまだKotlinで書いてある記事が少なかったので備忘録として残した(コードが良いかは不明)。
個人的にAsyncTaskLoaderはcallbackの設定とかしやすくてコールバックしたいときには書きやすかった(コナミ)。

参考

正しいAsyncTaskLoaderの使い方
https://qiita.com/akkuma/items/a1252498e95c1f68316c

Androidの非同期処理の話
https://qiita.com/glayash/items/60546794f83fd7a0f271