View控件可以在非UI線程更新嗎泣港?

先說結(jié)論:

可以

當你看到這個結(jié)論的時候暂殖,可能有人會想,這篇文章不值得一看当纱,騙人的吧G好俊!坡氯!
如果顛覆了你的認知晨横,那就對了,我也是這么過來的箫柳。
好手形,開始進入正題。

寫這篇文章悯恍,是基于一篇大神的文章:
我感覺我學了一個假的Android...看過鴻洋的文章库糠,腦子里只有臥槽…

示例:

環(huán)境:

  • Android Studio 3.6.3
  • Gradle Version 4.10.1-all
  • Gradle Plugin Version 3.3.1
  • Android SDK Build Tools Version 28.0.3
  • 測試機
    測試機

代碼:

  • UITestActivity
public class UITestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_uitest);
        findViewById(R.id.btn_question).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                requestAQuestion(false);
            }});
        findViewById(R.id.btn_question2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                requestAQuestion(true);
            }});
    }

    private void requestAQuestion(final boolean addLoop) {
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 模擬服務器請求,返回問題
                if (addLoop) {
                    Looper.prepare(); // 增加部分
                    showQuestionInDialog();
                    Looper.loop(); // 增加部分
                } else {
                    showQuestionInDialog();
                }
            }
        }.start();
    }

    private void showQuestionInDialog() {
        QuestionDialog questionDialog = new QuestionDialog(this);
        questionDialog.show("問題:");
    }
}

  • QuestionDialog
public class QuestionDialog extends Dialog {

    private TextView mTvTitle;
    private Handler sUiHandler = new Handler(Looper.getMainLooper());

    public QuestionDialog(@NonNull Context context) {
        super(context);

        setContentView(R.layout.dialog_uitest);

        mTvTitle = findViewById(R.id.tv_title);
        Button mBtnYes = findViewById(R.id.btn_yes);
        Button mBtnNo = findViewById(R.id.btn_no);
        mBtnNo.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                String s = mTvTitle.getText().toString();
                mTvTitle.setText(s + "涮毫?");
            }
        });
        mBtnYes.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                sUiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        String s = mTvTitle.getText().toString();
                        mTvTitle.setText(s + " ^_^ 我就想一次把這個程序整崩潰了瞬欧,咋地!0辗馈艘虎!");
                    }
                });
            }
        });
    }

    public void show(String pre) {
        mTvTitle.setText(pre + mTvTitle.getText().toString());
        show();
    }
}

效果演示:

  • 彈窗無響應:
    彈窗無響應演示
  • 更新UI控件


    更新UI控件

分析:

在大神的文章中相關的分析已經(jīng)說得很清楚了
我偷個懶,總結(jié)一下:

0咒吐、先說:View控件可以在非UI線程更新嗎野建?

答案是 YES

  • 條件:在創(chuàng)建View控件的線程中,則可以渤滞,不同的線程不能 直接 刷新
  • 疑問:在非UI線程刷新View控件是鬧哪樣呢贬墩?還是以正常人的思維寫代碼吧!

1妄呕、在【彈窗無響應】的演示中陶舞,為什么按返回鍵會出現(xiàn)無響應?

我可以說我不知道么绪励?
我研究了好久(其實就半天)肿孵,還是沒找出問題唠粥,但有幾個發(fā)現(xiàn):
一個是在不點擊返回鍵之前,其他任何操作都沒問題停做,而且通過日志查看晤愧,點擊按鈕【彈窗】時,也執(zhí)行了 show 方法蛉腌,但并未顯示彈窗官份,我懷疑是發(fā)送show消息(dialog中是通過handler來處理顯示、消失烙丛、取消等操作的)的時候出了問題舅巷,或者根本就沒走到消息發(fā)送的地方。
二個是返回鍵的處理河咽,是要關閉所有的頁面钠右,那這里出現(xiàn)ANR,有可能是出現(xiàn)了阻塞(個人猜測忘蟹,請自測)飒房,導致卡了UI線程。

一個讓我很郁悶的事情媚值,是無法跟蹤到源碼狠毯,每次走到判斷 mShowing 的時候必為 false ,但還走進去了杂腰,唉垃你,可能是我的打開方式不對(求好心人指教)

public void show() {
        //就是這個判斷讓我崩了潰了,false竟然還能進去喂很,
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }
  //此處省略好多代碼...
}

2惜颇、在【更新UI控件】的演示中,為什么一個暫時未崩潰了少辣,而另一個直接崩潰了凌摄?

就是大神的文章中拋出的問題:

切到UI線程執(zhí)行setText沒有立馬崩潰,而是執(zhí)行了好幾次之后才崩潰的漓帅,為什么呢锨亏?

  • 先說崩潰的根本原因:
    其實大神的文章中有提到:
public void requestLayout() {

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
}

每次,就是刷新導致的
當進行控件(View 樹)刷新的時候忙干,由于當前線程和刷新控件所在的線程不一致了器予,就崩潰了:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
  • 那表面原因是什么呢?
    我想原因就在那兩行注釋的地方
    看代碼:
private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
        //此處省略好多代碼...
        if (mLayout != null) {
            checkForRelayout();
        }
        //此處省略好多代碼...
}
private void checkForRelayout() {
        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
            //此處省略好多代碼...
            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                //此處省略好多代碼...
                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                //這兩行注釋說的特別??捐迫,
                //高度相同乾翔,還requestLayout嗎?我覺得問題的答案就出現(xiàn)在這里
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }
            requestLayout();
            invalidate();
        } else {
            //此處省略好多代碼...哦不,就兩行注釋
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

強烈注意:
實踐是檢驗真理的唯一反浓,以上僅為我的懷疑萌丈,出問題不負責,最好自己測試一下??

相關資料:
Android子線程真的不能更新UI么

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雷则,一起剝皮案震驚了整個濱河市辆雾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌月劈,老刑警劉巖度迂,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異艺栈,居然都是意外死亡英岭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門湿右,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罚勾,你說我怎么就攤上這事毅人。” “怎么了尖殃?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵丈莺,是天一觀的道長。 經(jīng)常有香客問我送丰,道長缔俄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任器躏,我火速辦了婚禮俐载,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘登失。我一直安慰自己遏佣,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布揽浙。 她就那樣靜靜地躺著状婶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪馅巷。 梳的紋絲不亂的頭發(fā)上膛虫,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音钓猬,去河邊找鬼稍刀。 笑死,一個胖子當著我的面吹牛逗噩,可吹牛的內(nèi)容都是我干的掉丽。 我是一名探鬼主播跌榔,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捶障!你這毒婦竟也來了僧须?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤项炼,失蹤者是張志新(化名)和其女友劉穎担平,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锭部,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡暂论,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拌禾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片取胎。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖湃窍,靈堂內(nèi)的尸體忽然破棺而出闻蛀,到底是詐尸還是另有隱情,我是刑警寧澤您市,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布觉痛,位于F島的核電站,受9級特大地震影響茵休,放射性物質(zhì)發(fā)生泄漏薪棒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一榕莺、第九天 我趴在偏房一處隱蔽的房頂上張望俐芯。 院中可真熱鬧,春花似錦帽撑、人聲如沸泼各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扣蜻。三九已至,卻和暖如春及塘,著一層夾襖步出監(jiān)牢的瞬間莽使,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工笙僚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芳肌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像亿笤,于是被迫代替她去往敵國和親翎迁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355