先说结论:
可以
当你看到这个结论的时候,可能有人会想,这篇文章不值得一看,骗人的吧!!!
如果颠覆了你的认知,那就对了,我也是这么过来的。
好,开始进入正题。
写这篇文章,是基于一篇大神的文章:
我感觉我学了一个假的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么