【Kotlin/Android Studio】Handlerの使い方!遅延や周期で処理を繰り返す方法
この記事からわかること
- Android Studio/KotlinのHandlerの使い方
- MessageQueueやRunnable、Looperの役割と違い
- カウントアップタイマーの実装方法
- 遅延処理:postDelayedメソッド
- スレッド操作
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
Handlerとは?
AndroidのHandler
とはスレッドと非同期で実行される処理に関連するタスクを管理するためのクラスです。Handler
を使用することでタイマーの設定や非同期的なタスクの処理、UIに関する作業をサブスレッドで行いたい場合などさまざまな機能を実現することができます。
Handler
を理解するためにはMessageQueue
やRunnable
オブジェクト、Looper
の存在が重要になってきます。
- Handler:スレッド間通信や非同期タスクを管理する役割
- MessageQueue:キュー構造でタスクを管理している場所
- Runnable:実行されるタスクの単位
- Looper:スレッド内でMessageQueueを管理し処理を実行する役割
MessageQueueとは?
MessageQueueはHandler
やLooper
などで使用される機能でアプリケーション内のスレッド間の通信を管理する役割を持っています。MessageQueueはスレッド1つに対して1つ関連づけられており、そのスレッドで処理するタスクを順番に処理していくための構造を持っています。Looper.myQueue()
で現在のスレッドのMessageQueueを取得することができます。
// 現在のスレッドのMessageQueueを取得する
Looper.myQueue()
Handler
がMessageQueueにタスクを追加し、Looper
がタスクを取り出し処理を実行していきます。管理されるタスクはメッセージと呼ばれる通信を目的としたタスクとRunnableオブジェクトのような具体的な処理を目的としたタスクの2種類が管理されています。
MessageQueueとは?〜まとめ〜
- キュー構造
- 複数のスレッドでタスクが混ざらないように管理する仕組み
- Handlerがタスクを追加
- Looperがタスクを取り出し処理を実行
- 扱うタスクの種類はメッセージとRunnableオブジェクトなどの具体的なタスク
Runnableオブジェクトとは?
public interface Runnable
RunnableオブジェクトとはMessageQueueに格納されるタスクの種類の1つです。このオブジェクトが実行すべきコード(処理)を保持しています。保持している場所はrun
メソッド中であり、このメソッドは引数なしかつ返り値のない(Void)のメソッドで定義されています。
public abstract void run ()
Runnableオブジェクト?〜まとめ〜
- タスクの種類の1つ
- 実行すべき具体的なコード(処理)を保持
- Runnableインターフェースとして提供
- 引数なしかつ返り値のない(Void)のrunメソッドのみ定義
Looperとは?
Looperは自身が管理するMessageQueue内のタスクを順番に処理していく役割を持っています。スレッド1つにLooper1つが関連づいているため、マルチスレッド処理を行うためには欠かせない仕組みになります。
例えばUI(メイン)スレッドのLooperを取得したい場合はgetMainLooper
メソッドを使用します。
// UI(メイン)スレッドのLooperを取得
val uiThreadLooper = Looper.getMainLooper()
Looperとは?〜まとめ〜
- スレッド1つに1つのLopperが関連づいている
- MessageQueueを保持している
- タスクを順番に処理していく役割
Handlerのインスタンス化
Handler
をインスタンス化する時は引数に対象のLooperを指定して両者を関連づけます。Looperを渡さずにHandler()
でインスタンス化することも可能ですが、公式から非推奨になっているようで、クラッシュを引き起こす原因になるようです。
// LooperとHandlerを関連付ける
uiThreadHandler = Handler(uiThreadLooper)
遅延処理:postDelayed
postDelayed
メソッドを使用することで処理を任意のミリ秒数遅延させることができます。
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
// 遅延実行する処理
}, 1000) // ミリ秒単位で遅延させる場合
ActivityやFragmentで遅延処理を実行し中でViewを操作する時などはviewLifecycleOwner
からCoroutines
で操作するとライフサイクルに依存したスコープになるので安全です。
viewLifecycleOwner.lifecycleScope.launch {
delay(1000)
// 遅延実行する処理
}
遅延処理を実装する方法は他にはTimer
などもあります。
カウントアップタイマーを実装してみる
最低限の使い方がわかった上でHandler
やLooper
を使用してカウントアップタイマーを実装してみたいと思います。とりあえず全体のコードは以下のような感じになりました。uiThreadHandler(Handlerオブジェクト)
からいろいろなメソッドを呼び出してタスクを操作しているのがわかると思います。
class MainActivity : AppCompatActivity() {
private var count = 0
private lateinit var uiThreadHandler: Handler
private lateinit var timerRunnable: Runnable
private lateinit var resultLabel: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
resultLabel = findViewById(R.id.result_label)
// UIスレッドのLooperを取得
val uiThreadLooper = Looper.getMainLooper()
// LooperとHandlerを関連付ける
uiThreadHandler = Handler(uiThreadLooper)
// タイマー用のRunnableを初期化
timerRunnable = object : Runnable {
override fun run() {
// カウントをアップしてUIを更新
count++
uiThreadHandler.post {
updateUI("Count: $count")
}
// 1000ミリ秒(1秒)ごとに再度このRunnableを呼び出す
uiThreadHandler.postDelayed(this, 1000)
}
}
// タイマーを開始
startTimer()
}
private fun updateUI(message: String) {
// UIを更新する処理を書く
resultLabel.text = message
}
private fun startTimer() {
// 最初の呼び出しを0ミリ秒後に行うように設定
uiThreadHandler.postDelayed(timerRunnable, 0)
}
override fun onDestroy() {
super.onDestroy()
// アクティビティが破棄される際にタイマーを停止
uiThreadHandler.removeCallbacks(timerRunnable)
}
}
キューにRunnableオブジェクトを送信
キューにRunnableオブジェクトを送信するにはpost
メソッドを使用します。今回はキューの中にtimerRunnable
オブジェクトが追加され、実行された中でUIを更新する処理をさらにキューへ追加する際に使用しています。
uiThreadHandler.post {
updateUI("Count: $count")
}
遅延して実行させるタスクをキューへ送信
キューに追加したタスクを遅延実行させるためにはpostDelayed
メソッドを使用します。1つ目の引数に対象のタスクを、2つ目の引数に遅延させたいミリ秒数を指定します。
// 1000ミリ秒(1秒)ごとに再度このRunnableを呼び出す
uiThreadHandler.postDelayed(this, 1000)
今回はRunnableオブジェクトの処理の最後で再度自身を1秒後に実行されるようにキューに送信することでループして処理を実行させています。
キュー内のRunnableをすべて削除
キュー内のRunnableをすべて削除させるためにはremoveCallbacks
メソッドを使用します。タイマーが不要になるタイミングやアクティビティが破棄されるタイミングで明示的に削除しておきます。。
// メッセージキュー内のRunnableをすべて削除
uiThreadHandler.removeCallbacks(timerRunnable)
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。