前言
最近加了移動(dòng)開發(fā)前線的安卓學(xué)習(xí)群掂摔,看到他們?cè)谟懻搊nMeasure相關(guān)的內(nèi)容,于是寫個(gè)文章來學(xué)習(xí)和記錄一下onMeasure相關(guān)的內(nèi)容级历。
Activity的裝載過程
從Activity的setContentView方法切入
setContentView
Window mWindow;
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
attach
這里的getWindow()返回的是mWindow對(duì)象叭披。
關(guān)于這個(gè)Window對(duì)象,Activity在創(chuàng)建時(shí)嚼贡,會(huì)調(diào)用一個(gè)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) {
……
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
……
}
Window類主要用來從WindowManagerService接收觸摸粤策、鍵盤等事件误窖,然后分發(fā)給Activity,并且Window類還承載了UI的顯示柔吼。Actiivty的Window實(shí)際上是個(gè)PhoneWindow對(duì)象吭服。
接著分析PhoneWindow.setContentView(id)。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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);
}
……
}
installDecor
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
……
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
……
}
}
mDecor是一個(gè)DecorView對(duì)象蝌戒,DecorView繼承自FrameLayout沼琉。
generateLayout
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
int layoutResource;
int features = getLocalFeatures();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
generateDecor函數(shù)就簡(jiǎn)單地調(diào)用了DecorView的構(gòu)造函數(shù),generateLayout函數(shù)主要工作是根據(jù)Window的Features來決定要加載什么樣的布局文件友鼻,這里以R.layout.screen_simple這個(gè)布局文件為例子傻昙,這個(gè)布局文件位置在framework\core\res\res\layout目錄中妆档,具體布局文件如下虫碉。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
在generateLayout函數(shù)中,加載了布局文件之后须板,添加到了mDecor中兢卵,并且LayoutParams皆為MATCH_PARENT,mContentParent為id為android.R.id.content的FrameLayout。
繼續(xù)分析setContentView秽荤,在這個(gè)函數(shù)的后邊的地方調(diào)用了mLayoutInflater.inflate,把我們指定的布局文件加載到了mContentParent中。
至此贺嫂,我們可以大致的發(fā)現(xiàn),在一個(gè)簡(jiǎn)單的Activity中,真正的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<DecorView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is where our layout is placed -->
</FrameLayout>
</LinearLayout>
</DecorView>
Activity啟動(dòng)過程
handleLaunchActivity->performLaunchActivity->和ActivityServiceManager交互->handleResumeActivity->performResumeActivity踱稍。
handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
……
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
……
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;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
} else if (!willBeVisible) {
……
}
……
}
}
在handleResumeActivity方法中悠抹,把PhoneWindow中生成的DecorView添加到了WindowManager中。
WindowManagerImpl.addView
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
mGlobal是一個(gè)WindowManagerGlobal對(duì)象啤挎。
WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
……
synchronized (mLock) {
// Start watching for system property changes.
……
root = new ViewRootImpl(view.getContext(), display);
……
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
……
root.setView(view, wparams, panelParentView);
}
在這個(gè)方法中新建了一個(gè)ViewRootImpl對(duì)象卵凑,并把DecorView對(duì)象傳入ViewRootImpl中,由ViewRootImpl來掌管整個(gè)視圖的measure勺卢,layout。
ViewRootImpl.getRootMeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
這個(gè)函數(shù)用來給DecorView賦MeasureSpec值宴抚,這個(gè)函數(shù)在measureHierarchy中被調(diào)用。
measureHierarchy
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
……
goodMeasure = true;
……
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
這個(gè)函數(shù)有個(gè)參數(shù)是WindowManager.LayoutParams菇曲,通過查看這個(gè)類可知,lp.width和lp.height都是match_parent弟胀,再結(jié)合getRootMeasureSpec函數(shù)蕊玷,可知,DecorView傳給子View的MeasureSpec是xxxdp+EXACTLY垃帅。
ViewGroup getChildMeasure方法
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* @param childDimension How big the child wants to be in the current
* dimension
* @return a MeasureSpec integer for the child
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
FrameLayout等ViewGroup子類在measure子View時(shí)方庭,會(huì)調(diào)用getChildMeasure方法,來決定子View的MeasureSpec械念。
總結(jié)
Parent的MeasureSpec | 子View的LayoutParams | 子View的MeasureSpec |
---|---|---|
</br>EXACTLY </br> |
xxxdp </br>match_parent </br>wrap_content |
xxxdp + EXACTLY </br>parent大小 + EXACTLY </br> parent大小 + AT_MOST |
</br>AT_MOST </br> |
xxxdp </br>match_parent </br>wrap_content |
xxxdp + EXACTLY </br>parent大小 + AT_MOST </br> parent大小 + AT_MOST |
</br>UNSPECIFIED </br> |
xxxdp </br>match_parent </br>wrap_content |
xxxdp + EXACTLY </br>0 + UNSPECIFIED </br> 0 + UNSPECIFIED |
DecorView的onMeasure函數(shù)
DecorView繼承自FrameLayout龄减,所以這里列出FrameLayout代碼
/**
* {@inheritDoc}
*/
@Override
protected 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);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
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) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
lp.leftMargin - lp.rightMargin,
MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
lp.topMargin - lp.bottomMargin,
MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
接下來驗(yàn)證我們的猜想希停,首先是DecorView署隘,這個(gè)Layout的MeasureSpec是xxxdp + EXACTLY,接著是LinearLayout,這個(gè)Layout獲得的MeasureSpec是 parent大小+EXACTLY磁餐。接著是FrameLayout,它的MeasureSpec也是parent大小+EXACTLY羞延,再接著就是我們的布局文件了。
TestView
public class TestView extends View{
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
Log.d("xxj","size "+size);
switch (mode){
case MeasureSpec.EXACTLY:
Log.d("xxj","mode exactly");
break;
case MeasureSpec.AT_MOST:
Log.d("xxj","mode at_most");
break;
default:
Log.d("xxj","mode unspecified");
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.netease.jnisample.TestView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
按照之前的猜想肴楷,F(xiàn)rameLayout獲得的MeasureSpec是parent大小 + EXACTLY荠呐。TestView獲得的MeasureSpec也應(yīng)該是parent大小+EXACTLY砂客。
運(yùn)行結(jié)果
接下來將TestView的width和height改為wrap_content呵恢,運(yùn)行結(jié)果應(yīng)為parent大小 + AT_MOST。
接下來將TestView的width和height改為50dp彤恶,結(jié)果應(yīng)為50dp + EXACTLY