为什么在使用runOnUiThread的同时要使用Handlers呢?

60

我曾经接触过 HandlersrunOnUiThread 的概念。但对我来说,它们之间的区别仍然存在疑惑。

它们都旨在从后台线程执行UI操作。但是,在选择两种方法之间需要考虑哪些因素。

例如,考虑一个在后台执行Web服务的RunnableThread,现在我想更新UI。

更新UI的最佳方式是什么?我应该选择Handler还是runOnUiThread

我仍然知道我可以使用AsyncTask并利用onPostExecute。但我只是想知道它们之间的区别。


5
runOnUiThread只是将一个Runnable对象发布到Handler上的一种快捷方式。Handler是Android中定义的跨线程通信设施的基础(例如,AsyncTaskonPostExecute使用Handler来传递doInBackground的结果)。 - zapl
5个回答

86

Activity.runOnUiThread()是更通用的Handlers的特殊情况。使用Handler,您可以在自己的线程中创建自己的事件查询。使用使用default constructor实例化的Handlers并不意味着“代码将在UI线程上运行”。默认情况下,处理程序绑定到它们从中实例化的Thread

要创建一个保证绑定到UI(主)线程的Handler,您应该创建一个绑定到Main LooperHandler对象,如下所示:

Handler mHandler = new Handler(Looper.getMainLooper());

此外,如果您检查runOnUiThread()方法的实现,它使用Handler来执行操作:
  public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

正如您从上面的代码片段中看到的那样,如果从UI线程调用runOnUiThread(),则Runnable action将立即执行。否则,它将被发布到Handler,稍后将被执行。

但是“它们被实例化的线程”通常不是UI线程吗? - SIr Codealot
9
@Mike,这完全取决于你的代码。通常情况下,处理程序是从主线程实例化的,但有很多情况下开发人员在创建Handler实例时并没有确切的当前线程信息。因此,如果开发人员需要保证处理程序在主线程中执行,应使用new Handler(Looper.getMainLooper()) - HitOdessit
@Hit 非常感谢,你的 Looper.getMainLooper() 这句话在我遇到一个毫无头绪的问题时给了我很大的帮助。(http://stackoverflow.com/questions/22831612/cant-handle-ui-after-reconnecting-to-the-server) - Muhammed Refaat
请注意,如果runOnUiThread()是在UI线程中调用的,它将立即运行action。否则,它将把action发布到Handler。 - Ken

2

处理程序是旧的方式(API级别1)来完成任务,然后引入了AsycTask(API级别3),并更加强调使用runOnUIThread(API级别1)。您应该尽可能避免使用处理程序,并根据需要选择其他两种方法。


1
但是为什么呢?我需要确切的区别。你能否请再解释一下。 - Andro Selva
没有区别,使用处理程序和循环器可以实现相同的功能,但这些技术可以帮助您避免错误。请参见http://en.wikipedia.org/wiki/Syntactic_sugar。 - Animesh

1
如果你的Runnable需要更新UI,那么请在runOnUiThread上发布它。
但并不总是可能在UI线程上发布Runnable
考虑这样一种情况,你想要执行网络/IO操作或调用Web服务。在这种情况下,你不能将Runnable发布到UI线程。它会抛出android.os.NetworkOnMainThreadException 这些类型的Runnable应该在不同的线程上运行,比如HandlerThread。完成操作后,你可以使用与UI线程关联的Handler将结果发布回UI线程。
public void onClick(View view) {

    // onClick on some UI control, perform Network or IO operation

    /* Create HandlerThread to run Network or IO operations */
    HandlerThread handlerThread = new HandlerThread("NetworkOperation");
    handlerThread.start();

    /* Create a Handler for HandlerThread to post Runnable object */
    Handler requestHandler = new Handler(handlerThread.getLooper());

   /* Create one Handler on UI Thread to process message posted by different thread */

    final Handler responseHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            //txtView.setText((String) msg.obj);
            Toast.makeText(MainActivity.this,
                    "Runnable on HandlerThread is completed and got result:"+(String)msg.obj,
                    Toast.LENGTH_LONG)
                    .show();
        }
    };

    NetworkRunnable r1 = new NetworkRunnable("http://www.google.com/",responseHandler);
    NetworkRunnable r2 = new NetworkRunnable("http://in.rediff.com/",responseHandler);
    requestHandler.post(r1);
    requestHandler.post(r2);

}

class NetworkRunnable implements Runnable{
    String url;
    Handler uiHandler;

    public NetworkRunnable(String url,Handler uiHandler){
        this.url = url;
        this.uiHandler=uiHandler;
    }
    public void run(){
        try {
            Log.d("Runnable", "Before IO call");
            URL page = new URL(url);
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ((line = buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Runnable", "After IO call:"+ text.toString());

            Message msg = new Message();

            msg.obj = text.toString();

            /* Send result back to UI Thread Handler */
            uiHandler.sendMessage(msg);


        } catch (Exception err) {
            err.printStackTrace();
        }
    }
}

1

Handler 有许多工作,例如消息传递和频繁的 UI 更新,如果您为任何正在运行的任务启动 A Thread。Handler 允许您发送和处理与线程的 MessageQueue 相关联的 Message 和 Runnable 对象,这在许多应用程序中非常有用,如蓝牙聊天、wifi 聊天等。而且 Handler 还有 PostDelay 和 PostAtTime 方法,可以让您玩弄任何视图以进行动画和更改可见性等操作。

您一定要看看这个。

http://developer.android.com/guide/components/processes-and-threads.html

http://developer.android.com/tools/testing/activity_testing.html


1
runOnUIThread 总是在 UI 线程上执行计算。当使用 Handler 时,可以启动一个线程在那里进行重型计算,并使用 Handler 将结果发布到 UI 线程。因此,如果您使用 runOnUIThread,则要小心,不要在其上进行重型计算。异步操作也应在其中使用 Hanlder 来发布更新和进度。这更依赖于您的易用性。 - Vipin Sahu

1

以下是HitOdessit的回答。

你可以创建一个类,如下所示。

public class Global{
    private static Handler mHandler = new Handler(Looper.getMainLooper());
    public static void runOnUiThread(Runnable action){
        mHandler.post(action);
    }
}

然后像这样调用它。
Global.runOnUiThread(new Runnable(){
    //Your code
});

这可以在任何地方运行(只要您可以访问全局类)。


1
这只是语法糖。在我看来,没有必要将处理程序对象设置为私有甚至放在不同的类中。 - ansh sachdeva

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