震驚瘤袖!Android子線程也能修改UI衣摩?(第二篇)

某天早晨,群里有個(gè)小伙伴這樣問了一個(gè)問題:
XXX:為什么我的控件可以在子線程里面更新
我(不假思索):你是不是在onCreate里面開了一個(gè)子線程捂敌,然后更新了UI
XXX:好像是這樣艾扮。。
我:你試試將子線程沉睡5秒鐘時(shí)間占婉,應(yīng)該就會(huì)閃退了
XXX:我試試泡嘴。
N分鐘以后......
XXX:我加了沉睡時(shí)間,還是不會(huì)閃退
我:讓我看一下截圖吧

image.png

他的onResume方法是自定義的逆济,在系統(tǒng)onResume方法中調(diào)用酌予,但是依然沒有閃退。
這個(gè)時(shí)候我的腦子也是一篇懵逼的奖慌。如果是onCreate開了子線程抛虫,然后子線程立刻更新UI,那是不會(huì)出現(xiàn)閃退的简僧。具體原因這篇文章有詳細(xì)解釋過(guò)建椰。但是沉睡5秒鐘還是能修改成功,這就讓我有點(diǎn)吃驚了涎劈。


所以我打算自己寫一個(gè)demo試試看

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(5000);
                mTvTest.setText("子線程修改UI");
            }
        }).start();
    }
image1.gif

實(shí)際測(cè)試下來(lái)好像還是會(huì)閃退,這種情況才是我認(rèn)為的現(xiàn)象阅茶。于是我把我的實(shí)驗(yàn)在群里發(fā)了一遍


我:我試了一下蛛枚,子線程修改UI是會(huì)閃退的,你是怎么做到的
XXX:我再試試脸哀。
過(guò)了一段時(shí)間
XXX:奇怪了蹦浦,我現(xiàn)在好像也試不出來(lái)了。撞蜂。盲镶。
又過(guò)了一段時(shí)間
XXX:我用的是radioGroup+radioButton侥袜,然后修改的是radioButton的文案,可以在子線程里執(zhí)行溉贿,weight設(shè)置為1枫吧,width設(shè)置為0。


上面這段對(duì)話讓我更疑惑了宇色。沒有想到原因自然是寫代碼實(shí)驗(yàn)一下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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">
    <RadioGroup
        android:id="@+id/rg_group"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal"
        app:layout_constraintTop_toTopOf="parent">
        <RadioButton
            android:id="@+id/rb_test1"
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:layout_weight="1"
            android:text="這是第一個(gè)radiobutton"/>
        <RadioButton
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:layout_weight="1"
            android:text="這是第二個(gè)radiobutton"/>
    </RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

布局文件如上寫完九杂,然后寫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的源碼抢蚀,發(fā)現(xiàn)并沒有特別之處镀层。既然還是沒找到原因,那么就debug源碼看下具體的原因皿曲。
前面的流程一切正常,然后執(zhí)行到checkForRelayout的時(shí)候就有問題了:
image.png

在checkForRelayout的方法里面唱逢,radioButton最終執(zhí)行了invalidate方法直接return掉了。根據(jù)這篇文章可知我們拋出Only the original thread that created a view hierarchy can touch its views.這個(gè)異常是在checkThread方法里面谷饿,而checkThread是由于調(diào)用了requestLayout方法惶我,這里沒有執(zhí)行requestLayout方法,自然不會(huì)崩潰博投。

  • 那么TextView是在什么地方執(zhí)行的requestLayout呢绸贡?
  • 又是什么原因?qū)е聸]有執(zhí)行requestLayout方法呢?
    我們先來(lái)看第一個(gè)問題:其實(shí)只要截圖中的兩個(gè)條件都沒有進(jìn)入就會(huì)執(zhí)行requestLayout方法
    第二個(gè)問題:回答這個(gè)問題首先看下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();
        }
    }

首先看下最外層的判斷條件毅哗,條件如果滿足的時(shí)候就不會(huì)執(zhí)行requestLayout听怕,那么什么時(shí)候滿足條件呢,需要具備以下幾個(gè)條件

  1. 寬度不是wrap_content的或者mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth
  2. mHint == null || mHintLayout != null
  3. mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)
    其實(shí)這三個(gè)條件同時(shí)滿足時(shí)就可以證明當(dāng)前的View寬度是固定的并且寬度值是大于0的虑绵。然后我們?cè)倏聪聴l件里面的代碼:
           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();

要想不執(zhí)行requestLayout方法尿瞭,那么我們首先必須滿足(mEllipsize != TextUtils.TruncateAt.MARQUEE)條件表明當(dāng)前TextView并不是走馬燈的形式。然后進(jìn)入接下來(lái)的條件

                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

這個(gè)條件要求我們?nèi)绻叨仁枪潭ㄖ档脑捘敲淳筒粫?huì)執(zhí)行requestLayout方法了翅睛。那么如果高度不是固定值怎么辦呢?接下來(lái)看下面的邏輯

                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }

當(dāng)前View的高度等于修改UI之前的高度并且HintLayout等于空或者是HintLayout的高度也等于修改UI之前的高度声搁,那么就不會(huì)執(zhí)行requestLayout。什么意思呢捕发?就是說(shuō)即便高度是不固定的疏旨,但是只要修改前后高度一致,那么一樣不會(huì)調(diào)用requestLayout扎酷。


這么看來(lái)只要View的寬度和高度在修改前后保持不變那么應(yīng)該就不會(huì)去做requestLayout的檐涝,也就是說(shuō)跟RadioButton沒有什么關(guān)系,只是恰好這么設(shè)置以后radioButton的寬高是固定的,那么再來(lái)看下高度不固定但是修改前后保持一致是否也是可以修改成功的:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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">
    <TextView
        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"/>
</androidx.constraintlayout.widget.ConstraintLayout>

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(5000);
                mTvTest.setText("子線程修改UI");
            }
        }).start();

看下這樣的運(yùn)行結(jié)果


image3.gif

在不改變高度的情況下確實(shí)是可以直接在子線程修改UI的谁榜,那再來(lái)試下修改了高度會(huì)怎么樣幅聘。這個(gè)時(shí)候我們將TextView的寬度設(shè)置小一點(diǎn),讓文案一行顯示不下窃植, 換行顯示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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">
    <TextView
        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"/>
</androidx.constraintlayout.widget.ConstraintLayout>

再來(lái)看下結(jié)果:


image4.gif

結(jié)果也是意料之中了帝蒿。這個(gè)時(shí)候TextView的內(nèi)容需要換行顯示,這個(gè)時(shí)候高度發(fā)生了變化撕瞧,那么最終就會(huì)進(jìn)入到checkThread里面去陵叽,然后報(bào)出錯(cuò)誤


總結(jié)

其實(shí)想想看,這么設(shè)計(jì)也是合情合理的丛版,既然TextView的寬高都保持不變巩掺,那么自然沒必要在去調(diào)用requestLayout方法測(cè)量它的寬高了,優(yōu)化了性能页畦。只不過(guò)這樣就直接導(dǎo)致了在子線程也可以修改文案胖替。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豫缨,隨后出現(xiàn)的幾起案子独令,更是在濱河造成了極大的恐慌,老刑警劉巖好芭,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燃箭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡舍败,警方通過(guò)查閱死者的電腦和手機(jī)招狸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)邻薯,“玉大人裙戏,你說(shuō)我怎么就攤上這事〔薰睿” “怎么了累榜?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)灵嫌。 經(jīng)常有香客問我壹罚,道長(zhǎng),這世上最難降的妖魔是什么寿羞? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任猖凛,我火速辦了婚禮,結(jié)果婚禮上稠曼,老公的妹妹穿的比我還像新娘形病。我一直安慰自己,他們只是感情好霞幅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布漠吻。 她就那樣靜靜地躺著,像睡著了一般司恳。 火紅的嫁衣襯著肌膚如雪途乃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天扔傅,我揣著相機(jī)與錄音耍共,去河邊找鬼。 笑死猎塞,一個(gè)胖子當(dāng)著我的面吹牛试读,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荠耽,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钩骇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了铝量?” 一聲冷哼從身側(cè)響起倘屹,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慢叨,沒想到半個(gè)月后纽匙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拍谐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年烛缔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赠尾。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡力穗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出气嫁,到底是詐尸還是另有隱情当窗,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布寸宵,位于F島的核電站崖面,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏梯影。R本人自食惡果不足惜巫员,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甲棍。 院中可真熱鬧简识,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颈走,卻和暖如春膳灶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背立由。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工轧钓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锐膜。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓毕箍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親道盏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霉晕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 看到標(biāo)題我想大部分人會(huì)覺得我是標(biāo)題黨颅围,怎么可能在子線程里面修改UI伟葫。先別急,慢慢往下看: 舉例 首先我們來(lái)看個(gè)例子...
    CDF_cc7d閱讀 4,645評(píng)論 2 14
  • 問題引入 Android 開發(fā)法則之一不能在子線程更新 UI,這個(gè)問題主要是 Android 關(guān)于 View 的一...
    JzyCc閱讀 692評(píng)論 0 2
  • 久違的晴天常拓,家長(zhǎng)會(huì)渐溶。 家長(zhǎng)大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了弄抬。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)茎辐。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友掂恕。感恩相遇拖陆!感恩不離不棄。 中午開了第一次的黨會(huì)懊亡,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,564評(píng)論 0 11
  • 可愛進(jìn)取依啰,孤獨(dú)成精。努力飛翔店枣,天堂翱翔速警。戰(zhàn)爭(zhēng)美好叹誉,孤獨(dú)進(jìn)取。膽大飛翔闷旧,成就輝煌桂对。努力進(jìn)取,遙望鸠匀,和諧家園∮馐粒可愛游走...
    趙原野閱讀 2,727評(píng)論 1 1