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

Android 编程:calledfromWrongThreadException 的原因的更多相关文章

  1. 读《Android编程权威指南》

    因为去年双十二购买了一折的<Android 编程权威指南(第一版)>,在第二版出来后图灵社区给我推送了第二版的优惠码,激动之余就立马下单购买电子书,不得不说Big Nerd Ranch G ...

  2. 菜鸟学Android编程——简单计算器《一》

    菜鸟瞎搞,高手莫进 本人菜鸟一枚,最近在学Android编程,网上看了一些视频教程,于是想着平时手机上的计算器应该很简单,自己何不尝试着做一个呢? 于是就冒冒失失的开撸了. 简单计算器嘛,功能当然很少 ...

  3. [电子书] 《Android编程入门很简单》

    <Android编程入门很简单>是一本与众不同的Android学习读物,是一本化繁为简,把抽象问题具体化,把复杂问题简单化的书.本书避免出现云山雾罩.晦涩难懂的讲解,代之以轻松活泼.由浅入 ...

  4. Eclipse下Android编程代码自动提示

    在用Eclipse进行Android编程,为了代码自动提示,需要进行如下操作: 1.设置 java 文件的代码提示功能 打 开 Eclipse 依次选择 Window > Preferences ...

  5. Android编程心得-在任意类中获取当前屏幕宽高

    进行Android编程时,很多时候都需要获取当前屏幕的宽度与高度,但是当我们需要在别的类中调用屏幕宽高时,直接用原来的方法是不行的,下面我来介绍如何在任意类中调用宽度高度的两种方法. public v ...

  6. Android编程中的实用快捷键

    作为一个优秀的程序员,不但要能开发出漂亮的软件,也要能熟练掌握编程的技巧,包括IDE的快捷键使用.比如linux 下的VI编辑器,对于不熟练快捷键的人来说就是一个噩梦,但一旦你熟练了VI的快捷键,VI ...

  7. Android编程中的5种数据存储方式

    Android编程中的5种数据存储方式 作者:牛奶.不加糖 字体:[增加 减小] 类型:转载 时间:2015-12-03我要评论 这篇文章主要介绍了Android编程中的5种数据存储方式,结合实例形式 ...

  8. 《Android编程权威指南》

    <Android编程权威指南> 基本信息 原书名:Android programming: the big nerd ranch guide 原出版社: Big Nerd Ranch Gu ...

  9. 《Delphi XE6 android 编程入门教程》推荐

    近5.6年已经没有看见关于delphi的新技术的书出来了(看来在国内delphi的使用量确实很低了), 高勇同学最近出了一本<Delphi XE6 android 编程入门教程>,上周刚拿 ...

  10. Android编程 EditView 中如何设置最多可以输入的字符数量 属性 android:ems 与 android:maxLength 的区别

    最近有一个新的感悟,那就是工作的时候千万不要遇到那种特要人无语的领导,很不幸我现在就遇到了这样的一个领导,说是要给领导认识的一个熟人家的孩子写本科毕业设计预算把我给派过去给本科生写毕业设计,这事情的确 ...

随机推荐

  1. IT项目外包有哪些注意事项

    2015年两会上,李克强总理在政府工作报告中首次提出“互联网+”计划,各种互联网创业项目也如雨后春笋般破土而出.由于很多创业团队并没有自己的技术团队,所以软件外包成为很多创业项目普遍采取的一种方案.在 ...

  2. jquery $.each()用法

    今天看到一个新的each玩法即each作为jquery的函数(平时用的大概都是用的each方法)使用: $.each([ 52, 97 ], function( index, value ) { al ...

  3. Java向上转型注意事项

    继承.接口:Java子类中如果含有父类中不包含的变量与方法,子类对象向上转型时就是丢失这些变量和方法. interface SuperClass{ int i = 2; void f() ; } cl ...

  4. SqlServer 挂载本地盘符到服务器端,方面备份还原

    --此脚本要在登录上SqlServer远程服务器后才能执行,把数据库备份到本地(内网使用) --(例如我用本机SqlServer客户端连接上192.168.3.12服务器时,执行脚本,可以把指定数据库 ...

  5. Javascript:charCodeAt()方法及示例

    js charCodeAt()函数说明 返回一个整数,代表指定位置上字符的 Unicode 编码.该Unicode编码为十六进制 strObj.charCodeAt(index) 参数: strObj ...

  6. static——第一次执行与它以后执行时结果不一样

    void generate_initializer(char* string); int _tmain(int argc, _TCHAR* argv[]) { "}; ; i < ; ...

  7. nginx 配置访问正则匹配

    server{ listen 80; server_name api.zyy.com; root /var/www/api_zyy; index index.php; location ~ /asse ...

  8. php图片上面写文字,输出图片

    <?php /* param $image 图象资源 param size 字体大小 param angle 字体输出角度 param showX 输出位置x坐标 param showY 输出位 ...

  9. Activity一共有以下四种launchMode

    1. standard: 无论什么情况都会生成一个新的Activity实例,并且放于栈顶. 2. singleTop:如果Activity纯在但是不位于栈顶,就重新生成一个Activity实例. 3. ...

  10. [c#]asp.net开发微信公众平台(2)多层架构框架搭建和入口实现

    上篇已经设计出比较完善的数据库了,这篇开始进入代码.  首先把上篇设计的数据库脚本在数据库中执行下,生成数据库,然后在VS中建立项目,为了方便理解和查看,我设计的都是很直白的类名和文件名,没有命名空间 ...