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倒换一下
![](http://7xinl9.com1.z0.glb.clouddn.com/linux1s1s1354900406_7918.jpg)
上面分析从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图:
![](http://7xinl9.com1.z0.glb.clouddn.com/linux1s1s20150428210857.png)
下面这张图同样也过时了,这里更新如下:
ViewRoot —> ViewRootImpl
之前ViewRoot继承自Handler,本质上可以说是个Handler感觉很奇怪。
所以经过改良以后,ViewRootImpl将Handler作为内部类来实现,这样剥离了View和Handler,思路上更清晰一点。
![](http://7xinl9.com1.z0.glb.clouddn.com/linux1s1s20150428210911.png)
这里仅仅是开启了认识View的大门,Android View 分析(中) 将带领我们更进一步的认识View。
本文参考互联网博文:/article/1363512.html
/article/1562127.html,特此说明。
相关文章推荐
- Android View 分析(中)
- Android View 分析(下)
- Android AsyncTask详解
- Android Service
- Android Socket
- 如何指定让Android的NDK工具编译出Release版本或Debug版本的Native程序
- Android中的设计模式
- Android通过webservice操作数据库1(查询数据库)
- 关于android移动动画和缩放动画的构造函数简介
- Android Property介绍
- Android_开源框架_Volley(Google IO 2013)源代码及内部实现分析
- android 滚动与设置接口方法
- Android属性动画完全解析(上),初识属性动画的基本用法 .
- Android的四种启动模式-----深入理解
- Android 获取手机的一些基本信息
- Android ThreadPoolExecutor 的参数详解
- Android客户端与java服务器端的Socket连接
- 木瓜妮子多媒体开发教程---第三天---Android下图像灰度化和二值化
- android-自定义圆形图片控件
- Android 关于inflate