笔记:android子线程刷新UI界面

最近在写一些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情况的。

你可能感兴趣的:(笔记:android子线程刷新UI界面)