Android - Handler vs Executor

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を提供しています。

例えば、 ThreadPoolExecutorgetTaskCount()と呼ばれる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

codechachaCopyright ©2019 codechacha