最近在写一些demo时写了下面这样一段代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView iv = (ImageView) findViewById(R.id.iv);
new Thread(new Runnable() {
@Override
public void run() {
iv.setImageResource(R.mipmap.ic_launcher);
}
}).start();
}
按照我以前的理解,在子线程中更新UI,应该会报错,程序会crash的啊,为什么这样就没有问题呢?
于是结合一些网上资料还有对源码的跟踪学习,终于知道原因了,现在以ImageView为例
public void setImageResource(@DrawableRes int resId) {
...
invalidate();
}
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) {
...
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)) {
...
// 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);
p.invalidateChild(this, damage);
}
...
}
}
主要是这段代码
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方法,这个方法的实现在ViewRootImpl类中
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
...
return null;
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
就是这个checkThread抛出的异常,那么这个mThread又是哪来的呢,原来在ViewRootImpl的构造方法中有这样一句
mThread = Thread.currentThread();
那么这个ViewRootImpl又是什么时候创建的呢?经过源码查看,原来是在ActivityThread的handleResumeActivity方法中创建的,在handleResumeActivity中有这样一段代码
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 (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
实际上就是在这个addView方法中,那这个addView的具体实现是在哪呢,一步步找吧,首先是这个wm对象,这个vm对象是通过Activity的getWindowManager方法获取到的,代码如下
public WindowManager getWindowManager() {
return mWindowManager;
}
继续看这个mWindowManager对象是怎么来的,最终找到在attach里面有这样一段
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
...
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
应该是从PhoneWindow这个对象中获取的,继续看PhoneWindow源码,好吧,PhoneWindow中没getWindowManager这个方法,是它父类Window的,那就继续到Window中寻找
public WindowManager getWindowManager() {
return mWindowManager;
}
继续看这个mWindowManager是什么时候创建的,记得上面PhoneWindow创建成功后调用过这样一个方法吗?
mWindow.setWindowManager
就是在这个方法中创建的
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm =(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
那么就是到WindowManagerImpl类中找addView方法了
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
继续看这个mGlobal对象
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
}
终于找到了,但是这些都是onResume执行完后才会生成,所以如果在此生命周期后子线程中刷新UI是会报错的,除非自己通过WindowManager的addView方法创建一个ViewRootImpl对象,修改mThread对象的值,使其最终checkThread时能通过。
但是我的demo是在onCreate中,按照道理说mThread对象应该是空啊,怎么通过的呢?其实要看这段代码
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);
}
在onCreate中,这时的p对象是空,所以是不会执行的,那界面又是怎么刷新的呢?我们再看ViewRootImpl的构造方法,其中有这样一句
loadSystemProperties();
看它的实现
public void loadSystemProperties() {
mHandler.post(new Runnable() {
@Override
public void run() {
// Profiling
mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
profileRendering(mAttachInfo.mHasWindowFocus);
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
if(mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
invalidate();
}
}
// Layout debugging
boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
if (layout != mAttachInfo.mDebugLayout) {
mAttachInfo.mDebugLayout = layout;
if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
}
}
}
});
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
所以是在ViewRootImpl创建成功后又主动刷新了下界面,但这时是不会检查thread情况的。