个人原创,转载请注明出处: 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进行包装,早些的版本我测试是需要这样做的,最近的版本还没来得及进行测试,稍后我补全这一部分,或者有知道的也可以交流下。
以上就是比较常用的方式。
个人搞了个博客App,平时上个厕所,睡觉前等随便看两篇文章,总能有些收获,希望大家支持!http://blog.csdn.net/supluo/article/details/43489475