基础篇-在非UI线程中更新UI元素

个人原创,转载请注明出处: http://blog.csdn.net/supluo/article/details/

先了解两个概念

1、UI:User Interface的缩写,用户界面的意思。你可以不恰当的理解为我们能够看到的,操作的东西;在Android中什么才称为UI呢,可以简单的理解为View及其子类等元素。这是一个不够正确的概念,只是对新手做一个简单的抛砖引玉。

2、ANR:Application Not Responding,意思是程序没有响应。

在如下情况下,Android会报出ANR错误:

– 主线程 (“事件处理线程” / “UI线程”) 在5秒内没有响应输入事件

– BroadcastReceiver 没有在10秒内完成返回

因此耗时的操作通常会放在其他线程中去执行,以防导致程序卡顿甚至出现ANR错误,线程执行完成之后再操作UI元素。

下面举例导致卡顿的例子,在一个按钮中执行以下代码:

            proDialog.Show();//显示进度对话框
            System.Threading.Thread.Sleep(10 * 1000);//线程休眠10秒,模拟耗时操作,触发ANR
            HandleResult("数据更新成功:" + new Random().Next(10));更新其他UI元素

上面的代码就会阻塞主UI线程,其实对话框也不会显示出来,调用对话框show方法后,对话框并不会立即出现,待读者自行讨论,因此上面的效果特别糟糕,只会显示按钮被按下的效果,十秒之后更新UI,对话框不会显示出来,因此正确的做法是显示一个对话框之后,将耗时的操作放在其他线程中去执行,执行完成之后再更新ui,但是我们并不能在其他线程中直接更新UI元素,因此这篇文章的主题就是讲解如何在非UI线程中更新UI元素。

下面进入正题

在正常开发过程中,我们通常需要将程序中遇到的比较耗时的操作新开其他线程去执行,以防阻塞主UI线程导致ANR错误,但是在实际开发过程中可能会遇到如下两个异常信息

1、Only the original thread that created a view hierarchy can touch its views;

2、Can‘t create handler inside thread that has not called Looper.prepare();

第一种在非UI线程中更新UI元素就会导致这种错误,第二种是在非UI线程中视图显示某些UI提示信息;其实可以笼统的归纳为都是在非UI线程中更新UI导致的。我们先看下面两个错误的例子:

一、非UI线程更新UI元素,(在一个按钮事件中执行以下操作,事件绑定代码就不贴了)

new Thread(() =>
            {
                try
                {
                    mText.Text = "数据更新成功:" + new Random().Next(10);
                }
                catch (Exception ex)
                {
                }
            }).Start();

执行结果抛出android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.即上面说的第一种错误信息出现的情况。

二、非UI线程进行UI提示。(在一个按钮事件中执行以下操作,事件绑定代码就不贴了)

new Thread(() =>
            {
                try
                {
                    proDialog.Show();//proDialog是一个dialog
                }
                catch (Exception ex)
                {
                }
            }).Start();

执行结果抛出java.lang.RuntimeException: Can‘t create handler inside thread that has not called Looper.prepare(),即上面说的第二种错误信息出现的情况。

解决办法:

这种错误对于才接触这行的开发者来讲,经常出现这样的问题,其实这种问题也相当简单比较容易解决,但是最主要的是找到一个最为合适的解决办法,下面说几种比较简单的解决方法。

1、委托式

将非UI元素中执行UI操作的部分委托给主UI线程执行,见以下代码:

        proDialog.Show();
            new Thread(() =>
            {
                System.Threading.Thread.Sleep(10 * 1000);

                this.RunOnUiThread(() => //this 指代的是Activity对象,RunOnUiThread 是Activity的一个成员方法
                {
                    HandleResult("数据更新成功:" + new Random().Next(10));
                });
            }).Start();

2、View.Post形式

使用UI元素的Post或者PostDelay方法

官方文档注释:Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.

Parameters: action The Runnable that will be executed.

Returns:Returns true if the Runnable was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting.

示例见以下代码:

proDialog.Show();
            new Thread(() =>
            {
                try
                {
                    System.Threading.Thread.Sleep(10 * 1000);
                    mText.Post(() =>
                    {
                        HandleResult("数据更新成功:" + new Random().Next(10));
                    });
                }
                catch (Exception ex)
                {
                    this.RunOnUiThread(
                        () =>
                        {
                            Toast.MakeText(this, "线程执行过程中出现了错误!", ToastLength.Long).Show();
                        }

                    );
                }
            }).Start();

这种方法包括PostDelay方法其实跟第一种方法是极其类似的,底层实现的本质是一样。

3、Handler形式

1)、初始化一个Handler对象

Handler mHandler;//定义变量

mHandler = new Handler(HandleMessage);//初始化,此处可以使用匿名函数等方式

//方法定义,此方法中主要是更新UI操作

private void HandleMessage(Message msg)
        {
            switch (msg.What) {
                case 1:
                    HandleResult("数据更新成功:" + new Random().Next(10));
                    break;
            }
        }

2)、在恰当的时候,使用handler发送消息进行UI更新

            proDialog.Show();
            new Thread(() =>
            {
                System.Threading.Thread.Sleep(10 * 1000);
                mHandler.SendEmptyMessage(1);
            }).Start();

Handler的具体使用可以查阅相关资料再进一步了解。

上面的方法大多都是使用了Handler来进行消息的处理,下面给出一张handler的工作图,

4、AsyncTask形式

AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

这个代码有点多,可以自行百度查阅,有相当多的不错的文章可以借阅。

提醒一点,在Xamarin.Android中,如果传递给AsyncTask的执行参数是字符串,则需要使用Java.Lang.String进行包装,早些的版本我测试是需要这样做的,最近的版本还没来得及进行测试,稍后我补全这一部分,或者有知道的也可以交流下。

以上就是比较常用的方式。

Demo下载地址

个人搞了个博客App,平时上个厕所,睡觉前等随便看两篇文章,总能有些收获,希望大家支持!http://blog.csdn.net/supluo/article/details/43489475

时间: 2024-10-14 20:24:31

基础篇-在非UI线程中更新UI元素的相关文章

为什么我们可以在非UI线程中更新UI

尊重原创转载请注明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵权必究! 炮兵镇楼 看到这样的标题--估计N多人会说我是逗比----因为很多盆友在学习Android(特别是从4.0之后开始入门的)的时候都会常看见或听到别人说我们更新UI呢要在UI线程(或者说主线程)中去更新UI,不要在子线程中更新UI,而Android官方呢也建议我们不要在非UI线程直接更新UI,为什么呢?借助Android官方的一句话来说就是:

Android 非UI线程中更新UI

Android 非UI线程中更新UI runOnUiThread(new Runnable() { public void run() { onDown(null); } });

Android开之在非UI线程中更新UI

当在非UI线程中更新UI(程序界面)时会出现如下图所示的异常: 那如何才能在非UI线程中更细UI呢? 方法有很多种,在这里主要介绍两种: 第一种:在需要更新UI的代码行后加Looper.prepare();与Looper.loop();两句话即可.如: new Thread(){ @Override public void run() { // TODO Auto-generated method stub txtRotation.setText("在非UI线程中更新UI!"); Lo

Android在非UI线程中更新UI的方法

1.使用Thread+Handler实现非UI线程更新UI界面 private MyHandler mHandler = new MyHandler(); ...... mHandler.sendResult(MyHandler.UPDATE_VIEW, null); ...... private class MyHandler extends Handler{ private static final int UPDATE_VIEW = 0; @Override public void han

UWP 在非UI线程中更新UI

大家都知道,不可以在 其他线程访问 UI 线程,访问 UI 线程包括给 依赖属性设置值.读取依赖属性.调用方法(如果方法里面修改了依赖属性)等.一旦访问UI线程,那么就会报错,为了解决这个问题,需要使用本文的方法,让后台线程访问 UI 线程. 本文提供三个方法可以让其他线程访问 UI 线程 第一个方法是比较不推荐使用的,可能出现 win10 uwp Window.Current.Dispatcher中Current为null await Window.Current.Dispatcher.Run

android Looper 非UI线程中更新UI

测试service中使用了一个Toast来打印log. 提示无法调用Looper.prepare() 加入后代码正常执行.  Looper.prepare();      Toast.makeText(getApplicationContext(), "Services Thread", Toast.LENGTH_LONG).show()  Looper.loop(); 不过再其他处重新加了一个Toast后出现错误,告知每一个线程只能有一个Looper Caused by: java.

android 不能在子线程中更新ui的讨论和分析

问题描述 做过android开发基本都遇见过ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是只能在主线程中更改ui,子线程要修改ui只能post到主线程或者使用handler之类.但是仔细看看exception的描述并不是这样的,"Only the original thread that created a view hierarchy can touch its views",只有创建该 view 布局层次的原始线程

C# WINFORM 线程中更新UI

幸好今天是周末,有时间把这个问题记录一下.在多种语言之间切换,发现开发效率降的很低了,开发成本都集中到调式上了,C/C++这些放弃很久了,突然感觉线程这个问题搞的有点烦躁 我这里提到的线程中更新UI,在大数据 大并发,以及CPU时间碎片上,未经过验证,项目紧 你懂的..如果你和我一样急于实现 可以考虑一下 总体是这样 //NetDataHandler 是被非UI主线程调用的 你现在看到到这两个函数是放在UI窗体上的 public void NetDataHandler(string jsonDa

网络操作不能直接写在主线程中 以及 为什么不能在子线程中更新UI控件的属性

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //注意: 所有网络操作不能直接写在主线程中 因为所有的网络操作都是耗时的,如果加载到主线程中,会导致与用户的交互出现问题 ,所以要加载到子线程中 // [self loadImage]; [self performSelectorInBackground:@selector(loadImage) withObject:nil]; } //加