”只能在UI主线程中更新View“。

这句话很熟悉吧?

来来,哥们,看一下下面的例子

@Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        tv = (TextView) findViewById(R.id.tv);  
        Thread.currentThread().setName("UIThread");  
          
        new LooperThread("非主线程修改").start();  
    }  
  
    private class LooperThread extends Thread {  
  
        private String text;  
  
        public LooperThread(String text) {  
            this.text = text;  
        }  
  
        @Override  
        public void run() {  
            Thread.currentThread().setName("OtherThread");  
            tv.setText(text);  
        }  
    }  

代码这么写,不是逗比吗!肯定崩啊!但是,如果你试一下,你会发现,绝大多数是不会崩的。至于极少数会崩溃的原因,我一会再说。

你可能会很疑惑,不是”只能在UI主线程中更新View“吗?你这个在子线程里面更新View,为什么不会崩呢?

那么,你再看下面的代码,这样写就肯定崩

public void changeNoUI(View view) {  
     new LooperThread("非主线程修改").start();  

调用的代码和上面的一样,只不过是我们是给一个Button设置了点击事件,然后自己手动调用的,这样就肯定会崩溃,报什么错呢?

报下面的错

02-02 16:44:38.786: E/AndroidRuntime(17907): FATAL EXCEPTION: OtherThread  
02-02 16:44:38.786: E/AndroidRuntime(17907): Process: com.example.demo, PID: 17907  
02-02 16:44:38.786: E/AndroidRuntime(17907): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6226)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:883)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.view.ViewGroup.invalidateChild(ViewGroup.java:4320)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.view.View.invalidate(View.java:10947)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.view.View.invalidate(View.java:10902)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.widget.TextView.checkForRelayout(TextView.java:6673)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.widget.TextView.setText(TextView.java:3860)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.widget.TextView.setText(TextView.java:3718)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at android.widget.TextView.setText(TextView.java:3693)  
02-02 16:44:38.786: E/AndroidRuntime(17907):    at com.example.demo.MainActivity$LooperThread.run(MainActivity.java:78)  

意思就是说,只有创建View层次结构的线程才能修改View,我们在非UI主线程里面更新了View,所以会报错。

但是你还没说为什么第一种调用方法为什么不报错啊!!!

先别着急,先看一下上面的报错信息,里面有好多东西可以研究呢!

我们先从下面开始看,首先是LooperThread.run()报错了,为什么报错呢,再往上,因为我们调用setText了,再往上就是TextView.checkForRelayout,然后上面是invalidate,我们修改了文字,肯定要invalidate啊,谁调用的呢?原来是ViewGroup.invalidateChild,往上找啊找,终于找到了罪魁祸首,ViewRootImpl.checkThread()报错了!

ViewRootImpl是什么玩意?这个玩意可很强大,你现在就只需要知道它能做很多和界面有关的事情就ok了,其实我对这个类也是只了解一点点。。。

checkThread()到底做了什么啊,就报错!ViewRootImpl是一个隐藏类,我们只能去看源码,源码如下

void checkThread() {  
       if (mThread != Thread.currentThread()) {  
           throw new CalledFromWrongThreadException(  
                   "Only the original thread that created a view hierarchy can touch its views.");  
       }  
   } 

  就是这个方法报错了!ViewRootImpl是依附在AttachInfo这个类上的,而AttachInfo是View得一个隐藏类,你在Eclipse里面是看不到的,需要直接看framework得源码。所以说,这里的Thread.currentThread()是UI主线程,就是这里判断是不是我们在非UI主线做了修改VIew的操作。

那么问题又来了,为啥第一种方式,不会报错呢!!!

ok,不要抓狂,咱们先回顾一下Activity的生命周期。靠!和Activity的生命周期怎么又扯上关系了?

Activity在onCreate中进行界面的数据准备,onStart()之后,Activity的界面就对用户可见了,那么知道这些有什么用呢?当然有用!上面两种调用方式的差别就在调用的时机不同!一个是在onCreate中开启线程调用,一个是我们手动调用,暗示着,第二种方法是在onResume之后调用!

这种调用时机的差别就决定了代码中setText的意义!

第一种做法中,虽然是在子线程中setText,但是这时候View还没画出来呢,所以并不会调用之后的invalidate,而相当于只是设置TextView的一个属性,不会invalidate,就没有后面的那些方法调用了,归根结底,就不会调用ViewRootImpl的checkThread,也就不会报错。而第二种方法,调用setText之后,就会引发后面的一系列的方法调用,VIew要刷新界面,ViewGroup要更新布局,计算子View的大小位置,到最后,ViewRootImpl就会checkThread,就崩了。

所以,严格上来说,第一种方法虽然在子线程了设置View属性,但是不能够归结到”更新View“的范畴,因为还没画出来呢,就没有所谓的更新。

那么,为啥说绝大多数不会报错呢?这是因为,如果我们在onCreate开启子线程之后,在子线程又执行了耗时操作,比如Thread.sleep,那么子线程再调用setText的时候,就会崩溃,因为这时候onStart、onResume都执行了,你再想在子线程更新View,那么门都没有!

不知道有没有同学会这样想:我记得在子线程里面用Toast也会报错,加上Looper.prepare和Looper.loop就可以了,这里可以这样做吗?

答案当然是不可以。

Toast和View本质上是不一样的,Toast在子线程报错,是因为Toast的显示需要添加到一个MessageQueue中,然后Looper取出来,发给Handler调用显示,子线程因为没有Looper,所以需要加上Looper.prepare和Looper.loop创建一个Looper,但是实质上,这还是在子线程调用,所以还是会报错的!

那么为什么Android要求只能在UI主线程中更改View呢?这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。

ok,希望上面说到的这些能对你有所帮助。

android 在非UI线程更新UI仍然成功原因深入剖析的更多相关文章

  1. Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

    Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...

  2. Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面

    目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...

  3. 学习通过Thread+Handler实现非UI线程更新UI组件

    [Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...

  4. 学习通过Thread+Handler实现非UI线程更新UI组件(转)

    [Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则 ...

  5. [Android学习笔记]子线程更新UI线程方法之Handler

    关于此笔记 不讨论: 1.不讨论Handler实现细节 2.不讨论android线程派发细节 讨论: 子线程如何简单的使用Handler更新UI 问题: android开发时,如何在子线程更新UI? ...

  6. Android开发——实现子线程更新UI

    Android中线程按功能分的话,可以分为两个,一个是主线程(UI线程),其他的都是子线程 主线程不能执行那些耗时过长的代码或任务(执行耗时过长的代码会出现应用未响应的提示),所以都是使用子线程来执行 ...

  7. Winform非UI线程更新UI界面的各种方法小结

    我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在进行异步操作时,经常需要将异步执行的进度报告给用户,让用户知道任务的进度,不至于让用户误认为程序“死掉了”,特别是对 ...

  8. 非UI线程更新UI界面的各种方法小结

    转载:https://www.cnblogs.com/xiashengwang/archive/2012/08/18/2645541.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控 ...

  9. WPF 非UI线程更新UI界面的各种方法小结

    转载:https://www.cnblogs.com/bdbw2012/articles/3777594.html 我们知道只有UI线程才能更新UI界面,其他线程访问UI控件被认为是非法的.但是我们在 ...

随机推荐

  1. python GIL(Global Interpreter Lock)

    一 介绍 ''' 定义: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple nati ...

  2. 剑指offer——面试题30:包含min函数的栈

    #include"iostream" #include"stdio.h" using namespace std; ; ; template<typena ...

  3. java翻译到mono C#实现系列(4) 利用CountDownTimer类实现倒计时功能 mono版

    群里的朋友问利用CountDownTimer类实现倒计时功能怎么实现,我就百度了,参考http://blog.csdn.net/qq344429461/article/details/7521361写 ...

  4. C++11 实现生产者消费者双缓冲

    基础的生产者消费者模型,生产者向公共缓存区写入数据,消费者从公共缓存区读取数据进行处理,两个线程访问公共资源,加锁实现数据的一致性. 通过加锁来实现 class Produce_1 { public: ...

  5. 【Maven学习】maven中依赖的配置详解

    根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或多个项目依赖.每个依赖可以包含的元素有: groupId,artifactId和version ...

  6. 天猫消息盒子的CSS实现

    css: body,h2,h3,ul,p{margin:0;padding:0;font-size:12px;} li{list-style: none; } a{text-decoration: n ...

  7. Linux防火墙简介 – iptables配置策略

    Linux防火墙简介 – iptables配置策略 Netfilter/iptables简介 要想真正掌握Linux防火墙体系,首先要搞清楚Netfilter和iptables的关系,Netfilte ...

  8. <数据挖掘导论>读书笔记10聚类分析续

    基于原型的聚类 模糊c均值使用模糊逻辑和模糊集合论的概念,提出一种聚类方案,它很像K均值,但是不需要硬性地将对象分派到一个簇中.模糊c均值算法有时也称为FCM 混合模型聚类采取这样的访谈,簇集合可以用 ...

  9. SQL基本操作(工作中够用了)

      以下文章内容都是我自己从平时学习SQL语言时整理而来,写这篇文章是希望我或大家在使用能更方便的查询. 如果有不完整或不正确的地方请大家指出~谢谢大家 基本SQL操作 创建数据库 CREATE DA ...

  10. Java实现进程调度算法(一) FCFS(先来先服务)

    一.概述 因为这次os作业对用户在控制台的输入输出有要求,所以我花了挺多的代码来完善控制台的显示. 也因为我这次要实现多个类似算法,所以将一些共性单独提取出来作为一个类. 如果只想要和算法有关的核心代 ...