某天早晨,群里有个小伙伴这样问了一个问题:

XXX:为什么我的控件可以在子线程里面更新

我(不假思索):你是不是在onCreate里面开了一个子线程,然后更新了UI

XXX:好像是这样。。

我:你试试将子线程沉睡5秒钟时间,应该就会闪退了

XXX:我试试。

N分钟以后......

XXX:我加了沉睡时间,还是不会闪退

我:让我看一下截图吧

image.png

他的onResume方法是自定义的,在系统onResume方法中调用,但是依然没有闪退。

这个时候我的脑子也是一篇懵逼的。如果是onCreate开了子线程,然后子线程立刻更新UI,那是不会出现闪退的。具体原因这篇文章有详细解释过。但是沉睡5秒钟还是能修改成功,这就让我有点吃惊了。

所以我打算自己写一个demo试试看

@Override

protected void onResume() {

super.onResume();

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(5000);

mTvTest.setText("子线程修改UI");

}

}).start();

}

image1.gif

实际测试下来好像还是会闪退,这种情况才是我认为的现象。于是我把我的实验在群里发了一遍

我:我试了一下,子线程修改UI是会闪退的,你是怎么做到的

XXX:我再试试。

过了一段时间

XXX:奇怪了,我现在好像也试不出来了。。。

又过了一段时间

XXX:我用的是radioGroup+radioButton,然后修改的是radioButton的文案,可以在子线程里执行,weight设置为1,width设置为0。

上面这段对话让我更疑惑了。没有想到原因自然是写代码实验一下:

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

android:id="@+id/rg_group"

android:layout_width="match_parent"

android:layout_height="30dp"

android:orientation="horizontal"

app:layout_constraintTop_toTopOf="parent">

android:id="@+id/rb_test1"

android:layout_width="0dp"

android:layout_height="30dp"

android:layout_weight="1"

android:text="这是第一个radiobutton"/>

android:layout_width="0dp"

android:layout_height="30dp"

android:layout_weight="1"

android:text="这是第二个radiobutton"/>

布局文件如上写完,然后写java代码:

@Override

protected void onResume() {

super.onResume();

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(5000);

mRbTest1.setText("子线程修改UI");

}

}).start();

}

run一下看下效果

image2.gif

竟然真的修改成功了!

这下就比较懵逼了,radioButton可以修改成功,难道radioButton做了什么特殊的处理么?随手去翻了一下radioButton的源码以及父类CompoundButton的源码,发现并没有特别之处。既然还是没找到原因,那么就debug源码看下具体的原因。

前面的流程一切正常,然后执行到checkForRelayout的时候就有问题了:

image.png

在checkForRelayout的方法里面,radioButton最终执行了invalidate方法直接return掉了。根据这篇文章可知我们抛出Only the original thread that created a view hierarchy can touch its views.这个异常是在checkThread方法里面,而checkThread是由于调用了requestLayout方法,这里没有执行requestLayout方法,自然不会崩溃。

那么TextView是在什么地方执行的requestLayout呢?

又是什么原因导致没有执行requestLayout方法呢?

我们先来看第一个问题:其实只要截图中的两个条件都没有进入就会执行requestLayout方法

第二个问题:回答这个问题首先看下checkForRelayout的完整代码:

/**

* Check whether entirely new text requires a new view layout

* or merely a new text layout.

*/

@UnsupportedAppUsage

private void checkForRelayout() {

if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT

|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))

&& (mHint == null || mHintLayout != null)

&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {

...代码省略...

} else {

// Dynamic width, so we have no choice but to request a new

// view layout with a new text layout.

nullLayouts();

requestLayout();

invalidate();

}

}

首先看下最外层的判断条件,条件如果满足的时候就不会执行requestLayout,那么什么时候满足条件呢,需要具备以下几个条件

宽度不是wrap_content的或者mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth

mHint == null || mHintLayout != null

mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)

其实这三个条件同时满足时就可以证明当前的View宽度是固定的并且宽度值是大于0的。然后我们再看下条件里面的代码:

int oldht = mLayout.getHeight();

int want = mLayout.getWidth();

int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

/*

* No need to bring the text into view, since the size is not

* changing (unless we do the requestLayout(), in which case it

* will happen at measure).

*/

makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,

mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),

false);

if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {

// In a fixed-height view, so use our new text layout.

if (mLayoutParams.height != LayoutParams.WRAP_CONTENT

&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {

autoSizeText();

invalidate();

return;

}

// Dynamic height, but height has stayed the same,

// so use our new text layout.

if (mLayout.getHeight() == oldht

&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {

autoSizeText();

invalidate();

return;

}

}

// We lose: the height has changed and we have a dynamic height.

// Request a new view layout using our new text layout.

requestLayout();

invalidate();

要想不执行requestLayout方法,那么我们首先必须满足(mEllipsize != TextUtils.TruncateAt.MARQUEE)条件表明当前TextView并不是走马灯的形式。然后进入接下来的条件

if (mLayoutParams.height != LayoutParams.WRAP_CONTENT

&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {

autoSizeText();

invalidate();

return;

}

这个条件要求我们如果高度是固定值的话那么就不会执行requestLayout方法了。那么如果高度不是固定值怎么办呢?接下来看下面的逻辑

if (mLayout.getHeight() == oldht

&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {

autoSizeText();

invalidate();

return;

}

当前View的高度等于修改UI之前的高度并且HintLayout等于空或者是HintLayout的高度也等于修改UI之前的高度,那么就不会执行requestLayout。什么意思呢?就是说即便高度是不固定的,但是只要修改前后高度一致,那么一样不会调用requestLayout。

这么看来只要View的宽度和高度在修改前后保持不变那么应该就不会去做requestLayout的,也就是说跟RadioButton没有什么关系,只是恰好这么设置以后radioButton的宽高是固定的,那么再来看下高度不固定但是修改前后保持一致是否也是可以修改成功的:

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

android:id="@+id/tv_test"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_marginTop="50dp"

android:text="text"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent"

app:layout_constraintEnd_toEndOf="parent"/>

@Override

protected void onResume() {

super.onResume();

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(5000);

mTvTest.setText("子线程修改UI");

}

}).start();

看下这样的运行结果

image3.gif

在不改变高度的情况下确实是可以直接在子线程修改UI的,那再来试下修改了高度会怎么样。这个时候我们将TextView的宽度设置小一点,让文案一行显示不下, 换行显示:

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity">

android:id="@+id/tv_test"

android:layout_width="30dp"

android:layout_height="wrap_content"

android:layout_marginTop="50dp"

android:text="text"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent"

app:layout_constraintEnd_toEndOf="parent"/>

再来看下结果:

image4.gif

结果也是意料之中了。这个时候TextView的内容需要换行显示,这个时候高度发生了变化,那么最终就会进入到checkThread里面去,然后报出错误

总结

其实想想看,这么设计也是合情合理的,既然TextView的宽高都保持不变,那么自然没必要在去调用requestLayout方法测量它的宽高了,优化了性能。只不过这样就直接导致了在子线程也可以修改文案。

android线程改变布局,震惊!Android子线程也能修改UI?(第二篇)相关推荐

  1. Android线程之主线程向子线程发送消息

    和大家一起探讨Android线程已经有些日子了,谈的最多的就是如何把子线程中的数据发送给主线程进行处理,进行UI界面的更新,为什么要这样,请查阅之前的随笔.本篇我们就来讨论一下关于主线程向子线程如何发 ...

  2. handler回调主线程_Android使用Handler实现子线程与子线程、子线程与主线程之间通信...

    转载:https://blog.csdn.net/shaoenxiao/article/details/54561753 今天这篇文章只讲一下怎么使用Handler实现子线程与子线程之间.子线程与主线 ...

  3. 【EventBus】事件通信框架 ( 发送事件 | 判断发布线程是否是主线程 | 子线程切换主线程 | 主线程切换子线程 )

    文章目录 前言 一.根据不同的线程模式进行不同的线程切换操作 二.完整代码示例 前言 发布线程发布事件之后 , 消息中心需要转发这些事件 , 并执行相应的订阅方法 ; 在转发的过程中 , 需要针对订阅 ...

  4. java 异常处理线程_转:Java子线程中的异常处理(通用)

    在普通的单线程程序中,捕获异常只需要通过try ... catch ... finally ...代码块就可以了.那么,在并发情况下,比如在父线程中启动了子线程,如何正确捕获子线程中的异常,从而进行相 ...

  5. 【Android 异步操作】Android 线程切换 ( 判定当前线程是否是主线程 | 子线程中执行主线程方法 | 主线程中执行子线程方法 )

    文章目录 一.判定当前线程是否是主线程 二.子线程中执行主线程方法 三.主线程中执行子线程方法 一.判定当前线程是否是主线程 在 Android 中 , 如果要判定当前线程是否是主线程 , 可以使用如 ...

  6. 适用于android 4.0以上版本的子线程网络图片查看器

    android 4.0版本的新特性之一:加载网络内容时会自动判断是否在主线程中运行. 并且获取到内容时不能直接在子线程中设置主线程中的View,会报出以下异常 异常: CalledFromWrongT ...

  7. Android攻城狮 Handler与子线程

    Handler和线程是相关联的.再次强调:不可以在主线程中做耗时操作,就比如示例里面主线程中的 handlerMessage(),不能执行耗时操作.创建一个与自定义线程相关的Handler的代码: c ...

  8. Android入门第37天-在子线程中调用Handler

    简介 前一章我们以一个简单的小动画来解释了Handler. 这章我们会介绍在子线程里写Handler.如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了:创建的流程如下: ...

  9. Android进阶:六、在子线程中直接使用 Toast 及其原理

    最近因为个人原因,很久没有更文章,感谢关注的小伙伴,望谅解! 一般我们都把Toast当做一个UI控件在主线程显示.但是有时候非想在子线程中显示Toast,就会使用Handler切换到主线程显示. 但是 ...

最新文章

  1. Qt WebSocket服务端的简单Demo
  2. dot全称_dot是什么币
  3. 记一次特别的往事 while 循环
  4. java 及时释放内存_Java 内存释放
  5. springboot配置mybatis
  6. 自己封装一个弹框插件
  7. ubuntu11.10 源码编译安装php5.3.8,Ubuntu 11.10编译安装Nginx、PHP 5.3.8、MySQL、MongoDB、Memcached、SSL、SMTP...
  8. Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析(2)...
  9. BZOJ3324 : [Scoi2013]火柴棍数字
  10. 这几款图片格式转换器帮助你轻松解决图片转换难题
  11. LeedCode 717 1比特与2比特字符
  12. Splunk Filed Alias 字段改名
  13. Android开机启动流程
  14. 从高级程序员-资深程序员-技术总监,我都为你整理好了学习路径
  15. 如何判断建设用地是否符合土地利用总体规划?
  16. 游戏金币数据库:QPTreasureDB
  17. matlab 模糊隶属度判断类别
  18. leetcode526 优美的排列
  19. luogu2774 方格取数问题 二分图最小权点覆盖集
  20. 根据开始时间与结束时间,计算季度

热门文章

  1. JavaScript设计模式——订阅者-发布者模式简单实现
  2. 程序员潜规则之痛——“JAVA 之父”也遭遇过
  3. 2021届计算机女硕士秋招面试惨痛经历完结
  4. python3 下载特定网页上的文件
  5. 安装ROS时遇到的一些坑,做个笔记方便自己查看
  6. HTTP 301和302状态码
  7. linux操作系统下c语言编程入门
  8. 阿里云大数据之MaxComputer简介
  9. YYDS《剑指Offer》再续新篇,百万程序员人手一册
  10. Java中Character(类型char)类及类型详解