View控件可以在非UI线程更新吗?

先说结论:

可以

当你看到这个结论的时候,可能有人会想,这篇文章不值得一看,骗人的吧!!!
如果颠覆了你的认知,那就对了,我也是这么过来的。
好,开始进入正题。

写这篇文章,是基于一篇大神的文章:
我感觉我学了一个假的Android...看过鸿洋的文章,脑子里只有卧槽…

示例:

环境:

  • 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 + " ^_^ 我就想一次把这个程序整崩溃了,咋地!!!");
                    }
                });
            }
        });
    }

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

效果演示:

  • 弹窗无响应:
    弹窗无响应演示
  • 更新UI控件


    更新UI控件

分析:

在大神的文章中相关的分析已经说得很清楚了
我偷个懒,总结一下:

0、先说:View控件可以在非UI线程更新吗?

答案是 YES

  • 条件:在创建View控件的线程中,则可以,不同的线程不能 直接 刷新
  • 疑问:在非UI线程刷新View控件是闹哪样呢?还是以正常人的思维写代码吧!

1、在【弹窗无响应】的演示中,为什么按返回键会出现无响应?

我可以说我不知道么?
我研究了好久(其实就半天),还是没找出问题,但有几个发现:
一个是在不点击返回键之前,其他任何操作都没问题,而且通过日志查看,点击按钮【弹窗】时,也执行了 show 方法,但并未显示弹窗,我怀疑是发送show消息(dialog中是通过handler来处理显示、消失、取消等操作的)的时候出了问题,或者根本就没走到消息发送的地方。
二个是返回键的处理,是要关闭所有的页面,那这里出现ANR,有可能是出现了阻塞(个人猜测,请自测),导致卡了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线程执行setText没有立马崩溃,而是执行了好几次之后才崩溃的,为什么呢?

  • 先说崩溃的根本原因:
    其实大神的文章中有提到:
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吗?我觉得问题的答案就出现在这里
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }
            requestLayout();
            invalidate();
        } else {
            //此处省略好多代码...哦不,就两行注释
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

强烈注意:
实践是检验真理的唯一,以上仅为我的怀疑,出问题不负责,最好自己测试一下😏

相关资料:
Android子线程真的不能更新UI么

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335