[028]子線程能否操作UI控件

前言

這是一個初級Android工程師面試問題,一般標(biāo)準(zhǔn)答案:子線程不能操作UI控件纫版。
那我為什么還要問這個弱智的問題呢?

因為我心目中的標(biāo)準(zhǔn)答案:子線程不能操作"參與繪制"的UI控件匹耕。

一、什么是操作UI

如何理解我的標(biāo)準(zhǔn)答案恋捆,首先回答一下,什么叫做操作UI ?
以TextView為例:

TextView.setText("OK");//操作1
TextView.setBackgroundColor(0xffffff);//操作2
TextView.invalidate();//操作3

其實操作1和操作2最終也是調(diào)用操作3,操作3才是真正刷新界面的代碼偷仿。
“操作UI”理解成“調(diào)用View.invalidate()”。

問題也就變成了:子線程能否調(diào)用View.invalidate()宵蕉。

二酝静、View.invalidate()

接下來分析一下View.invalidate()代碼

代碼2.1
    public void invalidate() {
        invalidate(true);
    }

    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        ....
        if (skipInvalidate()) {//判斷是否需要跳過繪制
            return;
        }

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
           ...
            // 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);//調(diào)用ViewParent的invalidateChild
            }
           ...
        }
    }

    private boolean skipInvalidate() {
        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
                (!(mParent instanceof ViewGroup) ||
                        !((ViewGroup) mParent).isViewTransitioning(this));
    }

// Propagate the damage rectangle to the parent view.
這句注釋其實很有意思,翻譯:將損壞矩形復(fù)制到父視圖羡玛。其實這個damage有助于提高繪制的效率别智,有興趣的朋友可以自己研究,以后我也會講這塊繪制的內(nèi)容稼稿,敬請期待薄榛。

言歸正傳,整個調(diào)用邏輯比較清晰可以看到

invalidate()->invalidate(true)->p.invalidateChild(this, damage);

按照一個Activity中View的嵌套關(guān)系让歼,我們可以知道整個調(diào)用過程變成了

invalidate()->invalidate(true)->DecorView.invalidateChild(this, damage)->ViewRootImpl.invalidateChild(this, damage);

三敞恋、ViewRootImpl.invalidateChild

繼續(xù)研究ViewRootImpl.invalidateChild的代碼邏輯

@Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();//最關(guān)鍵的線程檢查,如果是子線程就拋出異常了
        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;
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {//mThread為主線程谋右,也就是UI線程
            //子線程就拋出異常了
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

整個調(diào)用邏輯invalidateChild-> invalidateChildInParent-> checkThread
在checkThread中硬猫,如果發(fā)現(xiàn)當(dāng)前的線程Thread.currentThread不等于mThread(主線程/UI線程),也就是子線程。
拋出異常:Only the original thread that created a view hierarchy can touch its views.

四啸蜜、什么是參與繪制

看完上面的內(nèi)容坑雅,肯定有人說答案不就是子線程不能操作UI控件嘛,為什么還要加上"參與繪制"的條件盔性。我們好好思考一下代碼2.1中兩處代碼

4.1 skipInvalidate
        if (skipInvalidate()) {//判斷是否需要跳過繪制
            return;
        }

    //判斷是否需要可見或者處于動畫中
    private boolean skipInvalidate() {
        return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
                (!(mParent instanceof ViewGroup) ||
                        !((ViewGroup) mParent).isViewTransitioning(this));
    }

當(dāng)前的View不可見且沒有動畫霞丧,那skipInvalidate就是true,直接return了冕香,就可以避免調(diào)用到ViewRootImpl.invalidateChild-> checkThread蛹尝,也就不會拋出異常了。

4.2 ViewParent
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);//調(diào)用ViewParent的invalidateChild
        }

當(dāng)前View沒有ViewParent或者最頂層的ViewParent不是ViewRootImpl悉尾,也可以避免調(diào)用到ViewRootImpl.invalidateChild-> checkThread突那,也就不會拋出異常了。

4.2.1 什么情況下View沒有ViewParent:

我們new了一個View還沒有addView到一個ViewParent

4.2.2 什么情況下最頂層的ViewParent不是ViewRootImpl

我們在Activity觸發(fā)onResume之前操作View构眯,因為這個時候Decorview還沒有添加到ViewRootImpl愕难。

以上情況就是屬于“不參與繪制”的情況

總結(jié)

現(xiàn)在應(yīng)該理解我的標(biāo)準(zhǔn)答案:子線程不能操作"參與繪制"的UI控件。

思考

postInvalidate能否在子線程調(diào)用惫霸,它與invalidate的區(qū)別在哪里猫缭?

相信看完我這篇文章再結(jié)合代碼你肯定可以完美回答我這個問題.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市壹店,隨后出現(xiàn)的幾起案子猜丹,更是在濱河造成了極大的恐慌,老刑警劉巖硅卢,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件射窒,死亡現(xiàn)場離奇詭異,居然都是意外死亡将塑,警方通過查閱死者的電腦和手機脉顿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來点寥,“玉大人艾疟,你說我怎么就攤上這事】疲” “怎么了汉柒?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長责鳍。 經(jīng)常有香客問我碾褂,道長,這世上最難降的妖魔是什么历葛? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任正塌,我火速辦了婚禮嘀略,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乓诽。我一直安慰自己帜羊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布鸠天。 她就那樣靜靜地躺著讼育,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稠集。 梳的紋絲不亂的頭發(fā)上奶段,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音剥纷,去河邊找鬼痹籍。 笑死,一個胖子當(dāng)著我的面吹牛晦鞋,可吹牛的內(nèi)容都是我干的蹲缠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼悠垛,長吁一口氣:“原來是場噩夢啊……” “哼线定!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起确买,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤渔肩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拇惋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡抹剩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年撑帖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澳眷。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡胡嘿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钳踊,到底是詐尸還是另有隱情衷敌,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布拓瞪,位于F島的核電站缴罗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏祭埂。R本人自食惡果不足惜面氓,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一兵钮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舌界,春花似錦掘譬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藐握,卻和暖如春靴拱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趾娃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工缭嫡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抬闷。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓妇蛀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笤成。 傳聞我的和親對象是個殘疾皇子评架,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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