zoukankan      html  css  js  c++  java
  • Android 编程:calledfromWrongThreadException 的原因

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

    子线程更新UI

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

     public void onClick(View v) {
    
           //创建一个子线程执行耗时的从网络上获得天气信息的操作
    
           new Thread() {
    
               @Override
    
               public void run() {
    
                  //获得用户输入的城市名称
    
                  String city = editText.getText().toString();
    
                  //调用Google 天气API查询指定城市的当日天气情况
    
                  String weather = getWetherByCity(city);
    
                  //把天气信息显示在title上
    
                  setTitle(weather);
    
               }
    
           }.start();
    
        }

    但是很不幸,你会发现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之间的关系可以通过一个图来展现:

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

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

    private EditText editText;
    
        private Handler messageHandler;
    
        @Override
    
        public void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.main);
    
            editText = (EditText) findViewById(R.id.weather_city_edit);
    
            Button button = (Button) findViewById(R.id.goQuery);
    
            button.setOnClickListener(this);
    
            //得到当前线程的Looper实例,由于当前线程是UI线程也可以通过Looper.getMainLooper()得到
    
            Looper looper = Looper.myLooper();
    
            //此处甚至可以不需要设置Looper,因为 Handler默认就使用当前线程的Looper
    
            messageHandler = new MessageHandler(looper);
    
        }
    
     
    
        @Override
    
        public void onClick(View v) {
    
            //创建一个子线程去做耗时的网络连接工作
    
            new Thread() {
    
                @Override
    
                public void run() {
    
                    //活动用户输入的城市名称
    
                    String city = editText.getText().toString();
    
                    //调用Google 天气API查询指定城市的当日天气情况
    
                    String weather = getWetherByCity(city);
    
                    //创建一个Message对象,并把得到的天气信息赋值给Message对象
    
                    Message message = Message.obtain();
    
                    message.obj = weather;
    
                    //通过Handler发布携带有天气情况的消息
    
                    messageHandler.sendMessage(message);
    
                }
    
            }.start();
    
        }
    
     
    
        //子类化一个Handler
    
        class MessageHandler extends Handler {
    
            public MessageHandler(Looper looper) {
    
                super(looper);
    
            }
    
            @Override
    
            public void handleMessage(Message msg) {
    
                //处理收到的消息,把天气信息显示在title上
    
                setTitle((String) msg.obj);
    
            }
    
        }
    
    
    通过消息队列改写过后的天气预告程序已经可以成功运行,因为Handler的handleMessage方法实际是由关联有该消息队列的UI thread调用,而在UI thread中更新title并没有违反Android的单线程模型的原则。
  • 相关阅读:
    Redis学习——数据结构下
    CI 笔记7,easyui 异步加载
    CI 笔记,借鉴的4个辅助自定义函数
    CI笔记6 json 传值
    CI 笔记,使用 json的参考文档(废弃)
    CI 笔记5 (CI3.0 默认控制器,多目录)
    CI 笔记(easyui js命令)
    CI 笔记4 (easyui 手风琴)
    CI 笔记3 (easyui 的layout布局,最小化layout原型)
    CI 笔记3 (easyui 和 js 排错)
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/5584162.html
Copyright © 2011-2022 走看看