Android View刷新原理Invalidate()和PostInvalidate()源码分析

来源:互联网 发布:财务部swot矩阵分析表 编辑:程序博客网 时间:2024/05/21 06:52

一般Ui控件使用来简单开发时,并没有注意到系统如何刷新,而当我们自定义View或开发复杂的view时,就需要主动调用Invalidate或者postInvalidate等来通知系统刷新绘制UI,刷新视图。那接下来一个个来剖这两个Api的具体实现。

Invalidate()

invalidate最后调用到invalidateInternal函数,把view的相对尺寸和相关状态设置传递

    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)其中invalidateInternal函数中逻辑不少,主要部分如下,有段调用父view进行刷新:    // 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);}

那ViewParent.invalidateChild实现是在哪里呢,猜到ViewGroup,可以查源码发现ViewGroup类实现了ViewParent的接口,那什么时候子View的ViewParent什么时候被复制,那来看下子View被如何添加到父View,看下ViewGroup.addView时添加子View的逻辑,截取了关键代码:

   addInArray(child, index);  // tell our childrenif (preventRequestLayout) {    child.assignParent(this);} else {    child.mParent = this;}

可以看到addView时对child的ViewPrarent变量mParent进行赋值,把自己传递给子View,那么回到到ViewGroup对ViewParent接口中invalidateChild方法实现,发现里面有循环查找父View逻辑,如下:

 do {    View view = null;    if (parent instanceof View) {        view = (View) parent;    }   **********         省略代码    // If the parent is dirty opaque or not dirty, mark it dirty with the opaque    **********    parent = parent.invalidateChildInParent(location, dirty);    if (view != null) {        // Account for transform on current parent       ********    }} while(parent!=null)

可以看到这个do-while循环一直从当前子View轮询查找上层ViewParent知道parent=null即意味着没有父VIew了,就是顶层View,那什么时候parent.invalidateChildInParent(location, dirty)会返回null呢。这里就要分析View树形结构了,布局结构中最上层的ViewParent是谁,什么时候赋值的。这个要从setContentView函数设置布局文件开始讲,有点长,从中看到布局最顶层view就是DecorView(可以查源码),那DecorView的ViewParent又是谁,这个就必须从添加decorView定位。参考http://blog.csdn.net/luoshengyang/article/details/6689748这边老罗的文章,分析应用启动以及View显示加载过程,从中可以知道在AMS通知PerformResumeActivity命令时开始显示界面,会调用activity的makeVisible(),该函数添加了mDecor(DecorView)

 void makeVisible() {    if (!mWindowAdded) {        ViewManager wm = getWindowManager();        wm.addView(mDecor, getWindow().getAttributes());        mWindowAdded = true;    }    mDecor.setVisibility(View.VISIBLE); }

ViewManager.addView的实现在WindowManagerGlobal的addView方法中,会看到一个ViewRootImpl,看起来很想最最顶层 的View,查找源码发现:并非是个View,但是其实现了ViewParent,这就差不多连起来了,然后其调用了setView方法查看ViewRootImpl实现如下

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {                mView = view;                           ************很多代码省略                *************                view.assignParent(this);                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;                 ************很多代码省略                *************            }        }    }

发现 mView = view进行赋值,并调用assignParent(this),把这个ViewParent实现付给DecorView,从持有父View。既然ViewRootImp实现了ViewParent,并且付给了DecorView,那在DecorView查找父parent时(parent.invalidateChildInParent(location, dirty))就可以定位到ViewRootImpl实现了,找到ViewRootImp.invalidateChildInParent:发现终于返回了null,结束了循环,源码如下:

 @Override    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {        checkThread();        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);          if (mCurScrollY != 0 || mTranslator != null) {            mTempRect.set(dirty);            dirty = mTempRect;            if (mCurScrollY != 0) {                dirty.offset(0, -mCurScrollY);            }            if (mTranslator != null) {                mTranslator.translateRectInAppWindowToScreen(dirty);            }            if (mAttachInfo.mScalingRequired) {                dirty.inset(-1, -1);            }        }        invalidateRectOnScreen(dirty);        return null;    }

invalidateRectOnScreen(dirty);又调用了谁,继续定位

 private void invalidateRectOnScreen(Rect dirty) {        final Rect localDirty = mDirty;        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {            mAttachInfo.mSetIgnoreDirtyState = true;            mAttachInfo.mIgnoreDirtyState = true;        }        // Add the new dirty rect to the current one        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);        // Intersect with the bounds of the window to skip        // updates that lie outside of the visible region        final float appScale = mAttachInfo.mApplicationScale;        final boolean intersected = localDirty.intersect(0, 0,                (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));        if (!intersected) {            localDirty.setEmpty();        }        if (!mWillDrawSoon && (intersected || mIsAnimating)) {            scheduleTraversals();        }    }

可以看到把重新计算传到ViewRootImpl的Rect对象dirty和刷新之前的mDirty进行合并(union),更新了绘制Rect并重新调用schuduleTraversals()再到doTraversals最后调用到performTraversals函数,这个是view绘制的源头开始处,代码很多,里面通过performMeasure()、performLayout()、performDraw()调用了mView.measure() mView.layout 、mView.draw等方法。终于差不多看到希望了,mView.draw()就是调用了DecorView的draw(),然后就把这个布局遍历绘制了一遍。那么走到执行动画的View的其父View绘制draw方法时候,会走到dispatchDraw,并调用了子VIew的Draw()方法实现View的刷新。

postInvalidate()实现

public void postInvalidate() {        postInvalidateDelayed(0);}public void postInvalidateDelayed(long delayMilliseconds) {        // We try only with the AttachInfo because there's no point in invalidating        // if we are not attached to our window        if (mAttachInfo != null) {            Message msg = Message.obtain();            msg.what = AttachInfo.INVALIDATE_MSG;            msg.obj = this;            mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);        }    }

这个message被AttachInfo接受处理如下:

attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);1

继而走到VIewRootImpl的dispatchInvalidateDelayed方法,并把子VIew对象this传递了,可以查看其实现:

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);        mHandler.sendMessageDelayed(msg, delayMilliseconds);    }
  @Override        public void handleMessage(Message msg) {            switch (msg.what) {            case MSG_INVALIDATE:                ((View) msg.obj).invalidate();                break;         //省略很多代码

从而发现最终还是调用了子View的invalidate(),从而得知实际是一个原理,殊途同归,系统这样设计就可以看出其用意区别,是为了增加在非主线程中调用刷新,invalidate是主线程UI操作的,线程不能直接调用,故此通过postInvalidate通过handle来调用了invalidate,实现了线程中直接刷新办法。

总结invalidate()和postInvalidate()

invalidate和postInvalidate从上述源码分析两者唯一区别就是postInvalidate通过handle方式回到了invalidate从而实现了线程可以刷新UI的目的,这个作用在实现负责动画时候,通过子线程的变量改变之后直接调用postinvalidate就可以实现刷新Ui了。
阅读全文
1 0
原创粉丝点击