Activity setContentView() 方法浅析
2017-03-22 14:34
330 查看
Activity setContentView() 方法浅析
Android开发中,众所周知在新创建一个Activity都会覆写Activity的生命周期里面的onCreate(Bundle savedInstanceState),在onCreate方法里调用setContentView(int layoutResID)方法加载xml布局文件并显示出来,接下来从源码去分析xml文件是如何被加载显示的。此次分析的源码是Android5.0的源码,源码查看的工具是Source Insight。Android5.0源码分享地址:https://pan.baidu.com/s/1o79x4ka
Source Insight 下载地址:https://www.sourceinsight.com/download/
Source Insight的安装使用可参考http://www.2cto.com/kf/201601/486897.html这篇博客。
首先我们从Activity里面开始分析,在Activity里面查看setContentView()方法。源码
frameworks\base\core\java\android\app\Activity.java 里面提供的方法如下:
/** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * * @param layoutResID Resource ID to be inflated. * * @see #setContentView(android.view.View) * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams) */ public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); // 初始化ActionBar initWindowDecorActionBar(); }
从上面的代码片段里面可以看到Activity类里面setContentView共调用了两个方法,我们先看
initWindowDecorActionBar()方法,代码片段如下:
/** * Creates a new ActionBar, locates the inflated ActionBarView, * initializes the ActionBar with the view, and sets mActionBar. */ private void initWindowDecorActionBar() { Window window = getWindow(); // Initializing the window decor can change w 4000 indow feature flags. // Make sure that we have the correct set before performing the test below. window.getDecorView(); if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } //初始化ActionBar mActionBar = new WindowDecorActionBar(this); //ActionBar 左上角图标的左边加上一个返回的图标 。对应ActionBar.DISPLAY_HOME_AS_UP mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); mWindow.setDefaultIcon(mActivityInfo.getIconResource()); mWindow.setDefaultLogo(mActivityInfo.getLogoResource()); }
从上面的代码里面我们都看到了一个重要的方法getWindow(),下面我们看一下这个方法:
/** * Retrieve the current {@link android.view.Window} for the activity. * This can be used to directly access parts of the Window API that * are not available through Activity/Screen. * * @return Window The current window, or null if the activity is not * visual. */ public Window getWindow() { return mWindow; }
至此我们看到了一个重要的变量mWindow,我们知道Window是个抽象类,所以我们需要找到这个window的子类,下面我们看看这个变量是在什么地方赋值的,接下来我们在Activity里面搜索attach()方法,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, IVoiceInteractor voiceInteractor) { attachBaseContext(context); mFragments.attachActivity(this, mContainer, null); //初始化window对象 mWindow = PolicyManager.makeNewWindow(this); ...省略其他代码 mUiThread = Thread.currentThread(); //ActivityThread对象赋值 mMainThread = aThread; mInstrumentation = instr; ...省略其他代码 mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
上面这段代码我们可以看到mWindow被初始化了通过PolicyManager.makeNewWindow(this)获得Window对象,我们跟踪PolicyManager.makeNewWindow(this)这个方法,找到
frameworks\base\core\java\com\android\internal\policy\PolicyManager.java,可以看到:
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 { //sPolicy 对象初始化 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); } catch (InstantiationException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } catch (IllegalAccessException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } } // Cannot instantiate this class private PolicyManager() {} // The static methods to spawn new policy-specific objects public static Window makeNewWindow(Context context) { //通过sPolicy.makeNewWindow创建一个window对象。 //sPolicy则在上面的静态代码块中被实例化 return sPolicy.makeNewWindow(context); } public static LayoutInflater makeNewLayoutInflater(Context context) { return sPolicy.makeNewLayoutInflater(context); } public static WindowManagerPolicy makeNewWindowManager() { return sPolicy.makeNewWindowManager(); } public static FallbackEventHandler makeNewFallbackEventHandler(Context context) { return sPolicy.makeNewFallbackEventHandler(context); }
上面的代码中我们可以看到PolicyManager 类里面通过sPolicy.makeNewWindow(context)创建一个window,而IPolicy 这个接口的对象sPolicy则在静态代码块中被实例化,代码如下:
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 { //sPolicy 对象初始化 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); } catch (InstantiationException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } catch (IllegalAccessException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } }
根据POLICY_IMPL_CLASS_NAME创建Policy类的对象,Policy实现了IPolicy接口即获取了IPolicy的对象sPolicy 我们接下来再看Policy 这个类。frameworks\base\core\java\com\android\internal\policy\impl\Policy.java, 源码如下:
public class Policy implements IPolicy { private static final String TAG = "PhonePolicy"; private static final String[] preload_classes = { "com.android.internal.policy.impl.PhoneLayoutInflater", "com.android.internal.policy.impl.PhoneWindow", "com.android.internal.policy.impl.PhoneWindow$1", "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback", "com.android.internal.policy.impl.PhoneWindow$DecorView", "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState", "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState", }; static { // For performance reasons, preload some policy specific classes when // the policy gets loaded. for (String s : preload_classes) { try { Class.forName(s); } catch (ClassNotFoundException ex) { Log.e(TAG, "Could not preload class for phone policy: " + s); } } } public Window makeNewWindow(Context context) { return new PhoneWindow(context); } public LayoutInflater makeNewLayoutInflater(Context context) { return new PhoneLayoutInflater(context); } public WindowManagerPolicy makeNewWindowManager() { return new PhoneWindowManager(); } public FallbackEventHandler makeNewFallbackEventHandler(Context context) { return new PhoneFallbackEventHandler(context); }
代码里面makeNewWindow方法里面返回了PhoneWindow对象,这里我们就清楚的明白了Activity类里面的mWindow对象就是PhoneWindow的实例,getWindow()方法返回的就是PhoneWindow,Activity里面setContentView里面最终是通过getWindow().setContentView()方法去加载xml布局文件,所以接下我们就去看看PhoneWindow类里面的setContentView()是怎么实现的。在
frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindow.java这个类,可看到如下源码:
@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 { //inflate 解析xml布局文件 mLayoutInflater.inflate(layoutResID, mContentParent); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
上面代码中我们可以看到mLayoutInflater.inflate(layoutResID, mContentParent)方法,通过inflate()去解析xml布局文件,接下来我们就要去看看我们常用的LayoutInflater这个类,看看这个类里面inflate()方法是如何解析xml布局文件的。frameworks\base\core\java\android\view\LayoutInflater.java,我们看看这个类
public View inflate(int resource, ViewGroup root) public View inflate(XmlPullParser parser, ViewGroup root) public View inflate(int resource, ViewGroup root, boolean attachToRoot) public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
4个inflate()方法我们看最后一个的具体实现
/** * Inflate a new view hierarchy from the specified XML node. Throws * {@link InflateException} if there is an error. * <p> * <em><strong>Important</strong></em> For performance * reasons, view inflation relies heavily on pre-processing of XML f 101b8 iles * that is done at build time. Therefore, it is not currently possible to * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. * * @param parser XML dom node containing the description of the view * hierarchy. * @param root Optional view to be the parent of the generated hierarchy (if * <em>attachToRoot</em> is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if <em>attachToRoot</em> is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ 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; } }
上面的代码中可看到,通过XmlPullParser的parser获取xml中的TAG名称name,然后判断name,
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); }
如果name等于merge标签则代用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); ....省略其他代码 // 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); } }
看到这里我们大概明白了通过createViewFromTag创建View,然后root.addView()将view添加进去。
接下来我们看看createViewFromTag是如何创建View的,
/** * Creates a view from a tag name using the supplied attribute set. * <p> * If {@code inheritContext} is true and the parent is non-null, the view * will be inflated in parent view's context. If the view specifies a * <theme> attribute, the inflation context will be wrapped with the * specified theme. * <p> * Note: Default visibility so the BridgeInflater can override it. */ View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } ...省略其他代码 try { View view; ...省略其他代码 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = viewContext; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } ...省略其他代码 return view; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } catch (Exception e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } }
在createViewFromTag方法里面可以看到view是通过createView(name, null, attrs)方法创建的
view = createView(name, null, attrs);
看看createView方法的实现
/** * Low-level function for instantiating a view by name. This attempts to * instantiate a view class of the given <var>name</var> found in this * LayoutInflater's ClassLoader. * * <p> * There are two things that can happen in an error case: either the * exception describing the error will be thrown, or a null will be * returned. You must deal with both possibilities -- the former will happen * the first time createView() is called for a class of a particular name, * the latter every time there-after for that class name. * * @param name The full name of the class to be instantiated. * @param attrs The XML attributes supplied for this instance. * * @return View The newly instantiated view, or null. */ public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<? extends View> constructor = sConstructorMap.get(name); Class<? extends View> clazz = null; try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, name); if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) { boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); sConstructorMap.put(name, constructor); } else { // If we have a filter, apply it to cached constructor if (mFilter != null) { // Have we seen this name before? Boolean allowedState = mFilterMap.get(name); if (allowedState == null) { // New class -- remember whether it is allowed clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); if (!allowed) { failNotAllowed(name, prefix, attrs); } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } } Object[] args = mConstructorArgs; args[1] = attrs; constructor.setAccessible(true); final View view = constructor.newInstance(args); if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); } return view; } catch (NoSuchMethodException e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + (prefix != null ? (prefix + name) : name)); ie.initCause(e); throw ie; } catch (ClassCastException e) { // If loaded class is not a View subclass InflateException ie = new InflateException(attrs.getPositionDescription() + ": Class is not a View " + (prefix != null ? (prefix + name) : name)); ie.initCause(e); throw ie; } catch (ClassNotFoundException e) { // If loadClass fails, we should propagate the exception. throw e; } catch (Exception e) { InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName())); ie.initCause(e); throw ie; } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
createView方法最终创建对应的View控件。到来这里整个View的解析加载就完成了。
总结一下,Activity里面xml文件的加载是通过setContentView方法,Activity的setContentView方法调用了PhoneWindow的setContentView方法,PhoneWindow里面调用LayoutInflate类里面的inflate()方法通过XmlPullParser解析xml布局文件,获取布局文件里面的TAG标签,根据TAG标签新建相应的View,再将view添加到DecorView的ViewGroup里面显示。写的不好请多担待。
相关文章推荐
- setContentView(R.layout.activity_main) Error解决方法
- Android中一个Activity第二次启动时,onCreate()调用setContentView()方法时出错,程序崩溃
- setContentView(R.layout.activity_main) Error解决方法
- android开发 Activity的里面调用两次 setContentView方法
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法
- 切换Activity中布局的setContentView( )方法
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法 .
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法
- AppCompatActivity和Activity的setContentView方法的区别
- Activity中的setContentView方法到底做了什么?
- Activity的setContentView()方法源码分析
- Android如何给无法更改继承关系的Activity更换ActionBar(setContentView方法实战)
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法
- setContentView(R.layout.activity_main) Error解决方法
- activity 中的 setContentView( )方法