關(guān)于這個(gè)問題在面試的時(shí)候可能會(huì)被問到映跟,其實(shí)在某些情況下是可以在子線程中更新UI的!
比如:在一個(gè)activity的xml文件中中隨便寫一個(gè)TextView文本控件,然后在Activity的onCreate方法中開啟一個(gè)子線程并在該子線程的run方法中更新TextView文本控件扬虚,你會(huì)發(fā)現(xiàn)根本沒有任何問題努隙。
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.threadcrashui_act);
textView = (TextView) findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
// 更新TextView文本內(nèi)容
textView.setText("update TextView");
}
}).start();
}
但是如果你讓子線程休眠2秒鐘如下面代碼:
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.threadcrashui_act);
textView = (TextView) findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
//更新TextView文本內(nèi)容
textView.setText("update TextView");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
程序直接掛掉了,好吧辜昵,我們先去看看log日志荸镊,如圖提示
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
程序不允許在非UI線程中更新UI線程
對于這個(gè)問題,可能大家都跟我一樣有點(diǎn)疑惑對吧堪置?我當(dāng)時(shí)也很疑惑躬存,不明白為啥是這樣!Rㄏ恰借浊!然后我去翻資料查找原因扔仓,最終定位到setText這個(gè)方法
/**
* Sets the string value of the TextView. TextView <em>does not</em> accept
* HTML-like formatting, which you can do with text strings in XML resource files.
* To style your strings, attach android.text.style.* objects to a
* {@link android.text.SpannableString SpannableString}, or see the
* <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
* Available Resource Types</a> documentation for an example of setting
* formatted text in the XML resource file.
*
* @attr ref android.R.styleable#TextView_text
*/
@android.view.RemotableViewMethod
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
緊接著看setText方法
/**
* Sets the text that this TextView is to display (see
* {@link #setText(CharSequence)}) and also sets whether it is stored
* in a styleable/spannable buffer and whether it is editable.
*
* @attr ref android.R.styleable#TextView_text
* @attr ref android.R.styleable#TextView_bufferType
*/
public void setText(CharSequence text, BufferType type) {
setText(text, type, true, 0);
if (mCharWrapper != null) {
mCharWrapper.mChars = null;
}
}
再進(jìn)setText方法
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
//前邊的都省略掉 .......
if (mLayout != null) {
//這個(gè)方法的作用就是讓界面重新繪制下
checkForRelayout();
}
//后邊的也直接省略掉
}
然后我們看下checkForRelayout方法,在這里大家會(huì)看到一個(gè)invalidate的方法,因?yàn)槲覀冎浪械膙iew更新操作都會(huì)調(diào)用view的invalidate方法掀序!那么問題就在這里了
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
private void checkForRelayout() {
//注意看這里
invalidate();
} else {
//注意看這里
invalidate();
}
}
然后我們進(jìn)invalidate方法中查找原因
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
* the future.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
}
好 那么我們就看下invalidate(true)方法看看到底是哪的問題
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
//可能不同版本的api源碼不太一樣,這里直接看invalidateInternal方法携取。
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {
//我們關(guān)心的是這里 ViewParent纳令,這個(gè)ViewPrent是一個(gè)接口,而ViewGroup與ViewRootImpl實(shí)現(xiàn)了它进栽,有耐心的朋友可以在View這個(gè)類中去查找mPrent相關(guān)的一些信息德挣,如果細(xì)心的朋友在ViewGroup類中會(huì)找到ViewRootImpl這個(gè)類在它里邊的一些操作如setDragFocus等等。
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//程序在這里檢查是不是在UI線程中做操作
p.invalidateChild(this, damage);
}
}
}
這個(gè)ViewRootImpl我們可能在Eclipse或者Android Studio中我們無法查看快毛,大家可以使用Source Insight 去查看源碼:打開Source Insight 打開ViewRootImpl類格嗅,找到 invalidateChild這個(gè)方法
public void invalidateChild(View child, Rect dirty) {
//關(guān)鍵的地方就是這個(gè)方法
checkThread();
//后邊的全部省略
}
void checkThread() {
//在這里mThread表示的是主線程番挺,程序作了判斷,檢查當(dāng)前線程是不是主線程屯掖,如果不是就會(huì)拋出異常
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
看到上邊方法中拋出的異常是不是感覺很熟悉玄柏,對,沒錯(cuò)贴铜,就是我log中截出來的那句話7嗾!那么我們現(xiàn)在懵逼了绍坝,為什么我們在不讓子線程休眠的情況下去更新TextView文本可以徘意,而讓線程休眠兩秒后就出拋異常呢?根本原因就是ViewRootImpl到底是在哪里被初始化的轩褐!ViewRootImpl是在onResume中初始化的椎咧,而我們開啟的子線程是在onCreat方法中,這個(gè)時(shí)候程序沒有去檢測當(dāng)前線程是不是主線程把介,所以沒有拋異常G诜怼!下邊我們?nèi)タ碅ctivityThread源碼劳澄,去找出原因5丶肌!
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//ViewPrent實(shí)現(xiàn)類
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//問題的根源在這里秒拔,addView方法在這里對ViewRootImpl進(jìn)行初始化莫矗,大家可以去看看ViewGroup的源碼,找里邊的addView方法砂缩,你會(huì)發(fā)現(xiàn)作谚,最后又回到View的invalidate(true)方法;
wm.addView(decor, l);
}
}
}
面試會(huì)經(jīng)常問到,所以整理了下留待自己使用庵芭,同時(shí)分享給大家C美痢!有不足之處請大家留言指正双吆,同時(shí)歡迎加入 46674429 366576264 QQ技術(shù)群眨唬,進(jìn)行交流!:美帧匾竿!