非ui线程更新view可以更新ui么

博客分类:
在Android项目中经常有碰到这样的问题,在子线程中完成耗时操作之后要更新UI,下面就自己经历的一些项目总结一下更新的方法:
在看方法之前看一下Android中消息机制:
引用
Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
不熟悉的朋友可以参考一下这篇文档:
Android知识梳理:消息机制之Handler:http://gqdy365.iteye.com/blog/2148925
Android知识梳理:消息机制之Looper :http://gqdy365.iteye.com/blog/2137494
下面基于上述原理说一下更新方法:
方法一:用Handler
1、主线程中定义Handler:
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
//完成主界面更新,拿到数据
String data = (String)msg.
updateWeather();
textView.setText(data);
2、子线程发消息,通知Handler完成UI更新:
private void updateWeather() {
new Thread(new Runnable(){
public void run() {
//耗时操作,完成之后发送消息给Handler,完成UI更新;
mHandler.sendEmptyMessage(0);
//需要数据传递,用下面方法;
Message msg =new Message();
msg.obj = "数据";//可以是基本类型,可以是对象,可以是List、map等;
mHandler.sendMessage(msg);
}).start();
方法一的Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递content对象或通过接口调用;
方法二:用Activity对象的runOnUiThread方法更新
在子线程中通过runOnUiThread()方法更新UI:
new Thread() {
public void run() {
//这儿是耗时操作,完成之后更新UI;
runOnUiThread(new Runnable(){
public void run() {
imageView.setImageBitmap(bitmap);
}.start();
如果在非上下文类中(Activity),可以通过传递上下文实现调用;
Activity activity = (Activity) imageView.getContext();
activity.runOnUiThread(new Runnable() {
public void run() {
imageView.setImageBitmap(bitmap);
这种方法使用比较灵活,但如果Thread定义在其他地方,需要传递Activity对象;
方法三:View.post(Runnable r)
imageView.post(new Runnable(){
public void run() {
imageView.setImageBitmap(bitmap);
这种方法更简单,但需要传递要更新的View过去;
总结:UI的更新必须在主线程中完成,所以不管上述那种方法,都是将更新UI的消息发送到了主线程的消息对象,让主线程做处理;
浏览 66103
浏览: 1000367 次
来自: 深圳
没有源码。
你那个Jni库是自己编译的还是有现成的呢?
一个带人脸识别的智能照相机demohttp://blog.cs ...
咋用啊,搞不懂
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'分析为什么有时在非UI线程更新UI会崩溃 - 简书
分析为什么有时在非UI线程更新UI会崩溃
很多初学者肯定有这样一个经验,在Activity的一个子线程中更新UI,发现会报错。很多人知道这个错误,但却不知道是什么原因引起的。今天我们来分析一下是什么原因引起的。我今天以textview更新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"
tools:context="com.generalwei.mytest.ThreadActivity"&
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello"/&
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="@+id/btn_change"
android:text="更换UI"/&
&/RelativeLayout&
public class ThreadActivity extends AppCompatActivity {
private android.widget.TextV
private android.widget.B
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
this.btnchange = (Button) findViewById(R.id.btn_change);
this.tv = (TextView) findViewById(R.id.tv);
btnchange.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
tv.setText(new Date().getTime()+"");
}).start();
运行app,点击btnchange按钮,发现app崩溃。查看日志,发现这样一个错误:
我们来查看一下tv.setText()方法的源码。
public final void setText(CharSequence text) {
setText(text, mBufferType);
public void setText(CharSequence text, BufferType type) {
setText(text, type, true, 0);
if (mCharWrapper != null) {
mCharWrapper.mChars =
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
if (mLayout != null) {
checkForRelayout();
这里我们还是没有看见什么原因引起的,继续追查checkForRelayout()方法。
private void checkForRelayout() {
invalidate();
查看invalidate()方法,我们会看见这样一个注释:
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
public void invalidate() {
invalidate(true);
意思是说如果view是可见的,这个方法会刷新view。但是必须发生在ui线程上。看到这边我们发现我们的追踪是正确。那么接着看,为什么一定要在ui线程上更新ui。
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
final ViewParent p = mP
if (p != null && ai != null && l & r && t & b) {
final Rect damage = ai.mTmpInvalR
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
ViewParent 是一个接口,ViewRootImpl是它的实现类,那么我们继续追查,代码如下:
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
你会发现一个检查线程的方法,那么查看方法checkThread()。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
查看代码后发现有一个mThread线程,它是会是UI线程吗,我们就查看Thread的来源。
public ViewRootImpl(Context context, Display display) {
mThread = Thread.currentThread();
发现mThread是在ViewRootImpl创建的时候赋值,这个的线程一定是UI线程。所以当前线程不是UI线程的时候会抛异常。
为什么在onResume之前非UI线程也能更新UI
发现mThread是在ViewRootImpl创建的时候赋值,这个的线程一定是UI线程。所以当前线程不是UI线程的时候会抛异常。
但是有时候在也能在非UI线程中更新,后来我们发现在onResume之前用非UI线程更新能UI,而onResume之后就不行了。这是因为onResume之前还没有创建ViewRootImpl这个类,ActivityThread类中有一个handleResumeActivity方法,这个方法是用来回调Activity的onResume方法,具体的看如下代码:
final void handleResumeActivity(IBinder token,boolean clearHide, boolean
isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged =
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor =
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardB
if (r.mPreserveWindow) {
a.mWindowAdded =
r.mPreserveWindow =
// Normally the ViewRoot sets up callbacks with the Activity
// in addView-&ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded =
wm.addView(decor, l);
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow =
// Tell the activity manager we have resumed.
if (reallyResume) {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
// If an exception was thrown when trying to resume, then
// just end this activity.
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
我们可以看见这样一个方法performResumeActivity(),它的源码如下:
public final ActivityClientRecord performResumeActivity(IBinder token,boolean clearHide, String reason) {
if (r != null && !r.activity.mFinished) {
r.activity.performResume();
这里可以看见它调用了activity.performResume(),那么再继续查看下面的源码:
final void performResume() {
performRestart();
mInstrumentation.callActivityOnResume(this);
onPostResume();
performRestart()方法主要是为了执行回调onRestart方法,具体内容就不做分析了。mInstrumentation.callActivityOnResume()方法则是为了回调Activity的OnResume()方法。onPostResume()方法这是为了激活Window。
在handleResumeActivity()方法中我们可以看见一个WindowManager类,这个类是用来控制窗口显示的,而它的addView是用来添加视图。WindowManagerImpl是WindowManager的实现类,WindowManagerImpl的addView方法代码如下:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
mGlobal是WindowManagerGlobal的对象,继续看mGlobal.addView的代码:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
View panelParentView =
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i &= 0; --i) {
mRoots.get(i).loadSystemProperties();
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
int index = findViewLocked(view, false);
if (index &= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
// The previous removeView() had not completed executing. Now it has.
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type &= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type &= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i & i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
你可以看做ViewRootImpl的初始化时在这里进行的,这就是为什么在onResume之前可以更新UI了。
为什么要这么设计呢?
因为所有的UI控件都是非线程安全的,如果在非UI线程更新UI会造成UI混乱。所以一般我们会在Handler中更新UI。
如有写的不当之处,请多指教。
这是本人一些读书笔记,如有不对之处还请指教。
Android有条铁则:子线程不能修改UI 至于为什么 ,就是修改了会报错呗 具体错误如下 但是令人称奇的是:如果我把sleep去掉 竟然是可以更新的并不会报错,这就勾起了我的好奇心,我想进一步深入源码的认识下一到底为什么这时候可以更新,为甚延时之后又不能更新了, 为什么修...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
问题描述 做过android开发基本都遇见过 ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是只能在主线程中更改 ui,子线程要修改 ui 只能 post 到主线程或者使用 handler 之类。但是仔细看看...
压力如山,还在追求美丽 我想写下好多,写下这个世界,写下这个伟大的时代,也想写下我生存的内容。 我不知我的世界是什么,我在世界上用什么方式存在的。 一生的存在是什么呢,一生的思考是什么呢。 我的世界是什么呢,我要到哪里去,思考的无助以及理智的无效,在这世界上什么才能可行。我...
忘记了是什么时候参加比赛,记得时间一次一次的推迟,从8月份推迟到了10月份进行,冷空气侵袭,有点冷。 最近的日子,最近圈子都有点小,感谢公司的比赛,让我走出了象山,可以和同事们一起努力,为象山分公司加油! 记得以前学校的时候,太内向,心很想参加这样的比赛,但是总是不敢,紧张...
总是在不经意间
感叹时光的流逝、生命的短暂、不可回复的过去、不可预知的未来、不愿一成不变的命运
回到身体健康的过去
回到无忧无虑的自己
回到母亲的年少时光
回到单纯的自己
一切又是那么不可知
5月9日,青春爱情电影《以爱为名》在上海举办观影见面会。导演王毓雅,主演黄恺杰,王奕瑾一出场引得现场阵阵尖叫。粉丝直呼:终于见到男神女神了。一贯低调的黄恺杰在观众和导演的强烈要求下, 重现了剧中的经典桥段荧幕初吻! 黄恺杰撩出王奕瑾甜蜜初吻 当天黄恺杰携手王奕瑾甜蜜登场,观...
世间纷繁,花开花落,每一丝的气息都弥漫着生活的美好。美丽伴随着时间缓缓绽放,也随之黯淡地消去。望着那消逝的背影,面对心中出现的巨大陷落,叹口气,相比于说那是声嗟叹,不如说是祭奠。 电影中的人物,比现实中更加现实,我们早已经习惯了颠沛流离的逃亡,海誓山盟的爱情,或者患难见真情...关于在非UI线程中同样能更新UI的问题
主要程序代码如上,在这个程序中程序与结果如上图截图所示,在这个程序中并不像官方文档中所示会抛出异常,那么这是什么原因呢?猜想:最大的可能是跟Activity的生命周期有关,当我们创建一个Activity调用onCreate方法时,UI并没有执行onResume,也就是说UI没有真正展示出来之前我们的新建线程中的方法就已经执行,所以就不存在“更新”问题。所以就不会报错,程序是正常执行的。只有在onResume执行之后才存在跟新问题。ui线程与新线程之间执行是异步的。所以有时间差的问题。所以在这时候不用怀疑官方文档是否出错,假设我们在非主线程中更新ui但是耗时比较长就会出现抛出异常问题。佐证:在幕渴网老师在讲解Handler这堂课程中出现抛出异常的问题,那么我们来看看跟我们上面写的究竟有什么区别。直接上图。幕课网老师多加了一条代码,如图所示,在onCreate方法中是得新线程睡眠了1s,别小看这一秒,这一秒足以使得主线程执行完onCreate方法,执行完onResume方法,而在此时线程运行起来是真正意义上的更新UI,所以抛出了异常。我写着个希望跟大家一起探讨这个问题。希望大家能顶起来
暂无任何回答
39634人关注
Copyright (C) 2018 imooc.com All Rights Reserved | 京ICP备 号-11C# Winform 跨线程更新UI控件常用方法汇总_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
C# Winform 跨线程更新UI控件常用方法汇总
阅读已结束,下载本文需要
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
还剩2页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢&>&Xamarin.Android 非UI线程更新UI
Xamarin.Android 非UI线程更新UI
上传大小:14KB
Xamarin.Android 非UI线程更新UI
综合评分:5(7位用户评分)
下载个数:
{%username%}回复{%com_username%}{%time%}\
/*点击出现回复框*/
$(".respond_btn").on("click", function (e) {
$(this).parents(".rightLi").children(".respond_box").show();
e.stopPropagation();
$(".cancel_res").on("click", function (e) {
$(this).parents(".res_b").siblings(".res_area").val("");
$(this).parents(".respond_box").hide();
e.stopPropagation();
/*删除评论*/
$(".del_comment_c").on("click", function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_invalid/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parents(".conLi").remove();
alert(data.msg);
$(".res_btn").click(function (e) {
var parentWrap = $(this).parents(".respond_box"),
q = parentWrap.find(".form1").serializeArray(),
resStr = $.trim(parentWrap.find(".res_area_r").val());
console.log(q);
//var res_area_r = $.trim($(".res_area_r").val());
if (resStr == '') {
$(".res_text").css({color: "red"});
$.post("/index.php/comment/do_comment_reply/", q,
function (data) {
if (data.succ == 1) {
var $target,
evt = e || window.
$target = $(evt.target || evt.srcElement);
var $dd = $target.parents('dd');
var $wrapReply = $dd.find('.respond_box');
console.log($wrapReply);
//var mess = $(".res_area_r").val();
var mess = resS
var str = str.replace(/{%header%}/g, data.header)
.replace(/{%href%}/g, 'http://' + window.location.host + '/user/' + data.username)
.replace(/{%username%}/g, data.username)
.replace(/{%com_username%}/g, data.com_username)
.replace(/{%time%}/g, data.time)
.replace(/{%id%}/g, data.id)
.replace(/{%mess%}/g, mess);
$dd.after(str);
$(".respond_box").hide();
$(".res_area_r").val("");
$(".res_area").val("");
$wrapReply.hide();
alert(data.msg);
}, "json");
/*删除回复*/
$(".rightLi").on("click", '.del_comment_r', function (e) {
var id = $(e.target).attr("id");
$.getJSON('/index.php/comment/do_comment_del/' + id,
function (data) {
if (data.succ == 1) {
$(e.target).parent().parent().parent().parent().parent().remove();
$(e.target).parents('.res_list').remove()
alert(data.msg);
//填充回复
function KeyP(v) {
var parentWrap = $(v).parents(".respond_box");
parentWrap.find(".res_area_r").val($.trim(parentWrap.find(".res_area").val()));
评论共有2条
我也不知道为什么会下载这个东西,可能是不要分数吧,不过也解决了我的问题,确实是忘记这个函数了
有效,就是这个RunOnUiThread方法
综合评分:
积分/C币:3
yinkaisheng-nj
综合评分:
积分/C币:0
VIP会员动态
CSDN下载频道资源及相关规则调整公告V11.10
下载频道用户反馈专区
下载频道积分规则调整V1710.18
spring mvc+mybatis+mysql+maven+bootstrap 整合实现增删查改简单实例.zip
资源所需积分/C币
当前拥有积分
当前拥有C币
输入下载码
为了良好体验,不建议使用迅雷下载
Xamarin.Android 非UI线程更新UI
会员到期时间:
剩余下载个数:
剩余积分:0
为了良好体验,不建议使用迅雷下载
积分不足!
资源所需积分/C币
当前拥有积分
您可以选择
程序员的必选
绿色安全资源
资源所需积分/C币
当前拥有积分
当前拥有C币
(仅够下载10个资源)
为了良好体验,不建议使用迅雷下载
资源所需积分/C币
当前拥有积分
当前拥有C币
为了良好体验,不建议使用迅雷下载
资源所需积分/C币
当前拥有积分
当前拥有C币
您的积分不足,将扣除 10 C币
为了良好体验,不建议使用迅雷下载
你当前的下载分为234。
你还不是VIP会员
开通VIP会员权限,免积分下载
你下载资源过于频繁,请输入验证码
您因违反CSDN下载频道规则而被锁定帐户,如有疑问,请联络:!
若举报审核通过,可返还被扣除的积分
被举报人:
举报的资源分:
请选择类型
资源无法下载
资源无法使用
标题与实际内容不符
含有危害国家安全内容
含有反动色情等内容
含广告内容
版权问题,侵犯个人或公司的版权
*详细原因:
Xamarin.Android 非UI线程更新UI

我要回帖

更多关于 android 主线程更新ui 的文章

 

随机推荐