初探窗口

在知道Activity的生命周期调用时机之后,这些都太抽象了,到底我们看到的界面是怎么加载出来的才是最感兴趣的,这里面就主要涉及Activity的Window对象了,具体是什么机制呢?还是回到源码。

加载布局

还是先从Activity的加载过程中来看看到底是怎么加载布局的,来到Activity的attach方法:

Activity.attach

    final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor) {attachBaseContext(context);mFragments.attachHost(null /*parent*/);//初始化Window对象mWindow = new PhoneWindow(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);}mUiThread = Thread.currentThread();mMainThread = aThread;mInstrumentation = instr;mToken = token;mIdent = ident;mApplication = application;mIntent = intent;mReferrer = referrer;mComponent = intent.getComponent();mActivityInfo = info;mTitle = title;mParent = parent;mEmbeddedID = id;mLastNonConfigurationInstances = lastNonConfigurationInstances;if (voiceInteractor != null) {if (lastNonConfigurationInstances != null) {mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;} else {mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,Looper.myLooper());}}//设置WindowManagermWindow.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();mCurrentConfig = config;}

在这个方法里对Activity的一些成员变量进行了初始化(上下文,线程,标题。。。),特别是mWindow初始化为PhoneWindow这个类的实例。

好了,在初始化了Window之后,来到我们最熟悉的onCreate方法,我们一般在这个方法中会调用setContentView指定该Activity的布局文件,来看看这个方法:

Activity.setContentView

    public void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}public Window getWindow() {return mWindow;}    

这里的getWindow获取了mWindow变量,我们知道刚才在attach方法中它初始化为了PhoneWindow的实例,其实就是调用的PhoneWindow的setContentView方法:

PhoneWindow.setContentView

    @Overridepublic void setContentView(int layoutResID) {if (mContentParent == null) {installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}}

第一次的话,mContentParent为空,执行installDecor:

PhoneWindow.installDecor

    private void installDecor() {if (mDecor == null) {mDecor = generateDecor();mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}}if (mContentParent == null) {mContentParent = generateLayout(mDecor);......}

mDecor对象声明如下:

private DecorView mDecor;

这是window最开始的视图,其他布局的父容器,DecorView继承自FrameLayout,然后因为mContentParent为空,调用generateLayout(mDecor):

PhoneWindow.generateLayout(mDecor)

protected ViewGroup generateLayout(DecorView decor) {......mDecor.startChanging();//关键执行的代码View in = mLayoutInflater.inflate(layoutResource, null);decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));mContentRoot = (ViewGroup) in;//将布局赋值给mContentRootViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);......return contentParent;
}

根据不同的特性(features)来判断相应的布局文件,然后通过mLayoutInflater.inflate方法来解析加载,最后添加到mDecor上面,在这里我们发现mContentParent是mContentRoot的子布局,mContentRoot是mDecor的子布局,再来看看inflate方法是怎么来加载xml文件视图的:

LayoutInflater.inflater

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}final XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}
}    

再调用重载方法后新建了XML解析器后再调用重载方法:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {// Look for the root node.int type;while ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {// Empty}if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}final String name = parser.getName();if (DEBUG) {System.out.println("**************************");System.out.println("Creating root view: "+ name);System.out.println("**************************");}if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, inflaterContext, attrs, false);} else {// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {if (DEBUG) {System.out.println("Creating params from root: " +root);}// Create layout params that match root, if suppliedparams = root.generateLayoutParams(attrs);if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);}}if (DEBUG) {System.out.println("-----> start inflating children");}// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);if (DEBUG) {System.out.println("-----> done inflating children");}// We are supposed to attach all the views we found (int temp)// to root. Do that now.if (root != null && attachToRoot) {root.addView(temp, params);}// Decide whether to return the root that was passed in or the// top view found in xml.if (root == null || !attachToRoot) {result = temp;}}} catch (XmlPullParserException e) {InflateException ex = new InflateException(e.getMessage());ex.initCause(e);throw ex;} catch (Exception e) {InflateException ex = new InflateException(parser.getPositionDescription()+ ": " + e.getMessage());ex.initCause(e);throw ex;} finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;}Trace.traceEnd(Trace.TRACE_TAG_VIEW);return result;}}

这里原理是通过解析XML文件依次将组件添加到指定的root容器布局中,最后返回解析出来的布局视图即可。

再回到PhoneWindow.setContentView,看到这行代码:

mLayoutInflater.inflate(layoutResID, mContentParent);

将我们指定的布局添加到mContentParent之上。

最后梳理一下(布局从下到上):

mDecor->mContentRoot->mContentParent->自己的布局

绘制布局

了解了加载XML布局文件的整个过程,来看看这些组件是怎么绘制的,并展现到Activity上。还是从生命周期方法开始看,在onCreate方法中我们加载了视图,下一个生命周期就是onResume方法,不过在执行onResume之前会先执行ActivityThread.handleResumeActivity:

ActivityThread.handleResumeActivity

            ......if (!r.activity.mFinished && willBeVisible&& r.activity.mDecor != null && !r.hideForNow) {if (r.newConfig != null) {r.tmpConfig.setTo(r.newConfig);if (r.overrideConfig != null) {r.tmpConfig.updateFrom(r.overrideConfig);}if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "+ r.activityInfo.name + " with newConfig " + r.tmpConfig);performConfigurationChanged(r.activity, r.tmpConfig);freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));r.newConfig = null;}if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="+ isForward);WindowManager.LayoutParams l = r.window.getAttributes();if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)!= forwardBit) {l.softInputMode = (l.softInputMode& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))| forwardBit;if (r.activity.mVisibleFromClient) {ViewManager wm = a.getWindowManager();View decor = r.window.getDecorView();wm.updateViewLayout(decor, l);}}r.activity.mVisibleFromServer = true;mNumVisibleActivities++;if (r.activity.mVisibleFromClient) {r.activity.makeVisible();//使Activity可见}}......

主要关注上面的几行有关WindowManager和Activity显示有关的方法,在初始化了相关对象后调用r.activity.makeVisible()

Activity.makeVisible()

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

这里的循环只执行一次,在执行完后会将mWindowAdded这个全局变量置为true,ViewManager的通过getWindowManager初始化:

    public WindowManager getWindowManager() {return mWindowManager;}

其实就是返回Activity的WindowManager,这个变量在一开始的Activity.attach方法中赋值:

mWindowManager=mWindow.getWindowManager();

Window.getWindowManager

public WindowManager getWindowManager() {return mWindowManager;
}

还是没有具体的赋值,找了一下源码后发现有setWindowManager这个方法,这个方法也是在Activity的attach方法中调用

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();

Window.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);}

所以每个Activity都会对应一个WindowWindow会对应一个WindowManager,最后返回的是WindowManagerImpl实例,该类是WindowManager接口的具体实现,再回到

Activity.makeVisible()

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

在wm赋值后调用wm.addView(mDecor, getWindow().getAttributes()),具体的实现在WindowManagerImpl中:

WindowManagerImpl.addView

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mDisplay, mParentWindow);}

调用WindowManagerGlobal的addView方法,并且将mDecor传入,此类为单例:

WindowManagerGlobal.addView

    public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}//获取窗口参数final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {//获取上下文final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}ViewRootImpl root;View panelParentView = null;synchronized (mLock) {if (mSystemPropertyUpdater == null) {mSystemPropertyUpdater = new Runnable() {@Override public void run() {synchronized (mLock) {for (int i = mRoots.size() - 1; i >= 0; --i) {mRoots.get(i).loadSystemProperties();}}}};SystemProperties.addChangeCallback(mSystemPropertyUpdater);}int index = findViewLocked(view, false);if (index >= 0) {if (mDyingViews.contains(view)) {mRoots.get(index).doDie();} else {throw new IllegalStateException("View " + view+ " has already been added to the window manager.");}}if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {final int count = mViews.size();for (int i = 0; i < count; i++) {if (mRoots.get(i).mWindow.asBinder() == wparams.token) {panelParentView = mViews.get(i);}}}root = new ViewRootImpl(view.getContext(), display);//设置属性view.setLayoutParams(wparams);//添加布局mViews.add(view);mRoots.add(root);mParams.add(wparams);}try {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {synchronized (mLock) {final int index = findViewLocked(view, false);if (index >= 0) {removeViewLocked(index, true);}}throw e;}}

这里面主要涉及三个数组的操作:

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();

mViews用来保存根布局(mDecor),mRoots用来保存ViewRootImpl(此类用来关联View和ViewManager),mParams用来保存布局的属性

最后调用ViewRootImpl.setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {......requestLayout();......
}

这个方法里面主要执行了这一句用来请求绘制布局:

ViewRootImpl.requestLayout

    @Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}

首先是检查线程,如果不是UI线程则报错:

    void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}

然后调用scheduleTraversals

    void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//通过Handler发送异步消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}

mChoreographer.postCallback这一句内部是通过handler发送了一个异步消息,执行mTraversalRunnable的run方法:

    final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}

ViewRootImpl.doTraversal

    void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}

然后调用performTraversals,这个法就是绘制布局的主要执行方法,在这里面会测量组件的大小,计算位置,绘制组件:

ViewRootImpl.performTraversals

    private void performTraversals() {......//测量组件if (!mStopped || mReportNextDraw) {boolean focusChangedDueToTouchMode = ensureTouchModeLocally((relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="+ mWidth + " measuredWidth=" + host.getMeasuredWidth()+ " mHeight=" + mHeight+ " measuredHeight=" + host.getMeasuredHeight()+ " coveredInsetsChanged=" + contentInsetsChanged);// Ask host how big it wants to beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);int width = host.getMeasuredWidth();int height = host.getMeasuredHeight();boolean measureAgain = false;if (lp.horizontalWeight > 0.0f) {width += (int) ((mWidth - width) * lp.horizontalWeight);childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);measureAgain = true;}if (lp.verticalWeight > 0.0f) {height += (int) ((mHeight - height) * lp.verticalWeight);childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);measureAgain = true;}if (measureAgain) {if (DEBUG_LAYOUT) Log.v(TAG,"And hey let's measure once more: width=" + width+ " height=" + height);//主要调用的方法performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}layoutRequested = true;}}}......//计算组件的位置final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout|| mAttachInfo.mRecomputeGlobalAttributes;if (didLayout) {//主要调用的方法performLayout(lp, desiredWindowWidth, desiredWindowHeight);if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {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;try {mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);} catch (RemoteException e) {}}}if (DBG) {System.out.println("======================================");System.out.println("performTraversals -- after setFrame");host.debug();}} ......//开始绘制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();}//主要方法performDraw();}} else {if (viewVisibility == View.VISIBLE) {// 再次请求scheduleTraversals();} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {for (int i = 0; i < mPendingTransitions.size(); ++i) {mPendingTransitions.get(i).endChangingAnimations();}mPendingTransitions.clear();}}        }

主要就是三个方法,负责大小,位置,绘制:

  1. performMeasure
  2. performLayout
  3. performDraw

先来看看performMeasure,测量组件的大小

ViewRootImpl.performMeasure

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");try {mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}

这里调用mView的measure

View.measure

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {......onMeasure(widthMeasureSpec, heightMeasureSpec);......}

其实mView也就是我们开始说到的mDecor,所以onMeasure具体实现应该来到DecorView ,也就是FrameLayout:

FrameLayout.onMeasure

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int count = getChildCount();final boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;for (int i = 0; i < count; i++) {//循环遍历子控件final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//测量子空间间距final LayoutParams lp = (LayoutParams) child.getLayoutParams();maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);childState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {mMatchParentChildren.add(child);}}}}// 计算内边距(padding)maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// 检查最小的高度和宽度maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 检查前景的最小宽度和高度final Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));count = mMatchParentChildren.size();if (count > 1) {//子控件大于1for (int i = 0; i < count; i++) {final View child = mMatchParentChildren.get(i);final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec;if (lp.width == LayoutParams.MATCH_PARENT) {final int width = Math.max(0, getMeasuredWidth()- getPaddingLeftWithForeground() - getPaddingRightWithForeground()- lp.leftMargin - lp.rightMargin);childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);} else {childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() +lp.leftMargin + lp.rightMargin,lp.width);}final int childHeightMeasureSpec;if (lp.height == LayoutParams.MATCH_PARENT) {final int height = Math.max(0, getMeasuredHeight()- getPaddingTopWithForeground() - getPaddingBottomWithForeground()- lp.topMargin - lp.bottomMargin);childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);} else {childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,getPaddingTopWithForeground() + getPaddingBottomWithForeground() +lp.topMargin + lp.bottomMargin,lp.height);}//调用子控件的测量方法child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}}

先测量自己的高度和宽度,然后调用setMeasuredDimension设置测量的尺寸,这是父类View的方法:

View.setMeasuredDimension

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {boolean optical = isLayoutModeOptical(this);if (optical != isLayoutModeOptical(mParent)) {Insets insets = getOpticalInsets();int opticalWidth  = insets.left + insets.right;int opticalHeight = insets.top  + insets.bottom;measuredWidth  += optical ? opticalWidth  : -opticalWidth;measuredHeight += optical ? opticalHeight : -opticalHeight;}setMeasuredDimensionRaw(measuredWidth, measuredHeight);}private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}

将测量出来的高度和宽度保存到mMeasuredWidth和mMeasuredHeight中,如果有子控件就循环测量子控件的尺寸。

测量完毕,来到performLayout计算位置:

ViewRootImpl.performLayout

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {mLayoutRequested = false;mScrollMayChange = true;mInLayout = true;final View host = mView;if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {Log.v(TAG, "Laying out " + host + " to (" +host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");}Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");try {//主要执行的方法host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());mInLayout = false;int numViewsRequestingLayout = mLayoutRequesters.size();if (numViewsRequestingLayout > 0) {ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);if (validLayoutRequesters != null) {mHandlingLayoutInLayoutRequest = true;int numValidRequests = validLayoutRequesters.size();for (int i = 0; i < numValidRequests; ++i) {final View view = validLayoutRequesters.get(i);Log.w("View", "requestLayout() improperly called by " + view +" during layout: running second layout pass");view.requestLayout();}measureHierarchy(host, lp, mView.getContext().getResources(),desiredWindowWidth, desiredWindowHeight);mInLayout = true;host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());mHandlingLayoutInLayoutRequest = false;validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);if (validLayoutRequesters != null) {final ArrayList<View> finalRequesters = validLayoutRequesters;getRunQueue().post(new Runnable() {@Overridepublic void run() {int numValidRequests = finalRequesters.size();for (int i = 0; i < numValidRequests; ++i) {final View view = finalRequesters.get(i);Log.w("View", "requestLayout() improperly called by " + view +" during second layout pass: posting in next frame");view.requestLayout();}}});}}}} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}mInLayout = false;}

在内部还是调用了View 的layout方法:

View.layout

    public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;ListenerInfo li = mListenerInfo;if (li != null && li.mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>)li.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 &= ~PFLAG_FORCE_LAYOUT;mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

具体的方法在onLayout中,同样来到FrameLayout的onLayout:

FrameLayout.onLayout

    @Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */);}

调用layoutChildren

    void layoutChildren(int left, int top, int right, int bottom,boolean forceLeftGravity) {final int count = getChildCount();final int parentLeft = getPaddingLeftWithForeground();final int parentRight = right - left - getPaddingRightWithForeground();final int parentTop = getPaddingTopWithForeground();final int parentBottom = bottom - top - getPaddingBottomWithForeground();for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();int childLeft;int childTop;int gravity = lp.gravity;if (gravity == -1) {gravity = DEFAULT_CHILD_GRAVITY;}final int layoutDirection = getLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:if (!forceLeftGravity) {childLeft = parentRight - width - lp.rightMargin;break;}case Gravity.LEFT:default:childLeft = parentLeft + lp.leftMargin;}switch (verticalGravity) {case Gravity.TOP:childTop = parentTop + lp.topMargin;break;case Gravity.CENTER_VERTICAL:childTop = parentTop + (parentBottom - parentTop - height) / 2 +lp.topMargin - lp.bottomMargin;break;case Gravity.BOTTOM:childTop = parentBottom - height - lp.bottomMargin;break;default:childTop = parentTop + lp.topMargin;}child.layout(childLeft, childTop, childLeft + width, childTop + height);}}}

原理跟刚才的测量尺寸差不多,也是先计算自己的位置,然后再循环遍历子控件,调用子控件的layout方法,最后得出计算位置完毕。

最后来到第三步,performDraw,绘制布局:

ViewRootImpl.performDraw

    private void performDraw() {......draw(fullRedrawNeeded);......}

主要执行了draw方法:

ViewRootImpl.draw

    private void draw(boolean fullRedrawNeeded) {......if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return;}......}

再来到drawSoftware方法:

ViewRootImpl.drawSoftware

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {......mView.draw(canvas);......}

因为mView是FrameLayout,所以来到FrameLayout的draw方法:

FrameLayout.draw

    public void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_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 neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// 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 contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// we're done...return;}/** Here we do the full fledged routine...* (this is an uncommon case where speed matters less,* this is why we repeat some of the tests that have been* done above)*/boolean drawTop = false;boolean drawBottom = false;boolean drawLeft = false;boolean drawRight = false;float topFadeStrength = 0.0f;float bottomFadeStrength = 0.0f;float leftFadeStrength = 0.0f;float rightFadeStrength = 0.0f;// Step 2, save the canvas' layersint paddingLeft = mPaddingLeft;final boolean offsetRequired = isPaddingOffsetRequired();if (offsetRequired) {paddingLeft += getLeftPaddingOffset();}int left = mScrollX + paddingLeft;int right = left + mRight - mLeft - mPaddingRight - paddingLeft;int top = mScrollY + getFadeTop(offsetRequired);int bottom = top + getFadeHeight(offsetRequired);if (offsetRequired) {right += getRightPaddingOffset();bottom += getBottomPaddingOffset();}final ScrollabilityCache scrollabilityCache = mScrollCache;final float fadeHeight = scrollabilityCache.fadingEdgeLength;int length = (int) fadeHeight;// clip the fade length if top and bottom fades overlap// overlapping fades produce odd-looking artifactsif (verticalEdges && (top + length > bottom - length)) {length = (bottom - top) / 2;}// also clip horizontal fades if necessaryif (horizontalEdges && (left + length > right - length)) {length = (right - left) / 2;}if (verticalEdges) {topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));drawTop = topFadeStrength * fadeHeight > 1.0f;bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));drawBottom = bottomFadeStrength * fadeHeight > 1.0f;}if (horizontalEdges) {leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));drawLeft = leftFadeStrength * fadeHeight > 1.0f;rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));drawRight = rightFadeStrength * fadeHeight > 1.0f;}saveCount = canvas.getSaveCount();int solidColor = getSolidColor();if (solidColor == 0) {final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;if (drawTop) {canvas.saveLayer(left, top, right, top + length, null, flags);}if (drawBottom) {canvas.saveLayer(left, bottom - length, right, bottom, null, flags);}if (drawLeft) {canvas.saveLayer(left, top, left + length, bottom, null, flags);}if (drawRight) {canvas.saveLayer(right - length, top, right, bottom, null, flags);}} else {scrollabilityCache.setFadeColor(solidColor);}// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Step 5, draw the fade effect and restore layersfinal Paint p = scrollabilityCache.paint;final Matrix matrix = scrollabilityCache.matrix;final Shader fade = scrollabilityCache.shader;if (drawTop) {matrix.setScale(1, fadeHeight * topFadeStrength);matrix.postTranslate(left, top);fade.setLocalMatrix(matrix);p.setShader(fade);canvas.drawRect(left, top, right, top + length, p);}if (drawBottom) {matrix.setScale(1, fadeHeight * bottomFadeStrength);matrix.postRotate(180);matrix.postTranslate(left, bottom);fade.setLocalMatrix(matrix);p.setShader(fade);canvas.drawRect(left, bottom - length, right, bottom, p);}if (drawLeft) {matrix.setScale(1, fadeHeight * leftFadeStrength);matrix.postRotate(-90);matrix.postTranslate(left, top);fade.setLocalMatrix(matrix);p.setShader(fade);canvas.drawRect(left, top, left + length, bottom, p);}if (drawRight) {matrix.setScale(1, fadeHeight * rightFadeStrength);matrix.postRotate(90);matrix.postTranslate(right, top);fade.setLocalMatrix(matrix);p.setShader(fade);canvas.drawRect(right - length, top, right, bottom, p);}canvas.restoreToCount(saveCount);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);}

官方的注释写得好详细,来翻译一下就行了,绘制主要分为6个步骤:
1. 绘制背景
2. 保存画布图层
3. 绘制视图
4. 绘制视图的子控件
5. 绘制渐变边缘,恢复刚才保存的画布图层
6. 绘制前景或者进度条等装饰

总结

经过XML布局文件的加载和WindowManager对Window的操作,经过测量大小,计算位置,绘制三个步骤最后展现给了用户。

Activity界面的加载和绘制相关推荐

  1. android setting模块,android O版本 设置(Settings)模块总结--设置的一级界面的加载

    O版本的设置界面相对有N有了一些变化,O上面增加了顶级类别的菜单,而之前一些在一级菜单的则移动到了二级界面里面, 如"WIFI","移动网络"等之前是在一级界面 ...

  2. Activity四种加载模式

    在多Activity开发中,有可能是自己应用之间的Activity跳转,或者夹带其他应用的可复用Activity.可能会希望跳转到原来某个Activity实例,而不是产生大量重复的Activity. ...

  3. ECharts动态加载数据绘制折线图

    Echarts动态加载数据绘制折线图 ECharts 引入ECharts 步骤 连接数据接口,动态加载图表 动态加载数据,整体代码 折线图绘制 总结 绘制多个图表的方法 ECharts 纯Javasc ...

  4. HTML5 canvas 实现回合制战棋游戏(1):加载和绘制图形

    HTML5 canvas 实现回合制战棋游戏(1):加载和绘制图形 游戏介绍 完整代码 代码目录 游戏运行 HTML5 canvas 绘制图形 canvas 介绍 绘制函数 加载图片 生物行走动画绘制 ...

  5. Qt解决UI界面一次性加载数据过多卡顿的问题

    关于Qt如何解决UI界面一次性加载太多数据而卡顿的问题 思路分析: ​ 以QTableWidget电子词典为例,每次模糊查询单词时都会产生几十种,几千甚至上万种不同的结果: ​ 例如我们输入a,对a进 ...

  6. 【Unity使用UGUI实现王者荣耀UI界面(三)】登录界面以及加载界面优化

    [Unity使用UGUI实现王者荣耀UI界面(三)]登录界面以及加载界面优化 [只是用来玩玩的,不要太当真] 效果显示: zhans 1. 加载界面进度100%跳转登录界面 这个功能好做,只需要将上次 ...

  7. R语言时间序列(time series)分析实战:时序数据加载、绘制时间序列图

    R语言时间序列(time series)分析实战:时序数据加载.绘制时间序列图 目录

  8. 01_01 python机器学习_第一章学习内容整理_加载样本数据绘制散点图

    第一章学习内容整理_加载样本数据&绘制散点图 01 常用包说明 python可以解决很多问题,相应解决方案使用的包也很多,不太好记忆. 为了便于记忆,用大白话简单描述一下各个包的功能. # 科 ...

  9. 移动APP界面的加载动画与进度条

    调查显示,人们总是希望看到进度条的,尤其是在各种数据化APP中.充分理解当前状态或进度,对用户来说至关重要.另外,在需要耐心等待的时候,进度条是极为强大的工具.活力四射.有趣且漂亮令人愉快的进度条,绝 ...

最新文章

  1. 大数据竞赛平台——Kaggle 入门篇
  2. 第十章 基本数据结构——栈和队列
  3. numpy 按照指定字段排序
  4. 通过模拟器看Windows Phone 7
  5. vmware虚拟机移植带来的问题
  6. 数据类型不匹配_笔记 | 自学Python 06:数据类型之列表
  7. SAP Spartacus的page请求
  8. 阿里高专王夕宁:Istio网关之南北向流量管理
  9. CCNA初学者应该知道的词
  10. GDI+ Graphics类
  11. 关于hadoop2.4.1伪分布式系统的搭建
  12. memcached整理の基本使用
  13. linux 配置 MP3 RMVB 解码器
  14. commandname
  15. QQ游戏大厅的你画我猜游戏白屏问题解决
  16. 判断质数的所有方法详解(C语言)
  17. Bootstrap系列之下拉菜单(Dropdowns)
  18. 期货日内交易如何判断方向
  19. 解说微信抽奖大转盘小程序的开发过程以及一款抽奖大转盘活动软件!
  20. C++ TR1 正则表达式

热门文章

  1. Unity Shader-Command Buffer的使用(景深与描边效果重置版)
  2. sql注入中的联合注入
  3. 百度语音集成离线功能
  4. 云超融合数据中心 CloudFabric
  5. 橙色云构建研发协同生态链,助力企业降本增效
  6. Qt下使用vs编译的库文件
  7. 猴子年华、教你如何关闭微信朋友圈广告
  8. JavaScript中的alert
  9. 投影矩阵(投影变换)解惑
  10. Java计算平均成绩