zoukankan      html  css  js  c++  java
  • 源代码分析:onAttach, onMeasure, onLayout, onDraw 的顺序。

    从前文《 源代码解析:dialog, popupwindow, 和activity 的第一个view是怎么来的?》中知道了activity第一个view或者说根view或者说mDecorView 事实上就是一个FrameLayout,以及是在系统handleResume的时候增加到系统windowManager中的,并由framework中的ViewRootImpl 接管,通过ViewRootImpl.setView() 開始整个显示过程的。

    这次着重梳理一下view的显示过程(onAttach, onMeasure, onLayout, onDraw )在源代码中的过程。

    从ViewRootImpl.setview 開始。

        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    mView = view;
    
                    <span style="color:#ff0000;">requestLayout();</span>
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        mInputChannel = new InputChannel();
                    }
                    try {
                        mOrigWindowType = mWindowAttributes.type;
                        res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mAttachInfo.mContentInsets,
                                mInputChannel);
                    } catch (RemoteException e) {
                        mAdded = false;
                        mView = null;
                        mAttachInfo.mRootView = null;
                        mInputChannel = null;
                        mFallbackEventHandler.setView(null);
                        unscheduleTraversals();
                        throw new RuntimeException("Adding window failed", e);
                    } finally {
                        if (restore) {
                            attrs.restore();
                        }
                    }
    在第一次赋值mView的时候。会调用ViewRootImpl.requestLayout();

        public void requestLayout() {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    进而scheduleTraversals();

        public void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
    
                //noinspection ConstantConditions
                if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) {
                    final long now = System.nanoTime();
                    Log.d(TAG, "Latency: Scheduled traversal, it has been "
                            + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f)
                            + "ms since the last traversal finished.");
                }
    
                sendEmptyMessage(DO_TRAVERSAL);
            }
        }
    进而在handleMessage() 中

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case DO_TRAVERSAL:
    
                performTraversals();
    <span style="white-space:pre">	</span>}
    进而就是performTraversals(),也就是本次分析的重点。

    这个函数比較长,不适合把所有函数代码都 贴上来。

    就分段叙述。

    1. 函数刚開始的部分,初始化了一些后面会用到的变量和标志位。

            final View host = mView;
            WindowManager.LayoutParams lp = mWindowAttributes;
            final View.AttachInfo attachInfo = mAttachInfo;
            CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
            Rect frame = mWinFrame;
            mTraversalScheduled = false;
            mWillDrawSoon = true;
            boolean windowSizeMayChange = false;
            boolean fullRedrawNeeded = mFullRedrawNeeded;
            boolean newSurface = false;
            boolean surfaceChanged = false;

    2.  接下来,是一个重要的推断, 假设是初次运行,则调用host.dispatchAttachedToWindow(attachInfo, 0);

            if (mFirst) {
                // 略去一大堆赋值
                mLastConfiguration.setTo(host.getResources().getConfiguration());
                host.dispatchAttachedToWindow(attachInfo, 0);
                //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
    
                host.fitSystemWindows(mAttachInfo.mContentInsets);
    
            } else {
                desiredWindowWidth = frame.width();
                desiredWindowHeight = frame.height();
                if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                    if (DEBUG_ORIENTATION) Log.v(TAG,
                            "View " + host + " resized to: " + frame);
                    fullRedrawNeeded = true;
                    mLayoutRequested = true;
                    windowSizeMayChange = true;
                }
            }
    在dispatchAttachedToWindow()中重点处理了三件事:

     2.1. onAttachedToWindow();

     2.2. listener.onViewAttachedToWindow(this); 

     2.3 onWindowVisibilityChanged(vis);

        void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            //System.out.println("Attached! " + this);
            mAttachInfo = info;
            mWindowAttachCount++;
            onAttachedToWindow();
    
            final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                    mOnAttachStateChangeListeners;
            if (listeners != null && listeners.size() > 0) {
                // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
                // perform the dispatching. The iterator is a safe guard against listeners that
                // could mutate the list by calling the various add/remove methods. This prevents
                // the array from being modified while we iterate it.
                for (OnAttachStateChangeListener listener : listeners) {
                    listener.onViewAttachedToWindow(this);
                }
            }
    
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(vis);
            }
        }
    在这里onAttachedToWindow() 的凝视带来了一个问题:仅保证会在onDraw 前调用,而不保证在onMeasure 之前或者之后调用 onAttachedToWindow。

        /**
         * This is called when the view is attached to a window.  At this point it
         * has a Surface and will start drawing.  Note that this function is
         * guaranteed to be called before {@link #onDraw(android.graphics.Canvas)},
         * however it may be called any time before the first onDraw -- including
         * before or after {@link #onMeasure(int, int)}.
         *
         * @see #onDetachedFromWindow()
         */
        protected void onAttachedToWindow() {
        protected void onAttachedToWindow() {
            // Order is important here: LayoutDirection MUST be resolved before Padding
            // and TextDirection
            resolveLayoutDirectionIfNeeded();
            resolvePadding();
            resolveTextDirection();
            if (isFocused()) {
                InputMethodManager imm = InputMethodManager.peekInstance();
                imm.focusIn(this);
            }
        }

    3 fitSystemWindows(), 假设是系统window 则使用padding的值计算一下insets,并開始android.view.View.requestLayout()

        protected boolean fitSystemWindows(Rect insets) {
            if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
                mPaddingLeft = insets.left;
                mPaddingTop = insets.top;
                mPaddingRight = insets.right;
                mPaddingBottom = insets.bottom;
                requestLayout();
                return true;
            }
            return false;
        }

    在android.view.View.requestLayout()中,就是简单的一层一层向上检查parent是否存在,若存在调用parent的requestLayout();

        /**
         * Call this when something has changed which has invalidated the
         * layout of this view. This will schedule a layout pass of the view
         * tree.
         */
        public void requestLayout() {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
            }
    
            mPrivateFlags |= FORCE_LAYOUT;
            mPrivateFlags |= INVALIDATED;
    
            if (mParent != null) {
                if (mLayoutParams != null) {
                    mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
                }
                if (!mParent.isLayoutRequested()) {
                    mParent.requestLayout();
                }
            }
        }
    可是每层调用完毕。并非马上运行layout操作,而是通过赋值标志位mPrivateFlags |= FORCE_LAYOUT;,来标识一下而已。真正的layout过程在后面。

    4. 有一段代码要说一下。

            if (mLayoutRequested && !mStopped) {
                // Execute enqueued actions on every layout in case a view that was detached
                // enqueued an action after being detached
                getRunQueue().executeActions(attachInfo.mHandler);
    RunQueue 是在handler 没有初始化的时候用来处理事件的消息队列。 把给ViewRootImpl post的事件 类型是runnable , 等到handler 构造好后。再发给handler 处理。不是本文的重点,就简单提一下。


    5 接下来是measure 的部分

                boolean goodMeasure = false;
                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    // On large screens, we don't want to allow dialogs to just
                    // stretch to fill the entire width of the screen to display
                    // one line of text.  First try doing the layout at a smaller
                    // size to see if it will fit.
                    final DisplayMetrics packageMetrics = res.getDisplayMetrics();
                    res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
                    int baseSize = 0;
                    if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                        baseSize = (int)mTmpValue.getDimension(packageMetrics);
                    }
                    if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize);
                    if (baseSize != 0 && desiredWindowWidth > baseSize) {
                        childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                        host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                        if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
                                + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                        if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                            goodMeasure = true;
                        } else {
                            // Didn't fit in that size... try expanding a bit.
                            baseSize = (baseSize+desiredWindowWidth)/2;
                            if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize="
                                    + baseSize);
                            childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                            host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                            if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured ("
                                    + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                            if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                                if (DEBUG_DIALOG) Log.v(TAG, "Good!");
                                goodMeasure = true;
                            }
                        }
                    }
                }
                if (!goodMeasure) {
                    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
                    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                        windowSizeMayChange = true;
                    }
                }
    
    这段代码基本就是在某些情况下 用特定的參数来measure , 另外一些情况下。用另外一些值来measure.

    都是调用的host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 仅仅是情况不同,使用的參数不同。

    计算值的这部分參见前文《 源代码分析:LayoutParams的wrap_content, match_parent, 和详细值

    然后就是调用measure()方法

        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
    
                // first clears the measured dimension flag
                mPrivateFlags &= ~MEASURED_DIMENSION_SET;
    
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
                }
    
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
    
                // flag not set, setMeasuredDimension() was not invoked, we raise
                // an exception to warn the developer
                if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
    
                mPrivateFlags |= LAYOUT_REQUIRED;
            }
    
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
        }
    在一些推断和标志位mPrivateFlags 赋值后。调用onMeasure() 方法。 onMeasure() 的讨论也请移步前文《 源代码分析:LayoutParams的wrap_content, match_parent, 和详细值

    在方法的最后给标志位置位 
    mPrivateFlags |= LAYOUT_REQUIRED;

    6 接着一大堆复杂的逻辑和赋值之后。调用了relayoutWindow() 这个还不太明确是怎么回事 // TODO 

                    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    7 接着又是一堆逻辑和赋值,并在某种情况下还调用了host.measure()。算是measure 完毕了

    8. 然后是layout 的部分。

            final boolean didLayout = mLayoutRequested && !mStopped;
            boolean triggerGlobalLayoutListener = didLayout
                    || attachInfo.mRecomputeGlobalAttributes;
            if (didLayout) {
                mLayoutRequested = false;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    在layout() 中,先setFrame,然后推断状态标志位,进而回调onLayout(); 也就是自己定义的部分。

        public void layout(int l, int t, int r, int b) {
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
            boolean changed = setFrame(l, t, r, b);
            if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
                }
    
                onLayout(changed, l, t, r, b);
                mPrivateFlags &= ~LAYOUT_REQUIRED;
    
                if (mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
                    int numListeners = listenersCopy.size();
                    for (int i = 0; i < numListeners; ++i) {
                        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                    }
                }
            }
            mPrivateFlags &= ~FORCE_LAYOUT;
        }

    在setFrame()中,推断新的位置和旧的位置是否一致。

        protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
    
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // Remember our drawn bit
                int drawn = mPrivateFlags & DRAWN;
    
                int oldWidth = mRight - mLeft;
                int oldHeight = mBottom - mTop;
                int newWidth = right - left;
                int newHeight = bottom - top;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
    
                mPrivateFlags |= HAS_BOUNDS;
    
    
                if (sizeChanged) {
                    if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
                        // A change in dimension means an auto-centered pivot point changes, too
                        if (mTransformationInfo != null) {
                            mTransformationInfo.mMatrixDirty = true;
                        }
                    }
                    onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
                }
    
                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    // If we are visible, force the DRAWN bit to on so that
                    // this invalidate will go through (at least to our parent).
                    // This is because someone may have invalidated this view
                    // before this call to setFrame came in, thereby clearing
                    // the DRAWN bit.
                    mPrivateFlags |= DRAWN;
                    invalidate(sizeChanged);
                    // parent display list may need to be recreated based on a change in the bounds
                    // of any child
                    invalidateParentCaches();
                }
    
                // Reset drawn bit to original value (invalidate turns it off)
                mPrivateFlags |= drawn;
    
                mBackgroundSizeChanged = true;
            }
            return changed;
        }
    假设不一致,则调用invalidate();
        void invalidate(boolean invalidateCache) {
    <span style="white-space:pre">		</span>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);
                }
            }
    调用父控件的p.invalidateChild() 来计算并标识 dirty 区域, 区域范围就是子view 所处的区域。

        public void invalidateChild(View child, Rect dirty) {
            checkThread();
            if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
            if (dirty == null) {
                // Fast invalidation for GL-enabled applications; GL must redraw everything
                invalidate();
                return;
            }
            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);
                }
            }
            if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
                mAttachInfo.mSetIgnoreDirtyState = true;
                mAttachInfo.mIgnoreDirtyState = true;
            }
            mDirty.union(dirty);
            if (!mWillDrawSoon) {
                scheduleTraversals();
            }
        }
    假设setframe 的返回值 为true 。则表示 区域的内容发生了变化进而回调onLayout() 方法。和mOnLayoutChangeListeners的onLayoutChange()。


        /**
         * Called from layout when this view should
         * assign a size and position to each of its children.
         *
         * Derived classes with children should override
         * this method and call layout on each of
         * their children.
         * @param changed This is a new size or position for this view
         * @param left Left position, relative to parent
         * @param top Top position, relative to parent
         * @param right Right position, relative to parent
         * @param bottom Bottom position, relative to parent
         */
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    在onLayout() 中, view应该通过调用每个子view 的layout() 方法。来指定每个子view 的大小和位置,。


    9 接着有个computesInternalInsets的部分不太懂,sWindowSession.setInsets // TODO

            if (computesInternalInsets) {
                // Clear the original insets.
                final ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
                insets.reset();
    
                // Compute new insets in place.
                attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
    
                    try {
                        sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                                contentInsets, visibleInsets, touchableRegion);
                    } catch (RemoteException e) {
                    }
                }
            }

    10, 略去一部分处理过程。有焦点的回调和处理, 输入法的处理等。

    11. 进入draw 的部分。

            if (!cancelDraw && !newSurface) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }
                mFullRedrawNeeded = false;
    
                final long drawStartTime;
                if (ViewDebug.DEBUG_LATENCY) {
                    drawStartTime = System.nanoTime();
                }
    
                draw(fullRedrawNeeded);
    
                if (ViewDebug.DEBUG_LATENCY) {
                    mLastDrawDurationNanos = System.nanoTime() - drawStartTime;
                }
    
                if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
                        || mReportNextDraw) {
                    if (LOCAL_LOGV) {
                        Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
                    }
                    mReportNextDraw = false;
                    if (mSurfaceHolder != null && mSurface.isValid()) {
                        mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
                        SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                        if (callbacks != null) {
                            for (SurfaceHolder.Callback c : callbacks) {
                                if (c instanceof SurfaceHolder.Callback2) {
                                    ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                            mSurfaceHolder);
                                }
                            }
                        }
                    }
                    try {
                        sWindowSession.finishDrawing(mWindow);
                    } catch (RemoteException e) {
                    }
                }

    在draw() 方法中,计算了dirty 的区域。 假设使用了硬件加速的话,进行对应的处理,否则使用canvas  来绘制view。

    <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">    private void draw(boolean fullRedrawNeeded) {</span>
    <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">                        mView.draw(canvas);</span>
    在android.view.View.draw(Canvas)中。绘制全部的子类

        public void draw(Canvas canvas) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
            }
    
            final int privateFlags = mPrivateFlags;
            final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
            mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
    
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                final Drawable background = mBGDrawable;
                if (background != null) {
                    final int scrollX = mScrollX;
                    final int scrollY = mScrollY;
    
                    if (mBackgroundSizeChanged) {
                        background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                        mBackgroundSizeChanged = false;
                    }
    
                    if ((scrollX | scrollY) == 0) {
                        background.draw(canvas);
                    } else {
                        canvas.translate(scrollX, scrollY);
                        background.draw(canvas);
                        canvas.translate(-scrollX, -scrollY);
                    }
                }
            }
    
            // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                // Step 4, draw the children
                dispatchDraw(canvas);
    
                // Step 6, draw decorations (scrollbars)
                onDrawScrollBars(canvas);
    
                // we're done...
                return;
            }

    凝视写的非常明确。 在step 1 中绘制背景, 在step 3 中调用自己的ondraw(); 在step 4 中 调用dispatchDraw() 绘制子view 。


    最后调用sWindowSession.finishDrawing() 预计是通知底层完毕吧,不太懂。//TODO

                    try {
                        sWindowSession.finishDrawing(mWindow);
                    } catch (RemoteException e) {
                    }

    draw的部分到此完毕

    http://u.cncn.com/space-323-do-blog-id-377295.html
    http://u.cncn.com/space-323-do-blog-id-377142.html
    http://u.cncn.com/space-mtag-tagid-782.html
    http://u.cncn.com/space-mtag-tagid-783.html

    至此,一次完整的绘制流程就走完了,剩下的就是一遍遍的反复这个过程啦。

    回调过程參见前文《深入分析UI 上层事件处理核心机制 Choreographer

    尾巴


    尾巴。接下来过一下事件的流程吧。













  • 相关阅读:
    C#基础第五天
    基础学习14天 MD5加密
    C#基础第二天
    C#基础学习第一天
    Privacy Policy of ColorfulBroswer
    asp.net mvc 上传图片 摘自mvc 高级编程第311页
    多彩浏览器win10版 隐私声明
    uwp获取版本信息win10 VersionInfo
    uwp ,win10 post json
    windows phone 8.0 app 移植到windows10 app 页面类
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/7251174.html
Copyright © 2011-2022 走看看