21
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

Organization

AsyncTaskと同等の機能をKotlinのCoroutine(コルーチン)で実装

検証環境

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

  • Java:open jdk 1.8.0_152
  • Android Studio 3.0
  • CompileSdkVersion:26
  • MinSdkVersion:21
  • TargetSdkVersion:26
  • BuildToolsVersion:26.0.2
  • gradle:3.0.0

AsyncTaskとは

AsyncTaskは、Androidで非同期処理の実装に便利なユーティリティクラスの1つです。
詳細は、下記のサイトを参照してください。
https://developer.android.com/reference/android/os/AsyncTask.html)

Coroutineとは

Coroutineとは、Kotlin 1.1から実験的に導入された非同期処理を簡単に実装できる機能です。
async/await/launchなどの関数利用して、非同期処理を実現します。
(本記事ではasync関数で非同期処理を実現します。)
関数の詳細については、下記のサイトを参照してください。
https://github.com/Kotlin/kotlinx.coroutines)
AsyncTaskと同等の機能をCoroutineで実装した時の差を確認するため、本記事でCoroutine詳細については省略します。
また、キャンセル処理は実装しておりません。

サンプルアプリ

完成イメージ

この記事で作成するサンプルアプリの完成イメージは、下図のとおりです。
01_アプリイメージ.png

サンプルアプリの詳細

スタートボタンを押下すると、TextViewの表示が「始めます」に変わります。その後、TextViewに1〜10の値が0.8秒間隔でカウントアップします。カウントアップが終わると、TextViewの表示は「終わります」に変わります。

ソースコードと解説

サンプルアプリは、以下のファイルで構成されています。
(プロジェクト生成後から変更がないマニフェストファイルなどは、省略しています。)

ファイル一覧

レイアウト

  • activity_main.xml

Activity

  • MainActivity.kt(AsyncTaskで実装)
  • MainActivity.kt(Coroutineで実装)

Gradle

  • build.gradle(app)

Gradle

AndroidでCoroutineを利用するため、build.gradle(app)のdependenciesに下記の1行を追記します。

 compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3"

レイアウト

最終的なレイアウトのイメージは、下図のとおりです。

02_レイアウト.png

activity_main.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="jp.co.casareal.afterasync.MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="30sp" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="スタート"
        android:textSize="30sp" />
</LinearLayout>

Activity

MainActivity.kt(AsyncTaskで実装)

AsyncTaskでサンプルアプリを実装した時のアクティビティのソースコードは、以下のとおりです。

MainActivity
package jp.co.casareal.asyncbefor

import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

    inner class MyAsyncTask : AsyncTask<Void, Int, Void>() {

        override fun onPreExecute() {
            text.setText("始めます")
            Thread.sleep(800)
        }

        override fun doInBackground(vararg param: Void?): Void? {
            for (i in 1..10) {
                publishProgress(i)
                Thread.sleep(800)
            }
            return null
        }

        override fun onProgressUpdate(vararg values: Int?) {
            text.setText(values[0].toString())
        }

        override fun onPostExecute(result: Void?) {
            text.setText("終わります")
        }

    }
}

実装の解説

AsyncTaskを継承したクラスを、インナークラスとして定義しています。
onPreExecuteメソッド、onProgressUpdateメソッド、onPostExecuteメソッドは、UIの変更できるmainスレッドで実行されます。その為、サンプルコードでは、TextViewのプロパティを変更しています。

MainActivity.kt(Coroutineで実装)

package jp.co.casareal.afterasync

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async

class MainActivity : AppCompatActivity() {

    var job: Deferred<Unit>? = null

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

        button.setOnClickListener {
            // async関数の戻り(Deferred型)を受け取る
            job = async {
                // myTaskメソッドの呼び出し
                myTask()
            }
        }
    }

    private suspend fun myTask() {
        // onPreExecuteと同等の処理
        async(UI) {
            text.text = "始めます"
        }

        // doInBackgroundメソッドとonProgressUpdateメソッドと
        // 同等の処理
        Thread.sleep(800)
        for (i in 1..10) {
            async(UI) {
                text.text = i.toString()
            }
            Thread.sleep(800)
        }

        // onPostExecuteメソッドと同等の処理
        async(UI) {
            text.text = "終わります"
        }

    }
}

実装の解説

非同期処理を実装するために、async関数を利用しています。async関数の定義は下記のサイトを参照してください。
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html)

サンプルコードでは、下記の2種類のasync関数の記述方法が出てきます。

async関数
import kotlinx.coroutines.experimental.async

async{・・・処理・・・}

上記の方法で記述すると、「mainスレッドではないスレッド」で処理が実行されます。その為、UIの変更は出来ません。
もし、UIの変更などを行う処理が実行されると、「CalledFromWrongThreadException」が発生します。例外が発生した場合、その子コルーチンの処理は終了しますが、親コルーチンの処理は継続します。

import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.android.UI

async(UI) {・・・処理・・・}

async関数の引数に処理を実行するスレッドを、指定できます。
上記の方法で記述すると、mainスレッドで処理が実行されます。その為、UIの変更などが行えます。

suspend修飾子

コルーチンから呼び出すメソッドや関数は、「suspend」修飾子を必ず付与しなければなりません。

private suspend fun myTask() {・・・処理・・・}

まとめ

AsyncTaskをCoroutineで実装するとコード量が減り、可読性も上がっているように感じます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
21
Help us understand the problem. What are the problem?