现在的位置: 首页 > 综合 > 正文

子线程更新UI会发生android.view.ViewRoot$CalledFromWrongThreadException异常的解决方法 .

2013年08月25日 ⁄ 综合 ⁄ 共 6509字 ⁄ 字号 评论关闭

 子线程更新UI

显然假如你的程序需要执行耗时的操作的话,假如像上例一样由主线程来负责执行该操作是错误的。所以我们需要在onClick方法中创建一个新的子线程来负责调用GOOGLE API来获得天气数据。刚接触Android的开发者最轻易想到的方式就是如下:

Java代码 复制代码
收藏代码
  1. public void onClick(View v) {
  2. //创建一个子线程执行耗时的从网络上获得天气信息的操作
  3. new Thread() {
  4. @Override
  5. public void run() {
  6. //获得用户输入的城市名称
  7. String city = editText.getText().toString();
  8. //调用Google 天气API查询指定城市的当日天气情况
  9. String weather = getWetherByCity(city);
  10. //把天气信息显示在title上
  11. setTitle(weather);
  12. }
  13. }.start();
  14. }
  1. public void onClick(View v) {  
  2.   
  3.    //创建一个子线程执行耗时的从网络上获得天气信息的操作
      
  4.   
  5.    new Thread() {  
  6.   
  7.        @Override  
  8.   
  9.        public void run() {  
  10.   
  11.           //获得用户输入的城市名称
      
  12.   
  13.           String city = editText.getText().toString();  
  14.   
  15.           //调用Google 天气API查询指定城市的当日天气情况
      
  16.   
  17.           String weather = getWetherByCity(city);  
  18.   
  19.           //把天气信息显示在title上
      
  20.   
  21.           setTitle(weather);  
  22.   
  23.        }  
  24.   
  25.    }.start();  
  26.   
  27. }  

但是很不幸,你会发现Android会提示程序由于异常而终止。为什么在其他平台上看起来很简单的代码在Android上运行的时候依然会出错呢?假如你观察LogCat中打印的日志信息就会发现这样的错误日志:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

从错误信息不难看出Android禁止其他子线程来更新由UI thread创建的试图。本例中显示天气信息的title实际是就是一个由UI thread所创建的TextView,所以参试在一个子线程中去更改TextView的时候就出错了。这显示违反了单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行

2.2 Message Queue

在单线程模型下,为了解决类似的问题,Android设计了一个Message Queue(消息队列),线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:

l Message Queue

Message Queue是一个消息队列,用来存放通过Handler发布的消息。消息队列通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列。Android在第一启动程序时会默认会为UI thread创建一个关联的消息队列,用来管理程序的一些上层组件,activities,broadcast receivers 等等。你可以在自己的子线程中创建Handler与UI thread通讯。

l Handler

通过Handler你可以发布或者处理一个消息或者是一个Runnable的实例。没个Handler都会与唯一的一个线程以及该线程的消息队列管理。当你创建一个新的Handler时候,默认情况下,它将关联到创建它的这个线程和该线程的消息队列。也就是说,假如你通过Handler发布消息的话,消息将只会发送到与它关联的这个消息队列,当然也只能处理该消息队列中的消息。

主要的方法有:

1) public final boolean sendMessage(Message msg)

把消息放入该Handler所关联的消息队列,放置在所有当前时间前未被处理的消息后。

2) public void handleMessage(Message msg)

关联该消息队列的线程将通过调用Handler的handleMessage方法来接收和处理消息,通常需要子类化Handler来实现handleMessage。

l Looper

Looper扮演着一个Handler和消息队列之间通讯桥梁的角色。程序组件首先通过Handler把消息传送给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler,Handler接受到消息后调用handleMessage进行处理。

1) 可以通过Looper类的静态方法Looper.myLooper得到当前线程的Looper实例,假如当前线程未关联一个Looper实例,该方法将返回空。

2) 可以通过静态方法Looper. getMainLooper方法得到主线程的Looper实例

线程,消息队列,Handler,Looper之间的关系可以通过一个图来展现:

在了解了消息队列及其相关组件的设计思想后,我们将把天气预告的案例通过消息队列来重新实现:

在了解了消息队列及其相关组件的设计思想后,我们将把天气预告的案例通过消息队列来重新实现:

Java代码 复制代码
收藏代码
  1. private EditText editText;
  2. private Handler messageHandler;
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.main);
  7. editText = (EditText) findViewById(R.id.weather_city_edit);
  8. Button button = (Button) findViewById(R.id.goQuery);
  9. button.setOnClickListener(this);
  10. //得到当前线程的Looper实例,由于当前线程是UI线程也可以通过Looper.getMainLooper()得到
  11. Looper looper = Looper.myLooper();
  12. //此处甚至可以不需要设置Looper,因为 Handler默认就使用当前线程的Looper
  13. messageHandler = new MessageHandler(looper);
  14. }
  15. @Override
  16. public void onClick(View v) {
  17. //创建一个子线程去做耗时的网络连接工作
  18. new Thread() {
  19. @Override
  20. public void run() {
  21. //活动用户输入的城市名称
  22. String city = editText.getText().toString();
  23. //调用Google 天气API查询指定城市的当日天气情况
  24. String weather = getWetherByCity(city);
  25. //创建一个Message对象,并把得到的天气信息赋值给Message对象
  26. Message message = Message.obtain();
  27. message.obj = weather;
  28. //通过Handler发布携带有天气情况的消息
  29. messageHandler.sendMessage(message);
  30. }
  31. }.start();
  32. }
  33. //子类化一个Handler
  34. class MessageHandler extends Handler {
  35. public MessageHandler(Looper looper) {
  36. super(looper);
  37. }
  38. @Override
  39. public void handleMessage(Message msg) {
  40. //处理收到的消息,把天气信息显示在title上
  41. setTitle((String) msg.obj);
  42. }
  43. }
  1. private EditText editText;  
  2.   
  3.     private Handler messageHandler;  
  4.   
  5.     @Override  
  6.   
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.   
  9.         super.onCreate(savedInstanceState);  
  10.   
  11.         setContentView(R.layout.main);  
  12.   
  13.         editText = (EditText) findViewById(R.id.weather_city_edit);  
  14.   
  15.         Button button = (Button) findViewById(R.id.goQuery);  
  16.   
  17.         button.setOnClickListener(this);  
  18.   
  19.         //得到当前线程的Looper实例,由于当前线程是UI线程也可以通过Looper.getMainLooper()得到
      
  20.   
  21.         Looper looper = Looper.myLooper();  
  22.   
  23.         //此处甚至可以不需要设置Looper,因为 Handler默认就使用当前线程的Looper
      
  24.   
  25.         messageHandler = new MessageHandler(looper);  
  26.   
  27.     }  
  28.   
  29.   
  30.     @Override  
  31.   
  32.     public void onClick(View v) {  
  33.   
  34.         //创建一个子线程去做耗时的网络连接工作   
  35.   
  36.         new Thread() {  
  37.   
  38.             @Override  
  39.   
  40.             public void run() {  
  41.   
  42.                 //活动用户输入的城市名称   
  43.   
  44.                 String city = editText.getText().toString();  
  45.   
  46.                 //调用Google 天气API查询指定城市的当日天气情况
      
  47.   
  48.                 String weather = getWetherByCity(city);  
  49.   
  50.                 //创建一个Message对象,并把得到的天气信息赋值给Message对象
      
  51.   
  52.                 Message message = Message.obtain();  
  53.   
  54.                 message.obj = weather;  
  55.   
  56.                 //通过Handler发布携带有天气情况的消息
      
  57.   
  58.                 messageHandler.sendMessage(message);  
  59.   
  60.             }  
  61.   
  62.         }.start();  
  63.   
  64.     }  
  65.   
  66.   
  67.     //子类化一个Handler   
  68.   
  69.     class MessageHandler extends Handler {  
  70.   
  71.         public MessageHandler(Looper looper) {  
  72.   
  73.             super(looper);  
  74.   
  75.         }  
  76.   
  77.         @Override  
  78.   
  79.         public void handleMessage(Message msg) {  
  80.   
  81.             //处理收到的消息,把天气信息显示在title上
      
  82.   
  83.             setTitle((String) msg.obj);  
  84.   
  85.         }  
  86.   
  87.     }  

通过消息队列改写过后的天气预告程序已经可以成功运行,因为Handler的handleMessage方法实际是由关联有该消息队列的UI thread调用,而在UI thread中更新title并没有违反Android的单线程模型的原则

抱歉!评论已关闭.