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

2020-06-22  本文已影响0人  玉圣

先说结论:

可以

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

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

示例:

环境:

代码:

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("问题:");
    }
}

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();
    }
}

效果演示:

分析:

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

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

答案是 YES

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么

上一篇下一篇

猜你喜欢

热点阅读