くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Androidのスレッドについて

今回はAndroidのプロセスやスレッドについてまとめようと思います。

最近は非同期処理でCoroutine などが出てきたおかげで非同期処理が簡単にできるようになりました。

ただ、Coroutine などを理解する前にプロセスやスレッドについて理解する必要があると思い、簡単にまとめようと思います。

スレッドについて

Androidは、処理を行うためのベルトコンベア(スレッド)が1本しか存在しないシングルスレッドモデルです。

なので、重い処理が走った場合はアプリが止まってしまう可能性があります。

それを回避するために、一時的に別のベルトコンベア(サブスレッド)で処理が出来る仕組みが用意されています。

流れとしては、アプリの起動時にアプリ実行用のスレッドを作成します。

これは、メインスレッド(UI スレッド)と呼ばれていて、イベント(描画イベントを含む)を適切なユーザー インターフェースウィジェットに送信する役割を担うため非常に重要です。

また、これはほとんどの場合、アプリが Android UIツールキットのコンポーネント(ViewやButtonなど)とやり取りをするスレッドでもあります。

つまり、UI更新などやボタンのクリックなどUIの操作はUI スレッドで行う必要があり、重い処理(APIなど)を行う場合にはワーカースレッドで行う必要があります。

また、Android UI ツールキットはスレッドセーフではありません。

したがって、ワーカー スレッド(サブスレッド)からUIを操作するべきではありません。

ユーザー インターフェースに対するすべての操作は、UI スレッドから行う必要があります。そのため、Android のシングル スレッド モデルには下記のルールがあります。

  • UI スレッドをブロックしない
  • UI スレッド以外から Android UIツールキットにアクセスしない

下記がイメージです。ワーカースレッドはUIスレッドとは別の処理のコンベアのようなイメージです。

f:id:kumaskun:20210725195236p:plain

スレッドの作成

次はどうやってスレッドを作成するかについてです。

スレッドの作成については何パターンかあるので、説明したいと思います。

①handler

Handler は Android が提供するライブラリです。

別スレッドからアプリの UI を操作するのにLooperHandlerというしくみを使うことができます。

LooperはUIスレッドにあらかじめ用意されています。また、Looper は処理対象のリスト(キュー)を持っていて、そこに登録されたスレッドを順番に実行します。

Looperのリスト(キュー)への登録など、LooperとのやりとりはHandlerを使って行います。

f:id:kumaskun:20210725212019p:plain

実際のソースコードは下記です。

// Handlerのオブジェクト作成
val handler = Handler(Looper.getMainLooper())

// 別スレッドを作成
val runnable = object : Runnable {
    override fun run() {
        // 別スレッドで行う処理
    }
}

// 別スレッドを実行
handler.post(runnable)

Runnableは別スレッドを作成するインターフェイスで、run()のなかで別スレッドで行う処理を定義します。

そして、別スレッドで処理を行ったら、Handlerを介して、UIスレッドで処理を行います。

val handler = Handler(Looper.getMainLooper())となっているのはLooperはスレッドごとにあり、UIスレッドのLooperに属しているHandlerであることを明示しています。

また、別スレッドの結果をUIスレッドで更新するのは下記runOnUiThreadで行えます。

runOnUiThread {
    // UIスレッドで行う処理
}

②AsyncTask

Handlerとは別にAsyncTaskという非同期処理用クラスがあります。

使い方は下記のようになります。

class TestAsyncTask : AsyncTask<Void, Int, Void>() {

      // 別スレッドの処理を行う前にUIスレッドで処理を行う場合
        override fun onPreExecute() {
        }

      // 別スレッドの処理
        override fun doInBackground(vararg param: Void?): Void? {
        }


      // 別スレッドの処理が終わり、UIスレッドで行う場合
        override fun onPostExecute(result: Void?) {
        }

    }

上記のようなAsyncTaskクラスを作成して、インスタンス化して呼び出して、execute()を実行することで行えます。(TestAsyncTask().execute()など)

また、上記のAsyncTask<Void, Int, Void>のVoid, Int, Voidの部分はParamsProgressResultと定義されており、それぞれ下記を意味するので、必要に応じて変更します。

Params→別スレッドの処理に渡すパラメータ(doInBackgroundに渡される型)

Progress→進捗状況を更新する処理に渡される値の型

Result→別スレッドの処理の結果の返り値

AsyncTaskはAPI30からは非推奨となっているので、今後使うことはあまりないと思います。

2つのパターンを例に出しましたが、あまり使うことがないかもしれません。

例えば、APIの処理を行う場合にはokHttpやRetrofitなどはライブラリが勝手にワーカースレッドで処理を行うなどしてくれたり、RxjavaやCoroutineなどあまり意識せず、UIスレッドで処理を行ったりできるためです。

まとめ

AndroidではUIスレッドとワーカースレッドがあり、意識して実装しないと思いもよらない例外が発生してアプリがクラッシュすることがあります。

最近はライブラリが進歩してスレッドをあまり意識せずに開発ができるようになりました。

ただ、想定外のことが起きたときに概念を知っておくのと知らないのでは解決までのプロセスに差ができると思っているので、概念の理解の一助になると幸いです。

参照

developer.android.com

developer.android.com

engineer-club.jp

kommkett.co.jp