Android子线程更新UI的方法总结
消息机制,对于Android开发者来说,应该是非常熟悉。对于处理有着大量交互的场景,采用消息机制,是再好不过了。有些特殊的场景,比如我们都知道,在Android开发中,子线程不能更新UI,而主线程又不能进行耗时操作,一种常用的处理方法就是,在子线程中进行耗时操作,完成之后发送消息,通知主线程更新UI。或者使用异步任务,异步任务的实质也是对消息机制的封装。
关于子线程到底能不能更新UI这个问题,之前看到一篇文章很有趣,让我对这个问题也有了新的认识,那么我也来写个简单例子测试下,布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.joy.messagetest.MainActivity"> <TextView
android:id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Hello World!" /> </RelativeLayout>
布局中只有一个TextView,java代码如下:
package com.example.joy.messagetest; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TextView mTvTest; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); initView(); new Thread(new Runnable() {
@Override
public void run() {
mTvTest.setText("子线程可以更新UI");
}
}).start();
} private void initView() {
mTvTest = (TextView) findViewById(R.id.tv_test);
}
}
代码也很简单,我开启子线程,在子线程中,将 TextView 内容设置为“子线程可以更新UI”,而在布局文件中,TextView 的 text 为“Hello world!”,那么现在运行程序,可能会出现的结果有三种:
- 程序崩了,抛异常了:说明子线程不能更新UI
- 程序正常运行,textview 上面显示“Hello World!”:说明子线程不能更新UI
- 程序正常运行,textview 上面显示“子线程可以更新UI”:说明子线程可以更新UI
运行程序,结果如下:

这说明什么?从结果看,子线程更新UI成功了。真的是这样吗?我自己也不相信,赶紧再验证一遍。这次我在布局文件中添加一个Button,修改后的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.joy.messagetest.MainActivity"> <TextView
android:id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Hello World!" /> <Button
android:id="@+id/btn_test1"
android:layout_below="@id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="子线程更新UI测试"/> </RelativeLayout>
同时修改java代码:
package com.example.joy.messagetest; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView mTvTest;
private Button mBtnTest1; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); initView(); new Thread(new Runnable() {
@Override
public void run() {
mTvTest.setText("子线程可以更新UI");
}
}).start();
} private void initView() {
mTvTest = (TextView) findViewById(R.id.tv_test);
mBtnTest1 = (Button) findViewById(R.id.btn_test1);
mBtnTest1.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch(v.getId()){
case R.id.btn_test1:
new Thread(new Runnable() {
@Override
public void run() {
mTvTest.setText("子线程真的可以更新UI吗?");
}
}).start();
break;
default:
break;
}
}
}
我们增加了一个button,点击button,启动一个子线程,在子线程中将 textview 的显示内容改为 “子线程真的可以更新UI吗?”。同样按照前面的分析,我们再来验证一下。重新运行程序, textview 显示 “子线程可以更新UI”, 然后我们点击 button。结果如下:

怎么回事?程序崩了。仔细看,你会发现,点击 button 后 textview 的内容其实是发生了更改的,然后程序崩溃了。查看日志,抛出如下异常:
AndroidRuntime: FATAL EXCEPTION: Thread-176
Process: com.example.joy.messagetest, PID: 11201
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
这次终于看到了熟悉的错误日志,只有初始创建视图的线程才能触碰这些视图,也就是说只有主线程才能更新UI。通过下面一行
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
我们能发现点端倪:在 framework/base/core/java/android/view/ViewRootImpl.java 中有一个方法 checkThread ,源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
该异常就是在这里触发的。对于这个问题,如果你还想深入下去探究清楚,可以跟进去 RTFSC ! 这里推荐一篇文章,Android中子线程真的不能更新UI吗?
说了这么多,其实子线程是不能直接更新UI的。Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程即子线程中使用。换句话说,在子线程调用 invalidate 方法会导致线程不安全。熟悉View工作原理的人都知道,invalidate 方法会通知 view 立即重绘,刷新界面。作一个假设,现在我用 invalidate 在子线程中刷新界面,同时UI线程也在用 invalidate 刷新界面,这样会不会导致界面的刷新不能同步?这就是invalidate不能在子线程中使用的原因。
但是我们可以在子线程执行某段代码,需要更新UI的时候去通知主线程,让主线程来更新。如何做呢?常见的方法,除了前面提到的在UI线程创建Handler,在子线程发送消息到UI线程,通知UI线程更新UI,还有 handler.post(Runnable r)、 view.post(Runnable r)、activity.runOnUIThread(Runnable r)等方法。跟进去看源码,发现其实它们的实现原理都还是一样,最终都是通过Handler发送消息来实现的。下面分别用这几种方法实现一下在子线程更新UI。
修改后的布局文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.joy.messagetest.MainActivity"> <TextView
android:id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Hello World!" /> <Button
android:id="@+id/btn_test1"
android:layout_below="@id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="子线程更新UI测试"/> <Button
android:id="@+id/btn_test2"
android:layout_below="@id/btn_test1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Handler发送消息"/> <Button
android:id="@+id/btn_test3"
android:layout_below="@id/btn_test2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Handler.Post"/> <Button
android:id="@+id/btn_test4"
android:layout_below="@id/btn_test3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="View.Post"/> <Button
android:id="@+id/btn_test5"
android:layout_below="@id/btn_test4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Activity.RunOnUIThread"/> </RelativeLayout>
java代码如下:
package com.example.joy.messagetest; import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView mTvTest;
private Button mBtnTest1;
private Button mBtnTest2;
private Button mBtnTest3;
private Button mBtnTest4;
private Button mBtnTest5; private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 100) {
mTvTest.setText("由Handler发送消息");
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); initView(); new Thread(new Runnable() {
@Override
public void run() {
mTvTest.setText("子线程可以更新UI");
}
}).start();
} private void initView() {
mTvTest = (TextView) findViewById(R.id.tv_test);
mBtnTest1 = (Button) findViewById(R.id.btn_test1);
mBtnTest2 = (Button) findViewById(R.id.btn_test2);
mBtnTest3 = (Button) findViewById(R.id.btn_test3);
mBtnTest4 = (Button) findViewById(R.id.btn_test4);
mBtnTest5 = (Button) findViewById(R.id.btn_test5);
mBtnTest1.setOnClickListener(this);
mBtnTest2.setOnClickListener(this);
mBtnTest2.setOnClickListener(this);
mBtnTest3.setOnClickListener(this);
mBtnTest4.setOnClickListener(this);
mBtnTest5.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_test1:
new Thread(new Runnable() {
@Override
public void run() {
mTvTest.setText("子线程真的可以更新UI吗?");
}
}).start();
break;
case R.id.btn_test2: //通过发送消息
new Thread(new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(100);
}
}).start();
break;
case R.id.btn_test3: //通过Handler.post方法
new Thread(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
mTvTest.setText("handler.post");
}
});
}
}).start();
break;
case R.id.btn_test4: //通过 view.post方法
new Thread(new Runnable() {
@Override
public void run() {
mTvTest.post(new Runnable() {
@Override
public void run() {
mTvTest.setText("view.post");
}
});
}
}).start();
break;
case R.id.btn_test5: //通过 activity 的 runOnUiThread方法
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvTest.setText("runOnUIThread");
}
});
}
}).start();
break;
default:
break;
}
}
}
运行一下效果如下图:

以上就是消息机制最常见的应用场景——在子线程通知主线程更新UI的几种用法。
Android子线程更新UI的方法总结的更多相关文章
- Android子线程更新UI成功
android子线程更新UI成功 今天在写demo的时候,在子线程中更新UI,发现更新成功,记录一下. protected void onCreate(Bundle savedInstanceStat ...
- android子线程更新UI
参考:https://www.cnblogs.com/joy99/p/6121280.html 子线程是不能直接更新UI的.Android实现View更新有两组方法,分别是invalidate和pos ...
- Android子线程更新UI主线程方法之Handler
背景: 我们开发应用程序的时候,处于线程安全的原因子线程通常是不能直接更新主线程(UI线程)中的UI元素的,那么在Android开发中有几种方法解决这个问题,其中方法之一就是利用Handler处理的. ...
- android 子线程更新UI
参考http://examples.javacodegeeks.com/android/core/os/handler/android-handler-example/package com.exam ...
- Android 子线程更新UI 异常
众所周知,Android是不可以在子线程中直接更新UI的,需要借助Handler或者View.post(Runnable runnable)或者runOnUIThread(Runnable runna ...
- 子线程更新UI界面的2种方法
一.一般我们都会在子线程完成一些耗时的操作. 1.Android中消息机制: 2.知识点: Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队, ...
- Qt 子线程更新Ui
最近做练习,写一个Qt版的飞机大战,需要用子线程更新UI,发现Qt子线程不能更新Ui,否则程序会崩溃.在网上百度了下,说是需要在子线程自定义信号,然后在线程回调的run()函数里发射信号,主线程连接信 ...
- [Android学习笔记]子线程更新UI线程方法之Handler
关于此笔记 不讨论: 1.不讨论Handler实现细节 2.不讨论android线程派发细节 讨论: 子线程如何简单的使用Handler更新UI 问题: android开发时,如何在子线程更新UI? ...
- OkHttp3几个简单的例子和在子线程更新UI线程的方法
okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿. ...
随机推荐
- WinForm 禁止调整大小、禁止最大化窗口
这个设置代码必须添加到*.designer.cs中,就是自动隐藏的那部分: #region Windows Form Designer generated code 一般窗体设置的代码会生成到最后面, ...
- H264 帧结构分析、帧判断
http://blog.csdn.net/dxpqxb/article/details/7631304 H264以NALU(NAL unit)为单位来支持编码数据在基于分组交换技术网络中传输. NAL ...
- Oracle IO优化心得
很多的时侯,做Oracle DBA的我们,当应用管理员向我们通告现在应用很慢.数据库很慢的时侯,我们到数据库时做几个示例的Select也发现同样的问题时,有些时侯我们会无从下手,因为我们认为数据库的各 ...
- Android下fragment切换的动画效果
网上看到的例子,转过来记录一下,学习一下,感谢原作者的辛勤编码,效果非常好 基于Android3.0新增的动画api,效果很赞 共21种动画效果: <item>X轴缩放</item& ...
- Hibernate向MySQL插入中文数据--乱码解决
<property name="hibernate.connection.url">jdbc:mysql://127.0.0.1:3306/exam?useUnicod ...
- 【jQuery】总结:筛选器、控制隐藏、操作元素style属性
筛选器 -> http://blog.csdn.net/lijinwei112/article/details/6938134 常用到的: $("tr[id=ac_"+id+ ...
- Delphi DecodeDate和EncodeDate 拆分和聚合时间函数的用法
SysUtilsprocedure DecodeData(Date: TDateTime; var Year, Month, Day: Word);DecodeDate打断TdateTime成为年月日 ...
- Pacman主题下给Hexo增加简历类型
原文 http://blog.zanlabs.com/2015/01/02/add-resume-type-to-hexo-under-pacman-theme/ 背景 虽然暂时不找工作,但是想着简历 ...
- codeforces 700C Break Up 暴力枚举边+边双缩点(有重边)
题意:n个点,m条无向边,每个边有权值,给你 s 和 t,问你至多删除两条边,让s,t不连通,问方案的权值和最小为多少,并且输出删的边 分析:n<=1000,m是30000 s,t有4种情况( ...
- Spark学习体会
在去年图计算工作中,和公司里实习的博士生尝试过Spark后,发现Spark比Hadoop在计算速度上后很大的提高.Spark的计算使用Scala语言编写代码,其中图计算用到了GraphX.对Spark ...