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

    http://blog.csdn.net/moonlit1228/article/details/6399878

    子线程更新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的单线程模型的原则。

    Keep it simple!
    作者:N3verL4nd
    知识共享,欢迎转载。
  • 相关阅读:
    leetcode 279. Perfect Squares
    leetcode 546. Remove Boxes
    leetcode 312. Burst Balloons
    leetcode 160. Intersection of Two Linked Lists
    leetcode 55. Jump Game
    剑指offer 滑动窗口的最大值
    剑指offer 剪绳子
    剑指offer 字符流中第一个不重复的字符
    leetcode 673. Number of Longest Increasing Subsequence
    leetcode 75. Sort Colors (荷兰三色旗问题)
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5834746.html
Copyright © 2011-2022 走看看