先說結(jié)論:
可以
當你看到這個結(jié)論的時候暂殖,可能有人會想,這篇文章不值得一看当纱,騙人的吧G好俊!坡氯!
如果顛覆了你的認知晨横,那就對了,我也是這么過來的箫柳。
好手形,開始進入正題。
寫這篇文章悯恍,是基于一篇大神的文章:
我感覺我學了一個假的Android...看過鴻洋的文章库糠,腦子里只有臥槽…
示例:
環(huán)境:
- 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 + " ^_^ 我就想一次把這個程序整崩潰了瞬欧,咋地!0辗馈艘虎!");
}
});
}
});
}
public void show(String pre) {
mTvTitle.setText(pre + mTvTitle.getText().toString());
show();
}
}
效果演示:
-
彈窗無響應:彈窗無響應演示
-
更新UI控件
更新UI控件
分析:
在大神的文章中相關的分析已經(jīng)說得很清楚了
我偷個懶,總結(jié)一下:
0咒吐、先說:View控件可以在非UI線程更新嗎野建?
答案是 YES
- 條件:在創(chuàng)建View控件的線程中,則可以渤滞,不同的線程不能
直接
刷新 - 疑問:在非UI線程刷新View控件是鬧哪樣呢贬墩?還是以正常人的思維寫代碼吧!
1妄呕、在【彈窗無響應】的演示中陶舞,為什么按返回鍵會出現(xiàn)無響應?
我可以說我不知道么绪励?
我研究了好久(其實就半天)肿孵,還是沒找出問題唠粥,但有幾個發(fā)現(xiàn):
一個是在不點擊返回鍵之前,其他任何操作都沒問題停做,而且通過日志查看晤愧,點擊按鈕【彈窗】時,也執(zhí)行了 show
方法蛉腌,但并未顯示彈窗官份,我懷疑是發(fā)送show消息(dialog中是通過handler來處理顯示、消失烙丛、取消等操作的)的時候出了問題舅巷,或者根本就沒走到消息發(fā)送的地方。
二個是返回鍵的處理河咽,是要關閉所有的頁面钠右,那這里出現(xiàn)ANR,有可能是出現(xiàn)了阻塞(個人猜測忘蟹,請自測)飒房,導致卡了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線程執(zhí)行setText沒有立馬崩潰,而是執(zhí)行了好幾次之后才崩潰的漓帅,為什么呢锨亏?
- 先說崩潰的根本原因:
其實大神的文章中有提到:
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嗎?我覺得問題的答案就出現(xiàn)在這里
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return;
}
}
requestLayout();
invalidate();
} else {
//此處省略好多代碼...哦不,就兩行注釋
nullLayouts();
requestLayout();
invalidate();
}
}
強烈注意:
實踐是檢驗真理的唯一反浓,以上僅為我的懷疑萌丈,出問題不負責,最好自己測試一下??
相關資料:
Android子線程真的不能更新UI么