Handler、Runnable和Threads有什么不同?

36

Handler, Runnable和Threads之间有什么区别?

在我使用Android进行开发时,有时需要在后台运行一些任务。我通常使用Threads来运行它们。通常我会编写一个继承Thread并实现run方法的类。

我还看到一些示例是实现Runnable并将其传递给Threads。

但是我仍然感到困惑。有人能给我一个清晰的解释吗?

  1. 如果可以在Thread的run方法中编写后台代码,Runnable的意义是什么?
  2. Handler在线程内部如何使用,为什么需要使用它?
  3. Android还有另一个称为runOnUiThread的东西,我们该如何使用?我知道它用于更新UI。

我对Handler和UniThread不太了解,因此只是留下了评论而没有回答,但通常你只会创建可运行的实现。如果你想改变实现的某些东西,通常只会扩展线程。 - Mark W
我对Android的具体情况不太清楚,但你使用Runnable是因为它在调度运行的任务时提供了更多的灵活性。直接使用Thread意味着你被限制在使用一种特定的策略上。 - chrylis -cautiouslyoptimistic-
如果你能给我展示一些例子就太好了。 - Hong Wei Wang
6个回答

26

为什么要使用Runnable而不是Thread?

  • Runnable将需要异步运行的代码与如何运行代码分离开来。这使得您的代码更加灵活。例如,runnable中的异步代码可以在线程池或专用线程上运行。

    Thread拥有您的可运行对象可能不需要访问的状态。访问比必要状态多的内容是不良设计。

    线程占用大量内存。为每个小操作创建新线程需要处理时间来分配和释放此内存。

runOnUiThread实际上是在做什么?

  • Android的runOnUiThread队列中的Runnable在UI线程上执行。这很重要,因为您不应该从多个线程更新UI。runOnUiThread使用一个Handler

    请注意,如果UI线程的队列已满或需要执行的项目较长,则可能需要一段时间才能实际运行您排队的Runnable

什么是Handler?

  • 处理程序允许您发布可运行项以在特定线程上执行。在幕后,runOnUiThread将您的Runnable与Android的UI Handler一起排队,以便您的可运行项可以安全地在UI线程上执行。

11

1. 为什么要使用Runnable?

Runnable只是一个接口, 它需要实例化一个线程来包含它。而线程已经包含了生成线程的能力。如果你扩展Thread,你就不能扩展其他任何东西(Java不支持多重继承)。类可以有多个接口,因此你可以有Runnable。

另外,当你扩展Thread类时,每个线程都会创建唯一的对象并与之关联。当你实现Runnable时,它会将同一个对象分享给多个线程。

2. 为什么要使用Handler?它是什么?

Handler是用Java编写的(在内部使用一个线程),因此你可以使用一个线程来完成你可以使用Handler完成的所有操作。

那么为什么应该使用Handler呢?原因如下:

  • Handler允许你发送和处理与线程的MessageQueue相关联的Message和Runnable对象。简单来说,Handler可以使你的工作变得容易。

  • Android有两个主要的规则来处理线程:

  • 不要阻塞UI线程

  • 不要从UI线程以外的线程访问Android UI工具包

为了遵守上述2条规则,在Android中,我们有3个内置方法可以处理Activity类在不同线程上运行或被调用时的情况。然后我们可以使用以下三种方法之一将UI更新调度到UI线程中。Activity或View随后作为一个处理程序(下面更多关于处理程序)并将你的Runnable安排到UI线程上:

  1. Activity.runOnUiThread(Runnable)
    1. View.post(Runnable)
    2. View.postDelayed(Runnable, long) //(long = time to scheduling)

3. 什么是UI线程?

UI线程是渲染UI元素(如View和Activity)的主线程。不应该在UI线程中执行任何耗时操作。默认情况下,应用程序在UI线程中运行。您无需执行任何特殊操作即可使用UI线程。


4
Handler、Runnable和Threads实际上是协同工作的,我认为你不应该将它们进行比较。
Handler允许在两个线程之间以安全的方式发送消息。这意味着发送线程将消息放入目标线程队列中,而此目标队列将在适当的时间处理此消息。
Runnable是一个接口,您可以在实现中放置要在某个线程上执行的逻辑。您实际上也可以在非线程相关的地方使用Runnable。许多Java API实际上使用Runnable,而不仅仅是Thread。您可以使用handler发布Runnable,或者可以将其与执行程序一起使用。Runnables很好用,因为您可以以匿名实现的形式实现它们。
UniThread是指UI Thread吗?大多数用户界面都在单个线程中实现其工作:所有UI元素——窗口/小部件都使用消息进行通信(就像在Handler中一样)。例如,用户按下按钮,这会初始化带有按钮已被按下信息的消息,将其发送到UI线程,并最终传递给您的监听器。
在Android中,禁止(导致异常)从非UI线程修改UI元素,这是有意义的——如果您从其他线程修改它,那么当UI线程正在对相同的小部件进行某些更改时,可能会发生这种情况,导致未定义行为。

我看到Thread类实现了Runnable接口。我的理解是,Runnable与并发没有任何关系。Thread才是真正执行任务的类。Runnable只是一种可以放在代码中以便使用线程运行的东西。我的理解正确吗? - Hong Wei Wang
正确,但是在Android中,Thread是相当低级的概念,由于Android组件(例如活动)具有相当复杂的生命周期,因此您应该使用AsyncTask、Loaders、IntentService等。 - marcinj
同时,线程占用大量内存。它们的创建和收集成本很高。 - William Morrison

1

Runnable接口是Thread类的父类,run()是Runnable接口的方法。

因此,通常我们应该优先选择实现Runnable接口而不是继承Thread类,因为我们不想改变类的行为,也可以继承另一个类。这也有助于实现松耦合,并且我们可以从任何其他类更改用户界面。

我们可以通过4种方式更改UI:

1.使用Handler示例

public class MainActivity extends AppCompatActivity {    
private Handler mainHandler=new Handler();
class ExampleRunnable implements Runnable {
        int seconds;

        public ExampleRunnable(int seconds) {
            this.seconds = seconds;
        }

        @Override
        public void run() {
            for (int i = 0; i < seconds; i++) {
 mainHandler.post(new Runnable() {
                        @Override
                        public void run() {
                        button.setText("50% process completed");
                        }
                    });

使用runOnUIThread()方法时,我们需要将其与post附加使用。可以通过以下示例轻松理解。
class ExampleRunnable implements Runnable {
        int seconds;



 public ExampleRunnable(int seconds) {
        this.seconds = seconds;
    }

    @Override
    public void run() {runOnUIThread.post(new Runnable() {
                    @Override
                    public void run() {
                        button.setText(" Runnable");
                    }
                });
            }

3. 通过使用任何视图,我们可以在此处使用任何视图进行调用,我使用了 switch 进行调用。

public class MainActivity extends AppCompatActivity {
 private Switch aSwitch;
@Override
    protected void onCreate(Bundle savedInstanceState) {
 aSwitch=findViewById(R.id.switch1);
class ExampleRunnable implements Runnable {
            int seconds;



     public ExampleRunnable(int seconds) {
            this.seconds = seconds;
        }

        @Override
        public void run() { aSwitch.post(new Runnable() {
                        @Override
                        public void run() {
                            button.setText(" Runnable");
                        }
                    });
                }

通过在另一个线程中创建Handler,我们需要定义Looper,因为默认情况下它会将我们附加到我们的线程looper。

Handler threadHandler=new Handler(Looper.getMainLooper());
threadHandler.post(new Runnable()
{
                            @Override
                            public void run() {
                                button.setText(" Runnable");
                            }
                        });
                    }

这是4种实现方式,我认为你现在可能对可运行线程和runOnUIThread()以及Handler有所了解,这些都是由其他人精美编写的。

1
我使用线程来运行它。通常我会编写一个继承自Thread并实现run方法的类。
1.如果可以在Thread的run方法中编写后台代码,那么Runnable的意义是什么?
使用Runnable并从Runnable创建线程是一般做法。
来自Oracle关于并发性的教程concurrency,关于Runnable和Thread用法的比较:
Runnable对象更为通用,因为Runnable对象可以继承自除Thread以外的其他类。
2.Handler在线程内部如何使用,为什么我们需要使用它?这是一个广泛的主题。从官方文档网站简单解释:
  1. Handler 允许您发送和处理与线程的 MessageQueue 相关联的 MessageRunnable 对象。每个 Handler 实例都与单个线程及其消息队列相关联。

  2. 创建新的 Handler 时,它将绑定到创建它的线程/消息队列 -- 从那时起,它将向该消息队列传递消息和运行它们,并在它们出队时执行它们。

  3. Handler 有两个主要用途:(1) 安排消息和运行对象在将来某个时间执行;(2) 将操作排入不同于您自己的线程中执行

  4. 当为应用程序创建一个进程时,其主线程专用于运行管理顶级应用程序对象(活动、广播接收器等)及其创建的任何窗口的消息队列。 您可以创建自己的线程,并通过 Handler 与主应用程序线程进行通信。 这是通过从新线程调用与之前相同的 post 或 sendMessage 方法完成的。然后,给定的 Runnable 或 Message 将被安排在 Handler 的消息队列中,在适当时处理。

这张图片来自Anishar Ali在blog.mindorks.com的文章中,清晰地解释了相关概念。

enter image description here

Android有一个叫做runOnUiThread的东西,我们如何使用它?我知道它用于更新UI。

你可以查看runOnUiThread的实现以获取更多细节。

/**
     * Runs the specified action on the UI thread. If the current thread is the UI
     * thread, then the action is executed immediately. If the current thread is
     * not the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

请参考下面的帖子,了解 Handler 的使用示例。

Android: 在线程中显示 Toast


0
如果可以在线程的run方法中编写后台代码,那么Runnable的意义是什么?
“Runnable”是一个接口,用于创建类似于通过扩展“java.lang.Thread”类创建的线程类。唯一的区别是,“Runnable”接口允许类扩展其他类(如果需要)以覆盖/继承某个类的功能。扩展“java.lang.Thread”类将吊销此功能。
此外,Runnable接口表示可以由普通线程、执行器或任何其他方式执行的任务。因此,将任务逻辑上分离为可运行而不是线程是良好的设计决策。
阅读更多: http://javarevisited.blogspot.com/2012/01/difference-thread-vs-runnable-interface.html#ixzz2qgjDYJhT

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