这是小弟我第一篇博客,说的不对或者错的地方请各位大大指错。
相信大家在学android的时候,都是说在android中不能在子线程中更新UI。
那么是真的吗?
那么,我们不妨先测试一下下面的代码:
mTv = (TextView) findViewById(R.id.id_text);
new Thread(){
public void run() {
mTv.setText("Hello Gays...");
};
}.start();
Hello World 变成 Hello Gays了。madan,颠覆了我对android的认知。
那么我们进setText();这个方法看看。一路Ctrl + G:
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
if (text == null) {
text = "";
}
略过....
if (mLayout != null) {
checkForRelayout();
}
略过...
// SelectionModifierCursorController depends on textCanBeSelected,
which depends on text
if (mEditor != null) mEditor.prepareCursorControllers();
}
发现到快结尾的时候会执行一个checkForRelayout()的方法,不管怎么判断都会去调用
invalidate()方法。
private void checkForRelayout() {
略过...
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) {
invalidate();
return;
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht &&
(mHintLayout == null || mHintLayout.getHeight() == oldht)
) {
invalidate();
return;
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
然后继续沿着 invalidate()找,发现会执行到
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
略过...
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);
}
}
这里调用了invalidateChild(this, damage),于是乎我们就到ViewParent
这个类下面找,发现ViewParent是一个接口,啊。那咋办,其实这里的ViewParent是一个
ViewRootImpl,那么我们进入到ViewRootImpl类下面找invalidateChild(this, damage)
这个方法。
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
执行invalidateChildInParent(null, dirty)方法
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
略过...
return null;
}
很幸运,第一句就找到了我们想看到的代码,进入checkThread()
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy
can touch its views.");
}
}
为什么没有抛出异常,不,为什么!!!
其实 ViewRootImpl 是在Activity 生命周期执行到 onResume() 产生的。
那么问题就解决了,我在子线程停止一下看看,在上面的run方法添加一行Thread.sleep(
5000);
报错了,欧也。
那我们不妨再看看onResume()
我们知道整个activity创建是在ActivityThread里面创建的,那我们就去看看呗,我们找到了一个handleResumeActivity()的方法,在onResume()的时候会回调这个方法。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
略过....
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
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;
wm.addView(decor, l);
}
略过 ....
}
这里的decor其实就是整个Activity的根布局(FrameLayout),然后最后调用addView(
decor,l)方法。
我们去ViewManager 看看 addView()的方法,发现其实这里的vm是WindowManager,而
WindowManager是一个接口,实际上就是调用了WindowManagerImpl里的addView
方法,那我们就瞧一眼。
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
addView()中又继续调用 mGlobal.addView(),那么我们到最终的目的地看看
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
略过...
ViewRootImpl root;
View panelParentView = null;
略过...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
OK,我们看到ViewRootImpl 了,之后就和我们上面所说的一样了。
总结:1.是可以在子线程进行时间很短很短的更新UI,但是谁都无法预料在子线程中会消耗多久,所以之所以认为不安全,应该就是这里吧。
2.关键的关键就在于Activity的生命周期 , onCreate()–>onStart()–>onResume(),在主线程开始的时候,子线程也开始了,也就是说还没执行到onResume里面得到ViewRootImpl对象的时候,即ViewRootImpl还没有创建,所以在子线程中更新UI是可行的,反之就会在ViewRootImpl中执行checkThread()方法,throw new CalledFromWrongThreadException 的一个异常。
PS:我把很多源码都贴上来了,如果觉得不明白可以去github上,把源码clone下来。