现在我们接着上一篇文章继续往下讲layoutInflat.inflater
那么inflate方法里面具体做了什么?跟踪代码,该方法的实现是在LayoutInflater类中。
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
该方法很简单,方法体里面直接调用 如下方法
public View inflate(int resource, 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的pull解析器将xml资源解析成XmlResourceParser对象作为参数传 inflate(parser, root, attachToRoot);方法。该方法实现如下:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
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 (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, attrs, false, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = 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);
}
}
// Inflate all children under temp
rInflate(parser, temp, attrs, true, true);
// 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 (IOException 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 view 。我们来看看 rInflate方法的实现
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs, inheritContext);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, attrs, inheritContext);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) parent.onFinishInflate();
}
以上方法就是遍历xml资源根布局 root view 下的子元素,并且将子元素view依次添加到 root view下面。
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);相信大家也看到了这个generateLayoutParams(attrs),这个就是加载ViewGroup的属性的,当我们自定义ViewGroup的时候我们就要继承它。
LayoutInflater类给开发者暴露了两个方法用于加载布局
public View inflate(int resource, ViewGroup root)
public View inflate(int resource, ViewGroup root, boolean attachToRoot)
当root=null时,attachToRoot不起任何作用。
当root!=null时,attachToRoot=false时,xml资源布局不添加到root根布局下,也就是root失效。
当root!=null,attachToRoot=true时,xml资源布局会添加到root根布局下。
在调用第一种方法,没有attachToRoot参数时,当root=null时的情况和第一种分析一样,当root!=null的情况和第三那种分析一样。
当大家读到这里的时候就知道Activity中的PhoneView对象帮我们创建了一个PhoneView内部类DecorView(父类为FrameLayout)窗口顶层视图,
然后通过LayoutInflater将xml内容布局解析成View树形结构添加到DecorView顶层视图中id为content的FrameLayout父容器上面。到此,我们已经知道Activity的content内容布局最终会添加到DecorView窗口顶层视图上面,相信很多人也会有这样的疑惑:窗口顶层视图DecorView是怎么绘制到我们的手机屏幕上的呢?
下面试着分析DecorView的绘制流程。(因为技术不是太6,出错的地方大家不要见怪)
顶层视图DecorView添加到窗口的过程
DecorView是怎么添加到窗口的呢?这时候我们不得不从Activity是怎么启动的说起,当Activity初始化 Window和将布局添加到PhoneWindow的内部类DecorView类之后,ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,来看看handlerResumeActivity方法的实现:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
if (r.window == null && !a.mFinished && willBeVisible) {
//获得当前Activity的PhoneWindow对象
r.window = r.activity.getWindow();
//获得当前phoneWindow内部类DecorView对象
View decor = r.window.getDecorView();
//设置窗口顶层视图DecorView可见度
decor.setVisibility(View.INVISIBLE);
//得当当前Activity的WindowManagerImpl对象
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) {
//标记根布局DecorView已经添加到窗口
a.mWindowAdded = true;
//将根布局DecorView添加到当前Activity的窗口上面
wm.addView(decor, l);
分析:详细步骤以上代码都有详细注释,这里就不一一解释。handlerResumeActivity()方法主要就是addView方法添加到Activity的顶层视图DecorView添加到窗口视图上。我们来看WindowManagerImpl类的addView()方法。
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
源码很简单,直接调用了 mGlobal对象的addView()方法。继续跟踪,mGlobal对象是WindowManagerGlobal类。进入WindowManagerGlobal类看addView()方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
............
ViewRootImpl root;
View panelParentView = null;
............
//获得ViewRootImpl对象root
root = new ViewRootImpl(view.getContext(), display);
...........
// do this last because it fires off messages to start doing things
try {
//将传进来的参数DecorView设置到root中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...........
}
}
该方法中创建了一个ViewRootImpl对象root,然后调用ViewRootImpl类中的setView成员方法()。继续跟踪代码进入ViewRootImpl类分析
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
.............
//标记已添加DecorView
mAdded = true;
.............
//请求布局
requestLayout();
}
}
该方法实现有点长,我省略了其他代码,直接看以上几行代码:
将外部参数DecorView赋值给mView成员变量
标记DecorView已添加到ViewRootImpl
调用requestLayout方法请求布局
跟踪代码进入到 requestLayout()方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
跟踪代码,最后DecorView的绘制会进入到ViewRootImpl类中的performTraversals()成员方法,这个过程可以参考上面的代码流程图。现在我们主要来分析下 ViewRootImpl类中的performTraversals()方法。
private void performTraversals() {
// cache mView since it is used so much below...
//我们在Step3知道,mView就是DecorView根布局
final View host = mView;
//在Step3 成员变量mAdded赋值为true,因此条件不成立
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
.............
//顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
.....................
//在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
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 {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
............
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
........................
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
.......................
//执行绘制操作
performDraw();
}
该方法主要流程就体现了View绘制渲染的三个主要步骤,分别是测量,布局,绘制三个阶段。
这里先给出Android系统View的绘制流程:依次执行View类里面的如下三个方法:
measure(int ,int) :测量View的大小
layout(int ,int ,int ,int) :设置子View的位置
draw(Canvas) :绘制View内容到Canvas画布上。
由于篇幅的原因我会放在下一篇再讲谢谢大家