博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/smile_running

直接步入主题,今天我们来讲讲为什么在子线程无法更新 UI,这也是困扰我很久的一个问题,在我初学 Android 的那个时候,特别是在学习 Android 联网那一个章节,需要从网络上获取文本、图片等信息,然后更新到 TextView 或 ImageView 中。但是,我们在请求网络时,通常都是开了一个子线程去获取数据,等到拿到数据,开始的直接 setText(...) 或者 setImage(...) 等等,这个时候呢,它就给你来了这么一个错误:

  • Only the original thread that created a view hierarchy can touch its views.

    这段英文的意思是:只要原始创建视图的线程,才能去更新 View。那这个原始的线程,指的就是我们主线程,也就是 UI 线程,我们开启网络的线程,就称为子线程,这两个显然不是同一个线程,所以它就给我们报了这样的异常。

于是呢,我们一般都会把错误日志往搜索引擎里面一粘贴,答案就出来了。但是,这些答案都是告诉你怎么怎么做,怎么该代码才能运行,一般都不会告诉你为什么报这样的错。虽然呢,你在以后会知道不要在子线程中更新 UI 操作,但别人一问你为什么呢,却哑口无言了。

看完这篇文章,你就可以回答:为什么在子线程无法更新 UI 的问题了,下面一起进入源码探索,了解一下为什么吧。

首先呢,我们从最基础的一个 textview.setText("威威喵:https://blog.csdn.net/smile_Running"),这个方法入手,我们来看看这个方法的源码,里面做了什么事情:

//省略代码if (mLayout != null) {checkForRelayout();}//省略代码

它调用了这样的一个方法,如果布局不为空,它会去调用这个方法里面的关键一句代码

invalidate();

就是上面的方法,不仅 setText()、还有很多如 setXXX() 都会调用 invalidate() 去刷新 View。接下来我们就进入 invalidate() 的源码看看了,它会一直调用到这个方法,如下

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {if (mGhostView != null) {mGhostView.invalidate(true);return;}if (skipInvalidate()) {return;}//省略// Propagate the damage rectangle to the parent view.final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);p.invalidateChild(this, damage);}// 省略}}

我们看最关键的代码  p.invalidateChild(this, damage) ,调用了 ViewParent 接口的刷新 ChildView,进入代码看看吧,我们一路跟到了 ViewGroup 类下面的这个方法

    @Deprecated@Overridepublic final void invalidateChild(View child, final Rect dirty) {//代码省略View view = null;if (parent instanceof View) {view = (View) parent;}if (drawAnimation) {if (view != null) {view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;} else if (parent instanceof ViewRootImpl) {((ViewRootImpl) parent).mIsAnimating = true;}}parent = parent.invalidateChildInParent(location, dirty);if (view != null) {// Account for transform on current parentMatrix m = view.getMatrix();if (!m.isIdentity()) {RectF boundingRect = attachInfo.mTmpTransformRect;boundingRect.set(dirty);m.mapRect(boundingRect);dirty.set((int) Math.floor(boundingRect.left),(int) Math.floor(boundingRect.top),(int) Math.ceil(boundingRect.right),(int) Math.ceil(boundingRect.bottom));}}} while (parent != null);}}

博主跟到这里的话,发现已经下不去了,好像没看到这里搞了什么东西,也报那个异常的代码。不过呢,我们发现 ViewParent 它是一个接口,而看到它调了这样的方法  parent = parent.invalidateChildInParent(location, dirty),这里的 parent 可能是 View 的实例,也可能是 ViewRootImpl 的实例,而 View 里面显然没有这个方法,所以明显就调了 ViewRootImpl 里面的方法,进入看看吧:

    @Overridepublic ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);if (dirty == null) {invalidate();return null;} else if (dirty.isEmpty() && !mIsAnimating) {return null;}if (mCurScrollY != 0 || mTranslator != null) {mTempRect.set(dirty);dirty = mTempRect;if (mCurScrollY != 0) {dirty.offset(0, -mCurScrollY);}if (mTranslator != null) {mTranslator.translateRectInAppWindowToScreen(dirty);}if (mAttachInfo.mScalingRequired) {dirty.inset(-1, -1);}}invalidateRectOnScreen(dirty);return null;}

好了,目的地已经到了,就是那第一句代码:checkThread() 方法,来看看它干了什么

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

吼吼,你这个小东西藏在这里啊,这就够明显的吧,它是获取到了当前线程的实例,比如我们在子线程中去 setText() 的话,那就不等于 mThread 了,这个 mThread 就是我们的主线程,即 UI 线程。这下总可以回答:为什么在子线程无法更新 UI 这个问题了吧,哈哈。

【源码】让源码告诉你:为什么在子线程无法更新 UI 操作?相关推荐

  1. 【Mybatis源码】源码分析

    [Mybatis源码]源码分析 (一)Mybatis重要组件 [1]四大核心组件 (1)SqlSessionFactoryBuilder (2)SqlSessionFactory (3)SqlSess ...

  2. 网狐卓越版本内核引擎、卓越内核(源码,源码,源码)

    经过一段时间折腾,终于搞定网狐卓越版本内核引擎.源码,源码,源码. 话不多说,先上图. 代码截图 编译成功 运行效果图 下面来介绍逆向过程. 查看网狐给的头文件 KernelEngineHead.h. ...

  3. java 头尾 队列_源码|jdk源码之栈、队列及ArrayDeque分析

    栈.队列.双端队列都是非常经典的数据结构.和链表.数组不同,这三种数据结构的抽象层次更高.它只描述了数据结构有哪些行为,而并不关心数据结构内部用何种思路.方式去组织. 本篇博文重点关注这三种数据结构在 ...

  4. Flink源码分析 - 源码构建

    本篇文章首发于头条号Flink源码分析 - 源码构建,欢迎关注我的头条号和微信公众号"大数据技术和人工智能"(微信搜索bigdata_ai_tech)获取更多干货,也欢迎关注我的C ...

  5. Dapper源码学习和源码修改(下篇)

    继上篇Dapper源码学习和源码修改 讲了下自己学习Dapper的心得之后,下篇也随之而来,上篇主要讲的入参解析那下篇自然主打出参映射了. 好了,废话不多说,开始吧. 学习之前你的先学习怎么使用Dap ...

  6. Android之HandlerThread源码分析和简单使用(主线程和子线程通信、子线程和子线程通信)

    1.先熟悉handler方式实现主线程和子线程互相通信方式,子线程和子线程的通信方式 如果不熟悉或者忘记了,请参考我的这篇博客     Android之用Handler实现主线程和子线程互相通信以及子 ...

  7. [Android源码]Android源码之高仿飞鸽传书WIFI热点搜索与创建(一)

    (本文详情来源:android源码 http://www.eoeandroid.com/thread-296427-1-1.html   转载请注明出处!)  [Android源码分享]飞鸽传书的An ...

  8. java字节码文件加密_java 字节码加密源码

    java 字节码加密源码 java 2021-2-16 下载地址 https://www.codedown123.com/73152.html java 字节码加密源码,实现对class加密解密 资源 ...

  9. HTML五合一收款码网站源码(带35套模板)

    简介: HTML五合一收款码网站源码(带35套模板)是一款基于HTML开发制作的多码合一收款码生成网站源码,支持wx支付,支付宝支付,手机扣扣支付,京东钱包,百度钱包,五合一收款,将其二维码合并为一个 ...

最新文章

  1. Angular7中引用外部JS文件
  2. python day two,while
  3. 深挖谷歌 DeepMind 和它背后的技术
  4. Spring对AOP的支持
  5. 戴尔服务:为企业转型导航
  6. hdu - 2512 一卡通大冒险 (斯特灵数 贝尔数)
  7. Ubuntu 下 apt-get 命令
  8. Linux下用at计划任务
  9. [导入]PHP通用分页类
  10. ABP vNext中使用开源日志面板 LogDashboard
  11. Java通过cal.get(Calendar.MONTH)比真实月份少一个月
  12. 【阅读】《点石成金:访客至上的网页设计秘籍》读书笔记
  13. 华为WATCH D血压管理计划怎么用
  14. 数字签名 —— 哈希 + 私钥加密
  15. 点云匹配方法NDT(正态分布变换)
  16. AOPlog4j2propagation的7种事务配置
  17. IOS 点击空白处隐藏键盘的几种方法
  18. Day 17 - YOLO 相关概念说明
  19. 【宅男福利】百度云下载不限速软件,电脑和看视频无广告软件
  20. FL Studio 教程之显示按钮简介

热门文章

  1. iTOL:快速绘制超高颜值的进化树!
  2. 手机 听广播 不用 耳机 android,无需插入耳机即可收听FM广播的高级提示
  3. 批量修改CloudFlare上的域名的DNS - by PHP
  4. Sqlserver面试题
  5. Ubuntu各个版本换源方法
  6. [SQL]将子查询作为查询条件
  7. 浮点数和定点数的相互转换(浮点数量化为定点)
  8. 【高速数字化仪应用案例系列】虹科Spectrum在天文领域的应用
  9. UnityWebRequest 加载网络图片当作贴图给物体
  10. android build.version,android兼容性.使用Build.VERSION_CODES时我很困惑