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

Posted

技术标签:

【中文标题】Android 基础:在 UI 线程中运行代码【英文标题】:Android basics: running code in the UI thread 【发布时间】:2012-10-02 17:48:57 【问题描述】:

从在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() 函数中或在前两个 sn-p 之前调用的 new Task(...) 中需要完成繁重的工作。无论如何,AsyncTask 的 onPostExecute() 被放在事件队列的末尾,对吧? 【参考方案1】:

这些都不完全相同,尽管它们都具有相同的净效应。

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

第三个,假设您创建并执行BackgroundTask 的实例,将浪费大量时间从线程池中获取线程,执行默认无操作doInBackground(),然后最终执行数量到post()。这是迄今为止三者中效率最低的。如果您确实在后台线程中有工作要做,请使用AsyncTask,而不仅仅是使用onPostExecute()

【讨论】:

另请注意,AsyncTask.execute() 无论如何都要求您从 UI 线程调用,这使得此选项对于从后台线程简单地在 UI 线程上运行代码的用例无用,除非您将所有你的后台工作到doInBackground() 并正确使用AsyncTask @kabuko 如何检查我是否从 UI 线程调用 AsyncTask @NeilGaliaskarov 这看起来是个不错的选择:***.com/a/7897562/1839500 @NeilGaliaskarov boolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread()); @NeilGaliaskarov 对于大于或等于 M 的版本使用 Looper.getMainLooper().isCurrentThread【参考方案2】:

我喜欢HPP comment的那个,不用任何参数就可以在任何地方使用:

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

【讨论】:

效率如何?和其他选项一样吗?【参考方案3】:

还有第四种方式使用Handler

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

【讨论】:

你应该小心这个。因为如果您在非 UI 线程中创建处理程序,您会将消息发布到非 UI 线程。默认情况下,处理程序将消息发布到创建它的线程。 要在主 UI 线程上执行 new Handler(Looper.getMainLooper()).post(r),这是首选方式,因为 Looper.getMainLooper() 对 main 进行静态调用,而 postOnUiThread() 必须在范围内有 MainActivity 的实例. @HPP 我不知道这种方法,当您既没有视图也没有 Activity 时,这将是一个很好的方法。效果很好!非常非常非常感谢! @lujop AsyncTask 的 onPreExecute 回调方法也是如此。【参考方案4】:

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);

【讨论】:

+1 用于提及 GC 和对象创建。但是,我不一定同意The best solutions are always the ones that try to mitigate memory hogbest还有很多其他的标准,这有点premature optimization的味道。也就是说,除非你知道你调用它的次数足够多以至于创建的对象数量是一个问题(与你的应用程序可能创建垃圾的一​​万种其他方式相比。),best 可能是编写最简单的(最容易理解)代码,然后继续执行其他任务。 顺便说一句,textViewUpdaterHandler 最好命名为 uiHandlermainHandler,因为它通常对主 UI 线程的任何帖子都很有用;它根本与您的 TextViewUpdater 类无关。我会将它从其余代码中移开,并明确表示它可以在其他地方使用......其余代码是可疑的,因为为了避免动态创建一个对象,你打破了可能是一个单一的调用两个步骤setTextpost,它们依赖于您用作临时对象的长寿命对象。不必要的复杂性,而且不是线程安全的。不容易维护。 如果你真的遇到这样的情况,它被调用了很多次以至于值得缓存uiHandlertextViewUpdater,然后通过更改为@987654334来改进你的课程@并添加方法行uiHandler.post(this);然后调用者可以一步完成:textViewUpdater.setText("Hello", uiHandler);。那么以后,如果需要线程安全,方法可以将其语句包装在uiHandler上的锁中,调用者保持不变。 我很确定您只能运行 Runnable 一次。这绝对打破了你的想法,总体上很好。 @Nativ Nope,Runnable 可以运行多次。线程不能。【参考方案5】:

android P 开始,您可以使用 getMainExecutor():

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

来自Android developer docs:

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

来自CommonsBlog:

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

【讨论】:

【参考方案6】:

如果你需要在 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");
    
);

因为在某些情况下会出现空指针异常,比如pager fragment

【讨论】:

【参考方案7】:

使用处理程序

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

【讨论】:

您有什么理由选择使用通用处理程序而不是 runOnUiThread? 如果不是从 UI 线程调用,这将给出一个 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()【参考方案8】:

Kotlin 版本:

Handler(Looper.getMainLooper()).post 
   Toast.makeText(context, "Running on UI(Main) thread.", Toast.LENGTH_LONG).show()

或者,如果您使用的是 Kotlin 协程: 在协程范围内添加:

withContext(Dispatchers.Main) 
   Toast.makeText(context, "Running on UI(Main) thread.", Toast.LENGTH_LONG).show()

【讨论】:

以上是关于Android 基础:在 UI 线程中运行代码的主要内容,如果未能解决你的问题,请参考以下文章

android 怎么刷新UI组件

Android中AsyncTask使用具体解释

Android中Handler的使用

Android 旋转防止 .post() 或 UI 线程运行,与网络调用相关

Android中UI线程与后台线程交互设计的6种方法

android 基础UI控件学习总结