android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
有時候大家做項目的時候偶爾會碰到這個毛病。不用說大家都知道是子線程更新主線程(UI)線程的問題,一樣大家也會給出相對應的解法:使用handle+Thread方法通過發送Message進行更新UI線程。
eg:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
iv_img.setBackground(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_launcher));
btn_commit.setText("iiiiiiiiiiiiiiiiiiiii ");
Toast.makeText(MainActivity.this,"阿斯蒂芬噶是的發送到",Toast.LENGTH_LONG).show();
}
}).start();
此時就會出現1下毛病:
此時我們都知道最簡單的1種解決方式就是:
new Thread(new Runnable() {
@Override
public void run() {
Message msg = handler.obtainMessage();
msg.what = 100;
msg.obj = "發送消息";
handler.sendMessage(msg);
}
}).start();
}
接下來我們就來探究1下:子線程和UI線程之間更新問題。
首先我們要知道:
我們想看看報錯的這行代碼在ViewRootImpl.java:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread是1個線程,如果改線程是當前的線程的時候,則繼續向下走,不會拋出異常。大家可以看到,我在onCreate方法,只有1個線程,肯定是當前的線程,那為何會報錯呢?我們接著往下看。
接下來我們看看checkThread()在那幾處用到了,其中有
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
.......
invalidateRectOnScreen(dirty);
return null;
}
@Override
public void requestFitSystemWindows() {
checkThread();
mApplyInsetsRequested = true;
scheduleTraversals();
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
從報錯的點 at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:848)我們可以看到是848行是invalidateChildInParent方法里調用的。我們進1步與發現invalidateChildInParent又在invalidateChild()里調用,在View中那個地方調用了呢?我們進1步向下跟進。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
......
}
而invalidateInternal方法在invalidate()方法中是這樣的
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
現在我們清楚了,嚴格的來講原來子線程是不能刷新UI線程的。
解決方式:
第1種方式:
new Thread(new Runnable() {
@Override
public void run() {
Message msg = handler.obtainMessage();
msg.what = 100;
msg.obj = "發送消息";
handler.sendMessage(msg);
}
}).start();
}
第2中方式:
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
iv_img.setBackground(ContextCompat.getDrawable(MainActivity.this,R.drawable.ic_launcher));
}
});
第3種方式: 利用AsyncTask方法。
以上都是進程間通訊的幾種方式。這里我不再做過量的描寫。
如果您覺我的文章對您有所幫助,
QQ交換群 :232203809,歡迎入群
微信公眾號:終端研發部
(歡迎關注學習和交換)