美文网首页
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线程更新吗?

    先说结论: 可以 当你看到这个结论的时候,可能有人会想,这篇文章不值得一看,骗人的吧!!!如果颠覆了你的认知,那就...

  • 非UI线程不能更新View源码探索

    非UI线程不能更新View,相信对每个Android开发者来说都不陌生,那大家有没有想过为什么非UI线程就不能更新...

  • Android中为什么不能在子线程中更新UI

    点击查看 原因: 这个只解释了如果在子线程更新UI为什么会抛异常;真正不能再自在子线程更新UI的原因是:UI控件非...

  • 2020-03-03-Android View的刷新

    postInvalidate 今天刚好用到了view的postInvalidate方法,只是知道它可以在非UI线程...

  • 浅析Android 消息机制

    消息机制存在的意义 为什么不能在非UI线程中操作UI控件?因为Android的UI控件不是线程安全的,如果在多线程...

  • 多线程

    不能再非主线程中修改UI控件属性,不建议在主线程中做耗时操作 UI线程:主线程Activity Thread Me...

  • Android中为什么可以在非UI线程里更新UI组件?

    说好的不能在非UI线程里更新UI组件呢?其实非UI线程是可以刷新UI的,前提是它要拥有自己的ViewRoot,Vi...

  • SurfaceView和View的区别?

    一、SurfaceView和View的概念 View在UI线程去更新自己;SurfaceView则在一个子线程中去...

  • SurfaceView和View的区别

    一、概念 View在UI线程去更新自己;SurfaceView则在一个子线程中去更新自己SurfaceView是在...

  • Android handler使用方法

    android中系统不允许在非主线程更新UI。当我们在非主线程做了耗时操作后,需要去更新UI的时候,我们就需要使用...

网友评论

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

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