美文网首页
View控件可以在非UI线程更新吗?

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

作者: 玉圣 | 来源:发表于2020-06-22 13:04 被阅读0次

    先说结论:

    可以

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

    写这篇文章,是基于一篇大神的文章:
    我感觉我学了一个假的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么

    相关文章

      网友评论

          本文标题:View控件可以在非UI线程更新吗?

          本文链接:https://www.haomeiwen.com/subject/yklxfktx.html