HandlerThread相比其他类的最佳使用方法

68

我正在尝试了解使用HandlerThread的最佳用例。

根据定义:

"用于启动具有looper的新线程的便捷类。然后可以使用looper来创建处理程序类。请注意,仍必须调用start()。"

我可能错了,但是我可以通过使用ThreadLooperHandler来实现类似的功能。那么何时应该使用HandlerThread呢?一个示例会非常有帮助。


1
@TalKanel:谢谢回复。我已经查看了这篇文章,但它只谈到了Executor相对于HandlerThread的优点,我完全同意。我的问题更像是,在什么情况下最好使用HandlerThread呢? - Androidme
看看我在这里的评论 - http://stackoverflow.com/questions/17897536/how-to-handle-tcp-data/17898322#comment26144902_17898322。一般来说,我更喜欢使用`HandlerThread`,因为通过`Message`进行超级简单的同步。话虽如此,它也可能成为瓶颈。此外,没有什么阻止您使用带有`HandlerThread`的ExecutorService。我以前写过一个,这并不难。 - Delyan
@Rarw:这实际上是我的问题,如果我们可以通过其他方式完成任务,那么创建另一个类的必要性在哪里?我相信在特定情况下会有一些好处,我需要了解一下。 - Androidme
@Delyan:你能给个例子或者告诉我哪里可以找到使用HandlerThread的有效代码吗?目前为止,我看到的例子都是比较通用的。谢谢。 - Androidme
@Rarw:明白了...如果是这样的话,我没问题..谢谢。 - Androidme
显示剩余6条评论
2个回答

88
这里是一个现实生活中的例子,HandlerThread 可以派上用场。当您注册相机预览帧时,您将在 onPreviewFrame() 回调函数中接收到它们。 文档 解释说:此回调在从 open(int) 调用的事件线程上调用。 通常,这意味着回调将在主线程上调用。 因此,在菜单打开、动画播放或屏幕上打印统计信息时,处理大量像素数组的任务可能会被阻塞。
解决方案很简单,创建一个 new HandlerThread() 并将 Camera.open() 委托给该线程(我通过 post(Runnable) 来实现,您不需要实现 Handler.Callback)。
请注意,所有其他与相机的工作都可以像往常一样完成,您不必将 Camera.startPreview()Camera.setPreviewCallback() 委托给 HandlerThread。为了安全起见,我在继续主线程(或在更改之前用于调用 Camera.open() 的任何线程)之前等待实际的 Camera.open(int) 完成。
因此,如果您的代码起点是:
try {
    mCamera = Camera.open(1);
}
catch (RuntimeException e) {
    Log.e(LOG_TAG, "failed to open front camera");
}
// some code that uses mCamera immediately

首先将其作为原样提取到一个私有方法中:

private void oldOpenCamera() {
    try {
        mCamera = Camera.open(1);
    }
    catch (RuntimeException e) {
        Log.e(LOG_TAG, "failed to open front camera");
    }
}

不要调用oldOpenCamera(),而是直接使用newOpencamera()

private void newOpenCamera() {
    if (mThread == null) {
        mThread = new CameraHandlerThread();
    }

    synchronized (mThread) {
        mThread.openCamera();
    }
}
private CameraHandlerThread mThread = null;
private static class CameraHandlerThread extends HandlerThread {
    Handler mHandler = null;

    CameraHandlerThread() {
        super("CameraHandlerThread");
        start();
        mHandler = new Handler(getLooper());
    }

    synchronized void notifyCameraOpened() {
        notify();
    }

    void openCamera() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                oldOpenCamera();
                notifyCameraOpened();
            }
        });
        try {
            wait();
        }
        catch (InterruptedException e) {
            Log.w(LOG_TAG, "wait was interrupted");
        }
    }
}

请注意,如果您在打开 mCamera 后不立即访问原始代码中的该项,则整个 notify() -- wait() 线程间通信是不必要的。

更新: 这里将同样的方法应用于加速度计:单独线程中的加速度传感器


4
你会把相机关在哪里? - Matthias
2
@Matthias:这是一个好问题。如果您的用例允许,请在Activity.onPause()中释放相机-首先,因为这是根据Activity生命周期系统保证您的活动接收到的唯一回调。但是,在onPause()之后有时可以使用相机。在这种情况下,您需要在不同的场景中优雅地处理它。 - Alex Cohn
1
新的Runnable的结尾)}应该是})。 - Rich
1
@Praveen:这就是你可以在notify()覆盖中执行的操作。 - Alex Cohn
1
@GopalSinghSirvi:这可能需要一个单独的问题。但是不深入探讨,“闪光灯”按钮操作相机,因此将相机移动到单独的线程并没有帮助:在相机线程被锁定时无法更改闪光灯。您应该将QR码处理委托给另一个线程,可以使用Executor或AsyncTask来实现。 - Alex Cohn
显示剩余10条评论

15

这里有一个链接,可以访问 HandlerThreadLooper 的源代码。

如果你看这两个源代码,你会发现 HandlerThread 正如它所说的一样 - 它是启动带有 LooperThread 的方便方法。为什么会有它?因为线程默认没有消息循环HandlerThread 只是创建一个具有消息循环的线程的简单方法。从源代码来看,你能用 HandlerThreadLooper 来复制这个函数吗?答案是可以。

Executor 是不同的。 Executor 接收提交的可运行任务并执行它们。为什么需要这样做呢?这允许你将任务的执行与其实际内容分离。什么时候需要这样做呢?比如说,如果你有一个需要同时执行多个任务的情况。你可以选择使用 Executor 在单个线程上运行它们,以便它们被串行执行。或者你可以使用固定的线程池,以使一些任务并行执行而其他任务不是。在任何情况下,任务的内容 - 即它实际上正在做什么 - 与它的执行方式是分离的。


1
我认为你的区分不够精确。你也可以通过Handler.postRunnable提交要执行的任务。实际上,你可以创建一个只使用HandlerThreads的ExecutorService,只需更改ThreadFactory和submit()方法即可。使用HandlerThreads使跨线程通信变得更容易,而不是任务提交。 - Delyan
1
我想我表达不够清楚。我的意思并不是Executor可以更轻松地执行任务,而是Executor的目的仅在于处理这些任务的执行。HandlerThread则有不同的用途。我稍后会进行编辑。 - Rarw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接