当前位置: 代码迷 >> Android >> 6 doFrame
  详细解决方案

6 doFrame

热度:103   发布时间:2016-04-28 02:26:51.0
android 动画原理源码分析之Animation

在开发移动应用程序的时候用到动画是家常便饭的事,但是你有没有想过它是怎么实现的呢?今天小弟就在此分析一下。

1 startAnimation 方法。

 设置好animation变量,刷新父视图绘画缓存。
    /**     * Start the specified animation now.     *     * @param animation the animation to start now     */    public void startAnimation(Animation animation) {        animation.setStartTime(Animation.START_ON_FIRST_FRAME);        setAnimation(animation);        invalidateParentCaches();        invalidate(true);    }

2 invalidate

 void invalidate(boolean invalidateCache) {        if (skipInvalidate()) {            return;        }        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 || isOpaque() != mLastIsOpaque) {            mLastIsOpaque = isOpaque();            mPrivateFlags &= ~PFLAG_DRAWN;            mPrivateFlags |= PFLAG_DIRTY;            if (invalidateCache) {                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;            }            final AttachInfo ai = mAttachInfo;            final ViewParent p = mParent;            //noinspection PointlessBooleanExpression,ConstantConditions            if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {                if (p != null && ai != null && ai.mHardwareAccelerated) {                    // fast-track for GL-enabled applications; just invalidate the whole hierarchy                    // with a null dirty rect, which tells the ViewAncestor to redraw everything                    p.invalidateChild(this, null);                    return;                }            }            if (p != null && ai != null) {                final Rect r = ai.mTmpInvalRect;                r.set(0, 0, mRight - mLeft, mBottom - mTop);                // Don't call invalidate -- we don't want to internally scroll                // our own bounds                p.invalidateChild(this, r);            }        }    }
由分析可知,将进入invalidateChild 方法。

3 invalidateChild

 /**     * Don't call or override this method. It is used for the implementation of     * the view hierarchy.     */    public final void invalidateChild(View child, final Rect dirty) {        ViewParent parent = this;        final AttachInfo attachInfo = mAttachInfo;        if (attachInfo != null) {            // If the child is drawing an animation, we want to copy this flag onto            // ourselves and the parent to make sure the invalidate request goes            // through            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)                    == PFLAG_DRAW_ANIMATION;            // Check whether the child that requests the invalidate is fully opaque            // Views being animated or transformed are not considered opaque because we may            // be invalidating their old position and need the parent to paint behind them.            Matrix childMatrix = child.getMatrix();            final boolean isOpaque = child.isOpaque() && !drawAnimation &&                    child.getAnimation() == null && childMatrix.isIdentity();            // Mark the child as dirty, using the appropriate flag            // Make sure we do not set both flags at the same time            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;            if (child.mLayerType != LAYER_TYPE_NONE) {                mPrivateFlags |= PFLAG_INVALIDATED;                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;                child.mLocalDirtyRect.union(dirty);            }            final int[] location = attachInfo.mInvalidateChildLocation;            location[CHILD_LEFT_INDEX] = child.mLeft;            location[CHILD_TOP_INDEX] = child.mTop;            if (!childMatrix.isIdentity() ||                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {                RectF boundingRect = attachInfo.mTmpTransformRect;                boundingRect.set(dirty);                Matrix transformMatrix;                if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {                    Transformation t = attachInfo.mTmpTransformation;                    boolean transformed = getChildStaticTransformation(child, t);                    if (transformed) {                        transformMatrix = attachInfo.mTmpMatrix;                        transformMatrix.set(t.getMatrix());                        if (!childMatrix.isIdentity()) {                            transformMatrix.preConcat(childMatrix);                        }                    } else {                        transformMatrix = childMatrix;                    }                } else {                    transformMatrix = childMatrix;                }                transformMatrix.mapRect(boundingRect);                dirty.set((int) (boundingRect.left - 0.5f),                        (int) (boundingRect.top - 0.5f),                        (int) (boundingRect.right + 0.5f),                        (int) (boundingRect.bottom + 0.5f));            }            do {                View view = null;                if (parent instanceof View) {                    view = (View) parent;                }                if (drawAnimation) {                    if (view != null) {                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;                    } else if (parent instanceof ViewRootImpl) {                        ((ViewRootImpl) parent).mIsAnimating = true;                    }                }                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque                // flag coming from the child that initiated the invalidate                if (view != null) {                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&                            view.getSolidColor() == 0) {                        opaqueFlag = PFLAG_DIRTY;                    }                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;                    }                }                parent = parent.invalidateChildInParent(location, dirty);                if (view != null) {                    // Account for transform on current parent                    Matrix m = view.getMatrix();                    if (!m.isIdentity()) {                        RectF boundingRect = attachInfo.mTmpTransformRect;                        boundingRect.set(dirty);                        m.mapRect(boundingRect);                        dirty.set((int) (boundingRect.left - 0.5f),                                (int) (boundingRect.top - 0.5f),                                (int) (boundingRect.right + 0.5f),                                (int) (boundingRect.bottom + 0.5f));                    }                }            } while (parent != null);        }    }

(首先来看这个方法是不允许重载的,为什么呢?在个人看来,这是出于对Android API的一种保护机制。这是视图刷新的人口。)

分析可知:view.mPrivateFlags|PFLAG_DRAW_ANIMATION; 中加上了动画状态。(google用这种方式记录view的私有状态,是一种很好的方式,既提高了效率,也减少了不必要的变量)。通过这个函数之后,我们到了ViewRootImpl.

4  ViewRootImpl  invalidateChildInParent

    @Override    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {        checkThread();        if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);        if (dirty == null) {            invalidate();            return null;        } else if (dirty.isEmpty() && !mIsAnimating) {            return null;        }        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);            }        }        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();        }        return null;    }

这个方法主要作用是先确定哪些区域重新绘画,然后遍历绘画。接下来分析scheduleTraversals.
    void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            scheduleConsumeBatchedInput();        }    }

接下来进入Choreographer 中的posCallback。对于Choreographe这个类非常的重要,它简单来说是一个消息处理器,包括用户输入,动画,绘图。
    private void postCallbackDelayedInternal(int callbackType,            Object action, Object token, long delayMillis) {        if (DEBUG) {            Log.d(TAG, "PostCallback: type=" + callbackType                    + ", action=" + action + ", token=" + token                    + ", delayMillis=" + delayMillis);        }        synchronized (mLock) {            final long now = SystemClock.uptimeMillis();            final long dueTime = now + delayMillis;            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);            if (dueTime <= now) {                scheduleFrameLocked(now);            } else {                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);                msg.arg1 = callbackType;                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, dueTime);            }        }    }

这个方法主要是把所要做的刷新动作加入到列表中。

private void scheduleFrameLocked(long now) {        if (!mFrameScheduled) {            mFrameScheduled = true;            if (USE_VSYNC) {                if (DEBUG) {                    Log.d(TAG, "Scheduling next frame on vsync.");                }                // If running on the Looper thread, then schedule the vsync immediately,                // otherwise post a message to schedule the vsync from the UI thread                // as soon as possible.                if (isRunningOnLooperThreadLocked()) {                    scheduleVsyncLocked();                } else {                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);                    msg.setAsynchronous(true);                    mHandler.sendMessageAtFrontOfQueue(msg);                }            } else {                final long nextFrameTime = Math.max(                        mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);                if (DEBUG) {                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");                }                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, nextFrameTime);            }        }
这个 USE_VSYN是4.1之后才引入了,采用了三重缓存,大大提高了绘画效率。具体的可以查看官方资料对其描述。这个字段默认为true。一般情况下isRunningOnLooperThreadLocked()==true. 
接下来分析

5  scheduleVsyncLocked();

    private void scheduleVsyncLocked() {        mDisplayEventReceiver.scheduleVsync();    }
这个方法是一个native方法会回调onVsync()方法。鉴于篇幅的原因,在此补贴其具体源码。大概意思就是把它自身的runnable加入到队列中。
接下来执行调用run()。run调用doFrame方法。

6 doFrame

void doFrame(long frameTimeNanos, int frame) {        final long startNanos;        synchronized (mLock) {            if (!mFrameScheduled) {                return; // no work to do            }            startNanos = System.nanoTime();            final long jitterNanos = startNanos - frameTimeNanos;            if (jitterNanos >= mFrameIntervalNanos) {                final long skippedFrames = jitterNanos / mFrameIntervalNanos;//mFrameIntervalNanos是屏幕刷新周期                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { //如果跳过的帧数大于30,就存在跳帧现象,说明主线程做了太多的工作                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "                            + "The application may be doing too much work on its main thread.");                }                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;//                if (DEBUG) {                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "                            + "which is more than the frame interval of "                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "                            + "Skipping " + skippedFrames + " frames and setting frame "                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");                }                frameTimeNanos = startNanos - lastFrameOffset;            }            //跳帧情况判断            if (frameTimeNanos < mLastFrameTimeNanos) {                if (DEBUG) {                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "                            + "previously skipped frame.  Waiting for next vsync.");                }                scheduleVsyncLocked();                return;            }            mFrameScheduled = false;            mLastFrameTimeNanos = frameTimeNanos;        }        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
j接下来就是一系列的调用回到ViewRootImpl performTraversals().  然后开始一系列的调用。performMeasure ,persormLayout等等调用,。接下来重点关注performDraw().
由于篇幅的关系我也不贴出源码了,接下里调用到dispatchDraw.接下来我跟踪到ViewGroup 找到这个方法。
    /**     * [email protected]}     */    @Override    protected void dispatchDraw(Canvas canvas) {        final int count = mChildrenCount;        final View[] children = mChildren;        int flags = mGroupFlags;        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;            final boolean buildCache = !isHardwareAccelerated();            for (int i = 0; i < count; i++) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {                    final LayoutParams params = child.getLayoutParams();                    attachLayoutAnimationParameters(child, params, i, count);                    bindLayoutAnimation(child);                    if (cache) {                        child.setDrawingCacheEnabled(true);                        if (buildCache) {                                                    child.buildDrawingCache(true);                        }                    }                }            }            final LayoutAnimationController controller = mLayoutAnimationController;            if (controller.willOverlap()) {                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;            }            controller.start();            mGroupFlags &= ~FLAG_RUN_ANIMATION;            mGroupFlags &= ~FLAG_ANIMATION_DONE;            if (cache) {                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;            }            if (mAnimationListener != null) {                mAnimationListener.onAnimationStart(controller.getAnimation());            }        }        int saveCount = 0;        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;        if (clipToPadding) {            saveCount = canvas.save();            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,                    mScrollX + mRight - mLeft - mPaddingRight,                    mScrollY + mBottom - mTop - mPaddingBottom);        }        // We will draw our child's animation, let's reset the flag        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;        boolean more = false;        final long drawingTime = getDrawingTime();//获得当前的绘画时间        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {            for (int i = 0; i < count; i++) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                    more |= drawChild(canvas, child, drawingTime);                }            }        } else {            for (int i = 0; i < count; i++) {                final View child = children[getChildDrawingOrder(count, i)];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                    more |= drawChild(canvas, child, drawingTime);                }            }        }        // Draw any disappearing views that have animations        if (mDisappearingChildren != null) {            final ArrayList<View> disappearingChildren = mDisappearingChildren;            final int disappearingCount = disappearingChildren.size() - 1;            // Go backwards -- we may delete as animations finish            for (int i = disappearingCount; i >= 0; i--) {                final View child = disappearingChildren.get(i);                more |= drawChild(canvas, child, drawingTime);            }        }        if (debugDraw()) {            onDebugDraw(canvas);        }        if (clipToPadding) {            canvas.restoreToCount(saveCount);        }        // mGroupFlags might have been updated by drawChild()        flags = mGroupFlags;         //判断动画是否结束           if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {            invalidate(true);        }        if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&                mLayoutAnimationController.isDone() && !more) {            // We want to erase the drawing cache and notify the listener after the            // next frame is drawn because one extra invalidate() is caused by            // drawChild() after the animation is over            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;            final Runnable end = new Runnable() {               public void run() {                   notifyAnimationListener(); //通知动画结束监听者               }            };            post(end);        }    }
最终会调用到protected void applyTransformation(float interpolatedTime, Transformation t)。这个方法。所以如果想自定义动画的话,就重写这个方法。好了本次分析就到此为止。下片博客将会更加细致的分析,以及android 系统为什么要采取这种方式来进行动画。


3楼liu47036850017分钟前
点赞!老周
2楼hncc201221分钟前
19的老家伙你好
1楼qxf577740423分钟前
Mark 学习
  相关解决方案