View加载详解(一)

来源:互联网 发布:植物学专业知乎 编辑:程序博客网 时间:2024/06/02 00:00

现在我们接着上一篇文章继续往下讲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画布上。
由于篇幅的原因我会放在下一篇再讲谢谢大家

0 0