笔记: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情况的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容