废话不说,先看一个简单的效果
<span style="font-size:12px;">package com.xiaojiukeji.updateui;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(){
@Override
public void run() {
updataUI();
}
}.start();
}
private void updataUI() {
tv.setText("更新后的数据");
}
}</span>
布局就不贴了,就只有一个textView,执行后的效果是
是不是有点匪夷所思?别急,改一下代码,再看!
这里只是把主线程休眠一秒,然后再更新UI
<span style="font-size:12px;">package com.xiaojiukeji.updateui;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
updataUI();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
private void updataUI() {
tv.setText("更新后的数据");
}
}</span>
果然,这次报了错
<span style="font-size:12px;">FATAL EXCEPTION: Thread-63736
Process: com.xiaojiukeji.updateui, PID: 12295
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7665)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1119)</span>
很明显,错误提示不能在子线程中更新UI,那这就奇怪了,为什么呢?为什么直接更新就可以呢?且往下看
在setText()源码中可以找到这样一段代码
<span style="font-size:12px;">/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
(mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
(mHint == null || mHintLayout != null) &&
(mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
mLayoutParams.height != LayoutParams.MATCH_PARENT) {
<strong> <span style="color:#FF0000;">invalidate();</span></strong>
return;
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht &&
(mHintLayout == null || mHintLayout.getHeight() == oldht)) {
<em><strong><span style="color:#FF0000;">invalidate();</span></strong></em>
return;
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
<span style="color:#FF0000;"><em><strong>invalidate();</strong></em></span>
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
<span style="color:#FF0000;"><strong>invalidate();</strong></span>
}
}</span>
可以看到不管怎么样,都会执行到上面代码中我用红色标出来了invalidata()方法,这个是让UI界面绘制的,可以点击去继续看
<span style="font-size:12px;">public void invalidate() {
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) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
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);
<span style="font-size:14px;color:#FF0000;"><strong> p.invalidateChild(this, damage);</strong></span>
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}</span>
其中
<span style="font-size:12px;"><span style="font-size:14px;color:#FF0000;"><strong>p.invalidateChild(this, damage);</strong></span></span>
在判断当前操作是都在子线程中进行,也就是说,执行成功与否,在于这行代码的判断
感兴趣的同学可以通过source软件查看深层源代码,查看执行过程
总结:
在非UI线程中是可以更新Ui的(在onCreate中创建新线程更新UI):
当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,
若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是
在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),
就可以逃避掉checkThread()的检查进而更新UI。