Handlerは、Androidから任意のタスクを処理するためにMessageを送信したり、Runnableを実行するように設計されたクラスです。
Executorは、Javaが提供するインタフェースで、Runnableを実行するように設計されました。
この二つは、RunnableのようなTaskを特定Threadで実行させる共通点があります。 一方、様々な違いもあります。
この記事では、HandlerとExecutorの違いについて調べました。
Androidサンプルコードは、鼻間違って作成され、FrameworkやJavaライブラリはJavaで参照しました。
Android Library, Java Library
Handlerは、Androidが提供するライブラリです。一方、ExecutorはJavaで提供するライブラリです。 Executorを使用すると、Androidの開発経験がないJava開発者がより理解しやすいことができます。
次は、Android Handlerクラスです。
一般的に、 sendMessage()
と post()
でTaskを実行します。
public class Handler {
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
....
}
次は、JavaのExecutorインタフェースです。 execute()
でTaskを実行します。
public interface Executor {
void execute(Runnable command);
}
Message転送、Runnable実行
Handlerは、Message配信とRunnableを実行することができます。一方、ExecutorはMessage転送機能がなく、Runnableのみ実行することができます。
Handlerは、次のように handleMessage()
を実装することができます。
class MyHandler : Handler() {
companion object {
const val TAG = "MyHandler"
const val MSG_DO_SOMETHING1 = 1
const val MSG_DO_SOMETHING2 = 2
}
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_DO_SOMETHING1 -> {
Log.d(TAG, "Do something1")
}
MSG_DO_SOMETHING2 -> {
Log.d(TAG, "Do something2")
}
}
}
}
次のようにMessageを送信HandlerのThreadから直接実行されるか、一定時間後に実行されるようにすることができます。
handler.sendEmptyMessage(MyHandler.MSG_DO_SOMETHING2)
handler.sendEmptyMessageDelayed(MyHandler.MSG_DO_SOMETHING3, 2000)
また、Handlerは、次のようにRunnableを実行することができます。
handler.post(object: Runnable {
override fun run() {
// do something
}
})
一方、Executorは、次のようにRunnableを実行させるだけすることができます。
val executor = this.getMainExecutor()
executor.execute(object: Runnable {
override fun run() {
// do something
}
})
ちなみに、匿名クラスを渡す代わりにLambdaまたはKotlinで提供される構文では、次のように簡単に実装できます。
executor.execute(Runnable {
// do something
});
handler.post {
// do something
}
P OSからContext.getMainExecutor()でMain Threadで動作するExecutorをインポートすることができます。
Single Thread, Multi Thread
Handlerは、Single ThreadでTaskが処理されます。
しかし、Executorは、クラスによってSingle Threadで処理されることもあり、Multi Threadで処理されることがあります。
例えば、Androidから次のように ThreadPoolExecutor
オブジェクトを生成することができます。このオブジェクトは、希望のサイズのThreadPoolを生成し、 execute()
に渡されるRunnableをThreadPoolで実行されます。つまり、Multi ThreadでRunnableが実行されます。
val workQueue = LinkedBlockingQueue<Runnable>()
val executor = ThreadPoolExecutor(5 /* corePoolSize */,
5, 50, TimeUnit.MILLISECONDS, workQueue)
executor.execute { Log.d("Test", "Adding task1") }
executor.execute { Log.d("Test", "Adding task2") }
executor.execute { Log.d("Test", "Adding task3") }
executor.execute { Log.d("Test", "Adding task4") }
executor.execute { Log.d("Test", "Adding task5") }
以下は、上記のコードの実行結果です。 TIDを見ると、他のすべてのThreadで実行されたことを見ることができます。 Single threadがないので実行順序も保証されません。
12-06 21:41:16.739 5358 6371 D Test : Adding task1
12-06 21:41:16.739 5358 6373 D Test : Adding task3
12-06 21:41:16.739 5358 6375 D Test : Adding task5
12-06 21:41:16.740 5358 6372 D Test : Adding task2
12-06 21:41:16.742 5358 6374 D Test : Adding task4
登録されたTask数
Handlerの場合、登録されたTaskがいくつあるかを知ることができません。
一方、いくつかのExecutorクラスはTask数のためのAPIを提供しています。
例えば、 ThreadPoolExecutor
はgetTaskCount()
と呼ばれるAPIでスケジュールされたTask数を返します。
val workQueue = LinkedBlockingQueue<Runnable>()
val executor = ThreadPoolExecutor(5, 5,
50, TimeUnit.MILLISECONDS, workQueue)
executor.execute { Log.d("Test", "Adding task1") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task2") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task3") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task4") }
Log.d("Test", "Task count: ${executor.taskCount}")
executor.execute { Log.d("Test", "Adding task5") }
Log.d("Test", "Task count: ${executor.taskCount}")
Output:
12-06 22:07:10.994 6063 6063 D Test : Task count: 1
12-06 22:07:10.994 6063 6063 D Test : Task count: 2
12-06 22:07:10.995 6063 6063 D Test : Task count: 3
12-06 22:07:10.995 6063 6063 D Test : Task count: 4
12-06 22:07:10.996 6063 6063 D Test : Task count: 5
12-06 22:07:11.040 6063 8556 D Test : Adding task2
12-06 22:07:11.040 6063 8555 D Test : Adding task1
12-06 22:07:11.041 6063 8558 D Test : Adding task4
12-06 22:07:11.041 6063 8557 D Test : Adding task3
12-06 22:07:11.041 6063 8559 D Test : Adding task5
まとめ
もし自分が実装することがExecutorだけで十分であれば、JavaライブラリであるExecutorを使用するのがよいようです。また、Executorを使用すると、現在はSingle ThreadでTaskが実行されても、後にThreadPoolに簡単に拡張することができます。
一方、Handlerを使用する時があります。 handleMessage()
のようなパターンを使用する必要がしたり、 Context.registerReceiver()
のように引数としてHandlerを要求するAndroid APIを使用する場合です。
Related Posts
- エラー解決:android gradle plugin requires java 11 to run. you are currently using java 1.8.
- Android - コルーチンとRetrofitによる非同期通信の例
- Android - コルーチンでURL画像を読み込む
- Android - 振動、Vibrator、VibrationEffectの例
- Some problems were found with the configuration of task
- Query method parameters should either be a type that can be converted into a database column or a List
- UbuntuでAndroid 12オープンソースをダウンロードしてビルド
- Android - ViewModelを生成する方法
- Android - Transformations.map(), switchMap() の違い
- Android-Transformations.distinctUntilChanged()소개
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - 携帯電話の電話番号を取得する方法
- Android 12 - Splash Screens
- Android 12 - インクリメンタルインストール
- Android - adbコマンドでbugreportログファイルの抽出
- Android - adbコマンドでAppデータを削除する
- Android - adbコマンドでアプリ無効化、有効化
- Android - adbコマンドで特定のパッケージのPIDを検索
- Android - adbコマンドでパーミッションGrantまたはRevoke
- Android - adbコマンドで特定のパッケージのプロセスの終了
- Android - adbコマンドでapkのインストール、削除、
- Android - adb push、pullでファイルのコピー、ダウンロード
- Android - adbコマンドでscreen capture保存
- Android - adbコマンドでSystemアプリの削除、インストール
- Android - adbコマンドでsettings value確認、変更、
- Android 12 - IntentFilterのexported明示的な宣言
- Android - adbコマンドで工場出荷時の(Factory reset)
- Android - adb logcatコマンドでログ出力
- Android - adbコマンドでメモリダンプ(dump-heap)
- Android - adbコマンドでApp強制終了(force-stop)
- Android - adbコマンドでServiceの実行、終了
- Android - adbコマンドでActivity実行
- Android - adbコマンドでBroadcast配信
- Android - PackageManagerにPackage情報を取得する
- Android - ACTION_BOOT_COMPLETEDイベント受信