Android基础知识:在UI线程中运行代码


450

从在UI线程中运行代码的角度来看,两者之间是否有任何区别:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

要么

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}

为了澄清我的问题:我认为这些代码是从服务线程(通常是侦听器)调用的。我还认为需要在AsynkTask的doInBackground()函数中或在前两个代码片段之前调用的新Task(...)中完成大量工作。无论如何,将AsyncTask的onPostExecute()放在事件队列的末尾,对吗?
Luky 2012年

Answers:


288

尽管它们都具有相同的净效果,但没有一个是完全相同的。

第一和第二之间的区别是,如果你恰巧是执行代码时,主应用程序线程,则第一个(runOnUiThread())将执行Runnable立即。即使您已经在主应用程序线程上,第二个(post())也始终将Runnable事件队列放在事件队列的末尾。

第三个假设您创建并执行的一个实例,那么在最终执行等于a BackgroundTask的操作doInBackground()之前,将浪费大量时间从线程池中获取一个线程以执行默认的no-op post()。到目前为止,这是效率最低的三个。使用AsyncTask如果你确实有工作在后台线程做,不只是为使用onPostExecute()


27
还要注意,AsyncTask.execute()无论如何都需要您从UI线程进行调用,这会使该选项在仅从后台线程在UI线程上运行代码的用例中变得无用,除非您将所有后台工作移入doInBackground()AsyncTask正确使用。
kabuko

@kabuko如何检查AsyncTask从UI线程调用的内容?
尼尔·加利亚斯卡洛夫

@NeilGaliaskarov这似乎是一个可靠的选择:stackoverflow.com/a/7897562/1839500
Dick Lucas

1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-

1
@NeilGaliaskarov适用于大于或等于M的版本Looper.getMainLooper().isCurrentThread
Balu Sangem

251

我喜欢HPP评论中的那个,它可以在没有任何参数的任何地方使用:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

2
这有多有效?与其他选项一样吗?
EmmanuelMess

59

有第四种方式使用 Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

56
您应该对此小心。因为如果您在非UI线程中创建处理程序,则会将消息发布到非UI线程。默认情况下,处理程序将消息发布到创建它的线程。
lujop 2013年

107
在主UI线程do上执行new Handler(Looper.getMainLooper()).post(r),这是Looper.getMainLooper()对main进行静态调用的首选方式,而postOnUiThread()必须具有MainActivityin作用域的实例。
HPP

1
@HPP我不知道这种方法,当您既没有View也没有Activity时,这将是一个很好的方法。很棒!非常非常感谢您!
Sulfkain 2014年

@lujop AsyncTask的onPreExecute回调方法也是如此。
Sreekanth Karumanaghat

18

Pomber的答案是可以接受的,但是我不喜欢重复创建新对象。最好的解决方案始终是尝试减轻内存消耗的解决方案。是的,有自动垃圾收集功能,但移动设备中的内存保护属于最佳实践的范围。下面的代码更新服务中的TextView。

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

可以从以下任何地方使用它:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);

7
+1用于提及GC和对象创建。但是,我不一定同意The best solutions are always the ones that try to mitigate memory hog。还有许多其他标准best,并且有轻微的气味premature optimization。也就是说,除非您知道调用它的次数足以使创建的对象数成为问题(与您的应用可能创建垃圾的其他方法相比,这是一万种问题),否则可能best是编写最简单的(最容易理解的) )代码,然后继续执行其他任务。
制造商史蒂夫(Steve)2015年

2
顺便说一句,textViewUpdaterHandler最好将其命名为uiHandlermainHandler,因为它通常用于对主UI线程的任何发布;它根本不与TextViewUpdater类绑定。我会将其从该代码的其余部分移开,并明确说明它可以在其他地方使用...其余代码是可疑的,因为要避免动态创建一个对象,您可以将一次调用分解为两个步骤setTextpost,它们依赖于您用作临时对象的长期对象。不必要的复杂性,并且不是线程安全的。不容易维护。
制造商

如果你真的有这个地方叫了这么多次,这是值得缓存形势uiHandlertextViewUpdater,然后通过改变来提高你的类public void setText(String txt, Handler uiHandler)和方法添加一行uiHandler.post(this); 然后调用者可以在一个步骤做: textViewUpdater.setText("Hello", uiHandler);。然后在将来,如果需要确保线程安全,则方法可以将其语句包装在上的锁中uiHandler,并且调用方保持不变。
制造商史蒂夫(Steve)2015年

我很确定您只能运行一次Runnable。绝对是您的主意,总体而言,这很不错。
Nativ

@Nativ不,Runnable可以运行多次。线程不能。
Zbyszek

8

从Android P开始,您可以使用getMainExecutor()

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

来自Android开发人员文档

返回一个执行程序,该执行程序将在与此上下文关联的主线程上运行排队的任务。这是用于将调用分派到应用程序组件(活动,服务等)的线程。

CommonsBlog

您可以在Context上调用getMainExecutor()以获取将在主应用程序线程上执行其作业的Executor。还有其他方法可以使用Looper和自定义的Executor实现来实现,但这比较简单。


7

如果需要在Fragment中使用,则应使用

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

代替

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

因为在某些情况下会出现空指针异常,例如寻呼机片段


1

嗨,大家好,我告诉我这是一个基本问题

使用处理程序

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});

您为什么选择在runOnUiThread上使用通用处理程序?
ThePartyTurtle

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()如果未从UI线程调用它,则会给出a 。
Roc Boronat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.