您的位置:首页 > 移动开发 > Android开发

Android View 分析(上)

2015-06-01 21:19 561 查看

Android View 分析(上)

Set ContentView

做Android开发,在Activity中最熟悉的莫过于这么一个方法:

[code]@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    setContentView(R.layout.activity_main);
}


不妨追踪
setContentView
下去看看是神马情况。

Activity.java


[code]public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
...
public Window getWindow() {
    return mWindow;
}


mWindow
这货是什么?是谁创造了它,我们找一下成员变量
mWindow


发现
mWindow
的赋值在
attach(...)
方法中

[code]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, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);

    mFragments.attachActivity(this, mContainer, null);

    mWindow = PolicyManager.makeNewWindow(this);
    ...
}


注意L11行
PolicyManager
又是神马玩意?进去看看

[code]public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
。。。。。。
// The static methods to spawn new policy-specific objects
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
}


还好这货也就几行代码,通过反射生成一个单例,而最终的实现交给了
sPolicy.makeNewWindow(context);
我们接着定位到
IPolicy
的实现类
Policy


[code]public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";
    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }
}


到此我们终于找到了
mWindow
引用指向了哪里?最终执行了
PhoneWindow
的实例。那么我们直接找
PhoneWindow
setContentView
方法即可

[code]@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);
    }
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}


当第一次进入这个方法的时候
mContentParent 为 null
,所以首先会执行
installDecor();


[code]private void installDecor() {  
        if (mDecor == null) {  
            mDecor = generateDecor();//创建mDecor,它是DecorView类型,继承于FrameLayout。  
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
            mDecor.setIsRootNamespace(true);  
        }  
        if (mContentParent == null) {  
            mContentParent = generateLayout(mDecor);  
            //创建标题栏  
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);  
          ......  
        }  
}


这里忽略了大部分页面切换的过度动画,我们只关注主要流程就好。

先来看一下
generateDecor()


这个过程比较复杂,我就不贴代码了,大概说一下流程

根据getWindowStyle()返回的数组来设定一些窗口属性值feature,如是否全屏,是否带标题栏。

根据上面设定的features值,决定加载何种窗口布局文件。

把特定的view添加到decorView里。

contentParent由findViewById返回,实际上就是mDecorView一部分

接着看一下
mLayoutInflater.inflate(layoutResID, mContentParent)
, 这个方法将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。

Inflate

LayoutInflater
inflate
成员方法

[code]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();
    }
}


继续下去:

[code]public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        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 (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, 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) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // 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);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, 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 (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;
    }
}


这个方法比较长,稍微忍受一下,梳理一下上面inflate成员方法到底做啥牛逼的事情了。LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的,我们重点注意下 L41 调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。

当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:

[code]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();
}


可以看到,在第29行同样是createViewFromTag()方法来创建View的实例,然后还会在第32行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。

这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。

这里说的View、DecorView等都是UI单元,这些UI单元工作都在onDraw函数中完成。如果把onDraw想象成画图过程,那我们需要知道画布是什么?查阅资料后,得出答案就是
Surface


如何确定就是
Surface
呢?我们还得看一下Activity组件启动过程

Activity组件启动过程

我们先来看张比较直观的序列图

下面这个序列图已经过时了,为了方便,没有及时更改这个序列图,这里特别说明一下:

LocalWindowManager —> WindowManagerGlobal

ViewRoot —> ViewRootImpl

然后把
WindowManagerImpl
WindowManagerGlobal
倒换一下



上面分析从setContentView开始,也就是上图的第4步开始,然后执行第5步 PhoneWindow.installDecor(),然后我们直接跳过第7–10步,进入第11步:
WindowManagerImpl.addView()


[code]@Override
    public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}


然后进入第12步,调用:
WindowManagerGlobal.addView()


[code]public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ...
    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}


注意L6
root.setView(view, wparams, panelParentView);
直接把我们带入了第13步,而
ViewRootImpl
中正好有个成员变量:

[code]private final Surface mSurface = new Surface();


源代码 L248 到这里基本验证了上面的结论,这一篇博文中我们基本认识了一下四个类:
PhoneWindow,DecorView,ViewRoot,Surface
,对于这四个类下面给出比较直观的UML图:



下面这张图同样也过时了,这里更新如下:

ViewRoot —> ViewRootImpl

之前ViewRoot继承自Handler,本质上可以说是个Handler感觉很奇怪。

所以经过改良以后,ViewRootImpl将Handler作为内部类来实现,这样剥离了View和Handler,思路上更清晰一点。



这里仅仅是开启了认识View的大门,Android View 分析(中) 将带领我们更进一步的认识View。

本文参考互联网博文:/article/1363512.html

/article/1562127.html,特此说明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: