【问题标题】:Difference between a SingleThreadExecutor and a Handler in Android (by Example)Android 中 SingleThreadExecutor 和 Handler 之间的区别(通过示例)
【发布时间】:2021-07-25 17:18:47
【问题描述】:

我正在研究如何使用 Android 中的 Room 将数据写入 SQlite 数据库的教科书示例。显然,我们不能从主线程将数据写入数据库,但我们需要一个后台线程。在我的书中,我们所做的是,我们创建了一个额外的类AppExecutors,其内容如下:

public class AppExecutors {
    private final Executor _diskIO;
    private final Executor _networkIO;
    private final Executor _mainThread;

    private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
        _diskIO = diskIO;
        _networkIO = networkIO;
        _mainThread = mainThread;
    }

    public AppExecutors() {
        this(Executors.newSingleThreadExecutor(),
                Executors.newFixedThreadPool(3),
                new MainThreadExecutor());
    }

    public Executor diskIO() {
        return _diskIO;
    }


public Executor networkIO() {
        return _networkIO;
    }

    public Executor mainThread() {
        return _mainThread;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}

然后,在 MainActivity 内部,它也包含 UI,即按钮,当点击时触发一个动作,我们调用

getApp().getExecutors().diskIO().execute(() -> {
    ...
}

其中getApp().getExecutors() 返回AppExecutors 的新实例。

这是我的问题:

我知道getApp().getExecutors().mainThread().execute(() -> {...} 被传递了一个新的Runnable,并且这个Runnable 被提供给主线程的消息队列。 looper 将每个线程从消息队列中取出并执行。

但是,getApp().getExecutors().diskIO().execute(() -> {...} 的情况有所不同。 传递给它的 Runnable 显然是在后台线程中执行的。 Executors.newSingleThreadExecutor() 似乎正在开辟一个新线程。但是没有与该线程关联的处理程序或消息队列。所以,我想知道,这个线程在执行后不会关闭吗?一旦线程关闭,我们就不能再次打开同一个线程。这对我来说有点困惑,线程不应该保持打开状态吗? 我现在已经阅读了 ExecutorService ,显然这个服务只是让线程保持打开状态。但是话又说回来,这不是 messageque 和处理程序正在做的事情吗?那么这有什么不同呢?

【问题讨论】:

    标签: android multithreading executorservice


    【解决方案1】:

    我认为this answer 会部分回答您的问题。另外对于您的问题:

    所以,我想知道,这个线程不会在执行后关闭吗?一旦线程关闭,我们就不能再次打开同一个线程。这让我有点困惑,线程不应该保持打开状态吗?

    不,不会。该执行程序服务的线程将处于空闲或阻塞状态,直到您将新的可运行对象排队,因为它使用 BlockingQueue。如果队列为空,线程将被阻塞,如果 Runnale 对象到达其队列,线程将被激活。如您所见,ExecutorService 没有 Looper。仅当您调用 ExecutorService 的 shutdown() 或 shutdownNow() 方法时,它才会被销毁。

    但是话又说回来,这不正是messageque 和handler 正在做的事情吗?那么这有什么不同呢?

    相比之下,Handler 需要绑定到 Looper 才能发送消息和发布 Runnables,而 Looper 则存在于 HandlerThread 中。一个HandlerThread对应一个单线程的Executor服务,它的Handler对应一个Executor。在 AppExecutors 类中,一个 Handler 与主线程的 Looper 相关联,因为 UI 对象不能从创建它的主线程以外的线程中接触。

    让我们看看示例 Java 代码的区别。 使用 ExecutorService 的后台线程示例。

    /*
    To create a background thread making use of the Java concurrent API first we
    need to create an ExecutorService or only Executor if you don't want to manage
    the service. But generally it must be shutdown when we are done with it.
    That's why we need a reference to the ExecutorService in orderto shutdown it.
    */
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Executor executor = executorService;
    
    // To send a command:
    executor.execute(someRunnable);
    // As you can see an executor has only the execute method.
    
    // After we are done with executor service we must shutdown it.
    executorService.shutdown();
    // or
    executorService.shutdownNow();
    // This returns a list of runnables that were in the queue.
    
    

    使用 HandlerThread 的后台线程示例:

    /*
    To create a background thread which has a message queue we have to make
    use of the Android's HandlerThread and Handler API. A HandlerThread object
    manages a Looper and a Thread in it while A Handler object is used to send
    commands or messages to a living HandlerThread object.
    */
    // A HandlerThread can also be instantiated with a name and a priority.
    HandlerThread handlerThread = new HandlerThread();
    handlerThread.start(); // Must call start in contras to an ExecutorService
    
    // Tie this handler to the handlerThread's Looper so that we can send
    // commands or messages to its queue.
    Handler handler = new Handler(handlerThread.getLooper());
    
    // To send a command
    handler.post(someRunnable); // or
    handler.postDelayed(someRunnable, delayInMillis); // or
    handler.postAtFrontOfQueue(someRunnable); // or
    handler.postAtTime(someRunnable, upTimeInMillis);
    

    Handler API 只是 Android 特定的 API,并且有许多实用程序来发送和处理 Message 对象和 Runnable 命令。有关详细信息,请参阅Handler API。

    【讨论】:

    • 啊,我明白了,谢谢 Kozmotronik! ...因此,在 AppExecutors 类中,在私有构造函数中,而不是写 Executors.newSingleThreadExecutor(),我也可以像对主线程所做的那样做:创建一个实现 Executor 的类,我在其中创建一个新的 HandlerThread 和一个处理程序并像你的例子一样继续?
    • 由于您将使用 Handler 对象和 HandlerThread,因此实现 Executor 是没有用的。只是为了澄清您的理解,而不是实现 Executor 可以直接使用 Handler 将命令发布到主线程。然而,他们选择以包装方式实现它,以通过执行程序提供 API。如果您仍然不明白,我明天将使用 AppExecutors 类展示一个示例。
    • 再次感谢!我想我明白你的意思。但是,如果您有时间编写该示例,那将是很棒的。无论哪种方式,非常感谢您的帮助!
    • 好的,我今晚就做,继续看。
    【解决方案2】:

    这是一个经过修改的 AppExecutors 类的示例。您可以看到,我们没有使用单线程执行程序,而是使用了后台处理程序线程,我们创建了一个 Handler 对象,然后将其绑定到其 HandlerThread 对象。 此外,对于主线程执行,我们删除了包装类并创建了另一个 Handler 对象,然后我们将其绑定到主线程的 looper。我希望一切对你来说更清楚

    public class AppExecutors {
        private final Handler _diskIO;
        private final Executor _networkIO;
        private final Handler _mainThread;
    
        private AppExecutors(Handler diskIO, Executor networkIO, Handler mainThread) {
            _diskIO = diskIO;
            _networkIO = networkIO;
            _mainThread = mainThread;
        }
    
        public AppExecutors() {
    
            HandlerThread htDiskIO = new HandlerThread(
                "com.application.htDiskIO", // Give it a name (optional)
                Process.THREAD_PRIORITY_BACKGROUND
            );
    
            this( new Handler(htDiskIO.getLooper()),
                  Executors.newFixedThreadPool(3),
                  new Handler(Looper.getMainLooper()));
        }
    
        public Handler diskIO() {
            return _diskIO;
        }
    
    
        public Executor networkIO() {
            return _networkIO;
        }
    
        public Handler mainThread() {
            return _mainThread;
        }
    }
    

    以下是新 AppExecutors 类的一些示例用法 sn-ps:

    AppExecutors executors = new AppExecutors();
    
    // Now for diskIO operations you must call like:
    executors.diskIO().post(someRunnable);
    executors.diskIO().postDelayed(someRunnable, someDelay);
    
    // The network operation is same
    executors.networkIO().execute(someRunnable);
    
    // And last but not least; main thread operations
    execute.mainThread().post(someRunnable);
    execute.mainThread().postDelayed(someRunnable, someDelay);
    

    还有一点需要注意的是,HandlerThread 和 Handler API 属于 Google 的 android.os 包。 因此,您将无法在不是为 Android 操作系统编写的应用程序中使用它们。 以上就是这个例子的全部内容,祝你学业有成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-07
      • 2013-12-28
      • 2016-03-18
      • 2013-01-29
      • 1970-01-01
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多