android 开发之activity 启动流程《四》

前言

前面几节介绍了activity 的启动流程,但是仅仅梳理到了activty的声明周期的调用并没有深入分析每一个生命周期具体做了什么,本小节着重分析一下Activity  的resume 生命周期

一、resume

前面的章节介绍了AMS 通过Binder远程调用ApplicationThread的scheduleResumeActivity方法

        public final void scheduleResumeActivity(IBinder token, int processState,
                boolean isForward, Bundle resumeArgs) {
            updateProcessState(processState, false);
            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
        }

sendMessge 利用Handler 对象发出一个消息,Handler 收到此消息后调用handleResumeActivity方法。

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
        ......

        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;
            ......
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                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;
                ......
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //添加View
                    wm.addView(decor, l);
                }
            } 
 
            ......
        }
 
        ......
    }

这里先是通过performResumeActivity 调用activity的onResume  之后才调用的wm.addView, 这也是在onResume 里面无法获取View 宽高的原因,因为此时显示的View 还没有加入WindowMangerService系统内。

wm 实际类型是WindowManagerImpl,接着看WindowManagerImpl的 addView。

     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();  
  @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
WindowManagerImpl 的 addView 接着调用了WindowManagerGlobal的addView。
    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent and we're running on L or above (or in the
            // system context), assume we want hardware acceleration.
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;
        //创建ViewRoot
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);
        //保存view
        mViews.add(view);
       //保存ViewRoot
        mRoots.add(root);
        mParams.add(wparams);
        root.setView(view, wparams, panelParentView);
    }
adjustLayoutParamsForSubWindow 用于调整LayoutParams,之后便创建一个ViewRoot对象,android 会通过ViewRoot 发起对于页面的测量,布局,绘制这三个流程。创建ViewRoot 之后会调用其setView 方法。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
             ……
                requestLayout();;
            }
        }
    }

setView 内部调用了requestLayout方法

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里post了一个mTraversalRunnable,我们看看这个runnable做了啥事

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
            try {
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

核心就是调用performTraversals方法,performTraversals是整个绘制流程的开端。

由于performTraversals 非常的长,这里会分为若干个小部分介绍。

第一部分

private void performTraversals() {
    // cache mView since it is used so much below...
    mView就是DecorView根布局,记录ViewRootImpl管理的
   //View树的根节点,final修饰,避免运行过程中修改
    final View host = mView;
  //判断View绘制的条件是否具备.mAdded是DecorView是否成功添加到
  //Window的标示位
    if (host == null || !mAdded)
        return;
    //是否正在遍历
    mIsInTraversal = true;
  //是否马上绘制View
    mWillDrawSoon = true;
   //视图的大小可能改变
    boolean windowSizeMayChange = false;
    boolean newSurface = false;
    boolean surfaceChanged = false;
    WindowManager.LayoutParams lp = mWindowAttributes;
      //顶层视图DecorView所需要窗口的宽度和高度
    int desiredWindowWidth;
    int desiredWindowHeight;
   //DecorView视图是否可见
    final int viewVisibility = getHostVisibility();
    boolean viewVisibilityChanged = mViewVisibility != viewVisibility
            || mNewSurfaceNeeded;


   xxxx
   

    mWindowAttributesChangesFlag = 0;
     //将一个全局的Rect对象赋给了局部frame对象,
     //它用来保存Activity窗口当前的宽度和高度
    Rect frame = mWinFrame;
    if (mFirst) {
     //在构造方法中mFirst已经设置为true,
    //表示是否是第一次被请求执行测量、布局和绘制操作,

       //是否需要全部重绘
        mFullRedrawNeeded = true;
        mLayoutRequested = true;


//如果窗口的类型是有状态栏的,那么Activity窗口的宽度和高度
//就是除了状态栏
        if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
            // NOTE -- system code, won't try to do compat mode.
            Point size = new Point();
            mDisplay.getRealSize(size);
            desiredWindowWidth = size.x;
            desiredWindowHeight = size.y;
        } else {
  	 //否则Activity窗口的宽度和高度就是整个屏幕的宽高
            DisplayMetrics packageMetrics =
                mView.getContext().getResources().getDisplayMetrics();
            desiredWindowWidth = packageMetrics.widthPixels;
            desiredWindowHeight = packageMetrics.heightPixels;
        }


}else {
//除了第一次被请求执行测量、布局和绘制操作以外。
// 期望宽高为frame 保存的宽高。
    desiredWindowWidth = frame.width();
    desiredWindowHeight = frame.height();
   //mWidth ,mHeight 是页面当前显示的宽高,
//如果当前显示宽高与期望宽高不一致
    if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
   //需要进行完整的重绘以适应新的窗口尺寸
        mFullRedrawNeeded = true;
    //需要对控件树进行重新布局
        mLayoutRequested = true;
        windowSizeMayChange = true;
    }
}

WMS 有可能会单方面修改页面高度。例如页面当先正在全屏显示,由于某些场景此时WMS 希望页面高度变为屏幕的一般,此时wms 会将新的宽高传递到mWinFrame 里面,同时调用requestLayout,此时frame里面保存的就是WMS 最细传递过来的宽高,自然与当前显示的宽高不一致。


boolean insetsChanged = false;

boolean layoutRequested = mLayoutRequested && !mStopped;
if (layoutRequested) {
    final Resources res = mView.getContext().getResources();

    if (mFirst) {
        // make sure touch mode code executes by setting cached value
        // to opposite of the added touch mode.
        mAttachInfo.mInTouchMode = !mAddedTouchMode;
        ensureTouchModeLocally(mAddedTouchMode);
    } else {
         //mPendingOverscanInsets  是AMS主动传递过来的数据
  //下面的pending 也是一样的
        if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
            insetsChanged = true;
        }
        if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
            insetsChanged = true;
        }
        if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
            insetsChanged = true;
        }
        if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
            mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
            if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
                    + mAttachInfo.mVisibleInsets);
        }
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            windowSizeMayChange = true;
             //这里可看到

            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // NOTE -- system code, won't try to do compat mode.
 		
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
 		      //也是获取屏幕的宽高,不知道两者之间的区别是什么
                DisplayMetrics packageMetrics = res.getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
        }
    }

    // Ask host how big it wants to be
   //内部会调用测量的方法,测量当前页面的大小
    windowSizeMayChange |= measureHierarchy(host, lp, res,
            desiredWindowWidth, desiredWindowHeight);
}

这里涉及到几个屏幕区域的问题
mOverscanInsets  在手机端一般不存在OverScan区域,在电视端存在。

mContentInsets  记录的是内容区域,

mStableInsets   记录了Stable区域,比mContentInsets区域大 ,包含了状态栏但是不包含导航栏。
mVisibleInsets  记录了可见区域。

这些区域一般都是不变的。

这里我们可以看到即使页面的宽高设置为WRAP_CONTENT,也即跟内容大小一致,但是实际上desiredWindowWidth与desiredWindowHeight 依然是屏幕的宽高,也就是说设WRAP_CONTENT对于Activity 的大小无效,此时Actiivty 依然是满屏大小。

之后会第一次调用measureHierarchy,这个方法内部会测量View的大小。

执行完measureHierarchy方法后,ViewRootImpl就知道了View树需要的大小,并同时修改windowSizeMayChange的值,它的表示有可能需要改变窗口大小以适应View树的大小要求。

例如第一次测量的时候经过测量之后知道了页面的根View也就是DectorView的大小,但是此时还没有结算Window 窗口的胆小,因此第一次调用measureHierarchy返回的是true。

假如是第若干次调用measureHierarchy,此时系统的Window的大小已经确定了,调用measureHierarchy 发现这次根View的大小与上次不一样了,根View大小变了就可能导致Window 大小的变化,后面会通过WMS重新计算Window 的大小。

        if (collectViewAttributes()) {
            params = lp;
        }
        if (mAttachInfo.mForceReportNewAttributes) {
            mAttachInfo.mForceReportNewAttributes = false;
            params = lp;
        }

        if (mFirst || mAttachInfo.mViewVisibilityChanged) {
            mAttachInfo.mViewVisibilityChanged = false;
            int resizeMode = mSoftInputMode &
                    WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
            // If we are in auto resize mode, then we need to determine
            // what mode to use now.
            if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                final int N = mAttachInfo.mScrollContainers.size();
                for (int i=0; i<N; i++) {
                    if (mAttachInfo.mScrollContainers.get(i).isShown()) {
                        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
                    }
                }
                if (resizeMode == 0) {
                    resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
                }
                if ((lp.softInputMode &
                        WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
                    lp.softInputMode = (lp.softInputMode &
                            ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
                            resizeMode;
                    params = lp;
                }
            }
        }

        if (params != null) {
            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                if (!PixelFormat.formatHasAlpha(params.format)) {
                    params.format = PixelFormat.TRANSLUCENT;
                }
            }
            mAttachInfo.mOverscanRequested = (params.flags
                    & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
        }

        if (mApplyInsetsRequested) {
            mApplyInsetsRequested = false;
            mLastOverscanRequested = mAttachInfo.mOverscanRequested;
            dispatchApplyInsets(host);
            if (mLayoutRequested) {
                // Short-circuit catching a new layout request here, so
                // we don't need to go through two layout passes when things
                // change due to fitting system windows, which can happen a lot.
                windowSizeMayChange |= measureHierarchy(host, lp,
                        mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
            }
        }

这一段主要是处理fitSystemWindow 属性,当用户在xml 布局文件里面的某个View 中使用了fitSystemWindow 属性,然后在onCreate 调用了view.requestApplyInsets 方法,这个方法会一直向上调用直到ViewRoot的requestApplyInsets方法,ViewRoot 子方法内设置mApplyInsetsRequested=true,dispatchApplyInsets 方法会直到那么设置了fitSystemWindow属性的View,给这个View 添加一个相当于状态栏高度的Padding,这个会导致整个View 树的高度发生变化因此会再次measureHierarchy 方法测量View 树的高度。

       boolean windowShouldResize = layoutRequested && windowSizeMayChange
                && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                frame.height() < desiredWindowHeight && frame.height() != mHeight));

        // Determine whether to compute insets.
        // If there are no inset listeners remaining then we may still need to compute
        // insets in case the old insets were non-empty and must be reset.
        final boolean computesInternalInsets =
                mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
                        || mAttachInfo.mHasNonEmptyGivenInternalInsets;

        boolean insetsPending = false;
        int relayoutResult = 0;

        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null) {

            if (viewVisibility == View.VISIBLE) {
                insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
            }

            if (mSurfaceHolder != null) {
                mSurfaceHolder.mSurfaceLock.lock();
                mDrawingAllowed = true;
            }

            boolean hwInitialized = false;
            boolean contentInsetsChanged = false;
            boolean hadSurface = mSurface.isValid();

            try {
                final int surfaceGenerationId = mSurface.getGenerationId();
                //测量Window 大小。
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                if (!mDrawDuringWindowsAnimating &&
                        (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
                    mWindowsAnimating = true;
                }


                if (mPendingConfiguration.seq != 0) {
                    if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
                            + mPendingConfiguration);
                    updateConfiguration(mPendingConfiguration, !mFirst);
                    mPendingConfiguration.seq = 0;
                }

                final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
                        mAttachInfo.mOverscanInsets);
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
                        mAttachInfo.mVisibleInsets);
                final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                        mAttachInfo.mStableInsets);
                if (contentInsetsChanged) {
                    if (mWidth > 0 && mHeight > 0 && lp != null &&
                            ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
                                    & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
                            mSurface != null && mSurface.isValid() &&
                            !mAttachInfo.mTurnOffWindowResizeAnim &&
                            mAttachInfo.mHardwareRenderer != null &&
                            mAttachInfo.mHardwareRenderer.isEnabled() &&
                            lp != null && !PixelFormat.formatHasAlpha(lp.format)
                            && !mBlockResizeBuffer) {

                        disposeResizeBuffer();

                    }
                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
                    if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
                            + mAttachInfo.mContentInsets);
                }
                //overscan 区域变化了,则标示该区域变换了。
                //其他几个区域都是这样操作。
                if (overscanInsetsChanged) {
                    mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
                    // Need to relayout with content insets.
                    contentInsetsChanged = true;
                }
                if (stableInsetsChanged) {
                    mAttachInfo.mStableInsets.set(mPendingStableInsets);
                    if (DEBUG_LAYOUT) Log.v(TAG, "Decor insets changing to: "
                            + mAttachInfo.mStableInsets);
                    // Need to relayout with content insets.
                    contentInsetsChanged = true;
                }
                if (contentInsetsChanged || mLastSystemUiVisibility !=
                        mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
                        || mLastOverscanRequested != mAttachInfo.mOverscanRequested) {
                    mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                    mLastOverscanRequested = mAttachInfo.mOverscanRequested;
                    mApplyInsetsRequested = false;
                    dispatchApplyInsets(host);
                }
                if (visibleInsetsChanged) {
                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                    if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
                            + mAttachInfo.mVisibleInsets);
                }

                //xxxx  删除部分代码
            } catch (RemoteException e) {
            }

            //将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个
            // AttachInfo对象的成员变量mWindowLeft和mWindowTop中
            mAttachInfo.mWindowLeft = frame.left;
            mAttachInfo.mWindowTop = frame.top;
            //将计算得到的Activity Window的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中。
            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }

            // xxx

            if (!mStopped) {
                //这段代码用来检查是否需要重新测量Activity窗口的大小。如果满足以下条件之一,那么就需要重新测量:
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    //mWidth,mHeight 是WMS计算出来的窗口的大小。
                    //View 树的大小不应该操作窗口的大小。
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);


                    // //以WMS返回的宽高为限制再次测量一次
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;

                    if (lp.horizontalWeight > 0.0f) {
                        //WMS返回的宽是100,实际测量的结果是50,然后用户还设置了horizontalWeight(0-1)
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width,
                                View.MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        //同上
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
                                View.MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        //因为设置了Weight 那么再次测试View   树的大小。
                        if (DEBUG_LAYOUT) Log.v(TAG,
                                "And hey let's measure once more: width=" + width
                                        + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.

            // TODO: Well, we are checking whether the frame has changed similarly
            // to how this is done for the insets. This is however incorrect since
            // the insets and the frame are translated. For example, the old frame
            // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
            // reported frame is (2, 2 - 2, 2) which implies no change but this is not
            // true since we are comparing a not translated value to a translated one.
            // This scenario is rare but we may want to fix that.

            final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left
                    || mAttachInfo.mWindowTop != frame.top);
            if (windowMoved) {
                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWinFrame(frame);
                }
                mAttachInfo.mWindowLeft = frame.left;
                mAttachInfo.mWindowTop = frame.top;
            }
        }
windowShouldResize 表示是否需要重新请求wms 测量Window 的大小。
layoutRequested 为true,说明view正在调用requestLayout,请求重新布局。
windowSizeMayChange 是前面测量View 树的高度返回的结构,当View 的高度发生变化的时候,那么Window 的大小也可能要跟着发生变化。

mWidth  记录的是上一次WMS 测量的Window 的大小,同理mHeight 记录的是上次的高度。
当这两者与View 树的测量大小不一致的时候表示View 树大小可能变化了,因此需要WMS 重新测量Window。
最后的条件是如果窗口width或height被设置成WRAP_CONTENT,计算出来的窗口大小desiredWindowWidth/desiredWindowHeight 与上一次测量保存的frame.width()/frame.height()
 同时与WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致,那也说明窗口大小变化了。但是数据实话不清楚这个条件对应现实中的哪一种情形。


除了windowShouldResize 可以决定是否重新计算Window 的大小还有其他几个条件,也就是下面这个判断

if (mFirst || windowShouldResize || insetsChanged ||
        viewVisibilityChanged || params != null)

mFirst  表示Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true,这个时候Window 的大小一次都还没有测量过。

windowShouldResize t上面介绍了。

insetsChanged true 说明此次窗口overscan等一些边衬区域发生了改变,与上次不一样。

viewVisibilityChanged 说明View的可见性发生了变化
params 即窗口属性发生了变化,指向了mWindowAttributes

接着变调用relayoutWindow 方法请求WMS 测量Window 窗口的大小。

    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
                               boolean insetsPending) throws RemoteException {
        // xxx
        //注意后面参数是用来保存测量结果的。
        //例如mWinFrame 保存Window 大小。
        int relayoutResult = mWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingConfiguration, mSurface);
 
        //xxx
        return relayoutResult;
    }

调用WindowSession的relayout 方法,WindowSession 的relayout 内部会调用WMS 的方法计算Window的大小。窗口大小的结果会保存在mWinFrame 数组内部。
            mAttachInfo.mWindowLeft = frame.left;
            mAttachInfo.mWindowTop = frame.top;
            //将计算得到的Activity Window的宽度和高度保存在ViewRoot类的成员变量mWidth和mHeight中。
            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }

frame 与mWinFrame  是同一个数组。在relayout 计算完之后这里将Window 的位置保存在mAttachInfo里面,Window 的大小保存在mWidth,mHeight 变量里面。

这里之所以将大小赋值给mWidth,mHeight的原因是因为WMS有可能主动修改mWinFrame的值,例如修改了修改某些影响页面显示范围的系统设置,此时系统就会通过WMS 主动调整页面的大小与位置。

当测量完Window 之后可能会再次测量View 树的大小。

               if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    //mWidth,mHeight 是WMS计算出来的窗口的大小。
                    //View 树的大小不应该操作窗口的大小。
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);


                    // //以WMS返回的宽高为限制再次测量一次
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
mWidth != host.getMeasuredWidth() ||mHeight != host.getMeasuredHeight()表示窗口的大小与上次View 树的大小不一样,此时需要重新测量View 树。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

这两行代码是根据Window 的大小来生成MeasureSpec,生成的MeasureSpec的大小一定是不大于其第一个参数,这样就保证了View 树的宽高不会大于Window 的宽高。之后就调用performMeasure再次测试View 树的大小。测量完成之后会检查是否设置了宽高的Weight属性,如果设置了此时会再次调用performMeasure测量View 树的大小。

到此第一部分就分析完了,这一部分主要就是测量View树的宽高以及计算Window的宽高。

第二部分

        final boolean didLayout = layoutRequested && !mStopped;
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area

            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                // start out transparent
                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
                host.getLocationInWindow(mTmpLocation);
                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);

                host.gatherTransparentRegion(mTransparentRegion);
                if (mTranslator != null) {
                    mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
                }

                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    mFullRedrawNeeded = true;
                    // reconfigure window manager
                    try {
                        mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                    } catch (RemoteException e) {
                    }
                }
            }

            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals -- after setFrame");
                host.debug();
            }
        }

这里主要的就是调用performLayout 方法进行布局。performLayout方法会调用View的layout方法,在layout方法中回调onLayout方法,开发者可以重写这个方法,这个具体分析与measure类似。

第三部分

       //执行回调
     boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
                viewVisibility != View.VISIBLE;

        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }
               //开始绘制View

                performDraw();
            }
        } else {
            if (viewVisibility == View.VISIBLE) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

在View的Draw方法里绘制一个View分一下几步

drawBackground 绘制背景
保存画布图层
onDraw(canvas) 回调 绘制内容 由子类去实现
dispatchDraw(canvas) 有子view 就绘制子view
绘制淡入淡出效果并恢复图层
onDrawForeground(canvas) 画装饰 比如前景色 滚动条
 

二、使用步骤

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值