从源码的角度说说Activity的setContentView的原理
生活随笔
收集整理的這篇文章主要介紹了
从源码的角度说说Activity的setContentView的原理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們在Activity開發的時候天天會用到這個方法,有時候還需要根據需求在setContentView調用的時候做一些動作,因此我們就需要知道它內部是如何工作的,我們來一起看一下:
setContentView有三個重載方法:
public void setContentView(int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}public void setContentView(View view) {getWindow().setContentView(view);initWindowDecorActionBar();}public void setContentView(View view, ViewGroup.LayoutParams params) {getWindow().setContentView(view, params);initWindowDecorActionBar();}他們實際都在調用getWindow方法返回的Window對象的setContentView方法,那getWindow對象返回的是誰呢? public Window getWindow() {return mWindow;}
我們可以看到getWindow返回的是一個mWindows的對象,那么它在哪被賦值的呢?通過查找我們可以找到在Activity的attach方法中有這么一段: mWindow = PolicyManager.makeNewWindow(this);
也就是說繼續往下看,發現PolicyManager.makeNewWindow內部確實這樣的: // The static methods to spawn new policy-specific objectspublic static Window makeNewWindow(Context context) {return sPolicy.makeNewWindow(context);}
好,那我們找找sPolicy又是誰:
我們可以在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-timetry {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);}}也就是說,sPolicy對象是類com.android.internal.policy.impl.Policy的一個實例,OK,我們進入這個類看一下它的makeNewWindow方法,噢噢,是這樣啊: public Window makeNewWindow(Context context) {return new PhoneWindow(context);}
原來那個getWindow返回的是PhoneWindow對象,setContentView調用的則是PhoneWindow的setContentView,OK,說了這么多重點就是看下PhoneWindow的setContentView方法是如何工作的:
PhoneWindow的setContentView的重載方法內部有些不同,我們先看參數為int的setContentView:
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();}}我們只看重點部分,都是通過LayoutInflater的inflate的方法加載的,是不是很熟悉呢?inflate方法的第二個參數是mContentParent,就是說要把這個layoutResID的這個布局添加到mContentParent中,那么mContentParent就是我們的主布局了,從代碼中也可以看出來: /*** The ID that the main layout in the XML layout file should have.*/public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; protected ViewGroup generateLayout(DecorView decor) {// Apply data from current theme....ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);if (contentParent == null) {throw new RuntimeException("Window couldn't find content container view");}...return contentParent;} mContentParent = generateLayout(mDecor);
好,入口的東西都分析的差不多了,著重看一下LayoutInflater的inflate的工作原理,我們常用的方法是這3個: public View inflate(int resource, ViewGroup root) {return inflate(resource, root, root != null);}public View inflate(XmlPullParser parser, ViewGroup root) {return inflate(parser, 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();}}
其實它們都沒做什么,主要做工作的是它: 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 xmlfinal 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 suppliedparams = 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 temprInflate(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;}}
這段代碼比較多,我們挑一下重點看: // Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, attrs, false);ViewGroup.LayoutParams params = null;if (root != null) {// Create layout params that match root, if suppliedparams = 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);}}// 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;}
這段代碼就是說通過createViewFromTag方法生成臨時View對象,然后如果View生成成功,給它產生一個默認的LayoutParams對象附在上面,最后判斷root是否為空,決定是否要將這個臨時View添加到root之上,好,接下來,我們的重點是createViewFromTag方法: /*** 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");}Context viewContext;if (parent != null && inheritContext) {viewContext = parent.getContext();} else {viewContext = mContext;}// Apply a theme wrapper, if requested.final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);final int themeResId = ta.getResourceId(0, 0);if (themeResId != 0) {viewContext = new ContextThemeWrapper(viewContext, themeResId);}ta.recycle();if (name.equals(TAG_1995)) {// Let's party like it's 1995!return new BlinkLayout(viewContext, attrs);}if (DEBUG) System.out.println("******** Creating view: " + name);try {View view;if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, viewContext, attrs);} else if (mFactory != null) {view = mFactory.onCreateView(name, viewContext, attrs);} else {view = null;}if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);}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;}}if (DEBUG) System.out.println("Created view is: " + view);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;}}
這么多代碼,其實我們的重點是這部分: View view;if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, viewContext, attrs);} else if (mFactory != null) {view = mFactory.onCreateView(name, viewContext, attrs);} else {view = null;}if (view == null && mPrivateFactory != null) {view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);}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;}}
我們可以看到,剛開始會交給mFactory2、mFactory、mPrivateFactory這三個View工廠來進行處理,這三個對象都是通過構造方法設置進來的,如下: protected LayoutInflater(LayoutInflater original, Context newContext) {mContext = newContext;mFactory = original.mFactory;mFactory2 = original.mFactory2;mPrivateFactory = original.mPrivateFactory;setFilter(original.mFilter);}這個構造方法在哪里被調用我們先不去看,我們可以繼續我們的重點:
if (-1 == name.indexOf('.')) {view = onCreateView(parent, name, attrs);} else {view = createView(name, null, attrs);}
這里的意思是,如果這個Tag是沒有( . )的話,那就是說這個tag是android提供的默認的控件,像View,TextView,Button等等。否則,就是其它情況了,我們先看默認沒有( . )的情況: protected View onCreateView(String name, AttributeSet attrs)throws ClassNotFoundException {return createView(name, "android.view.", attrs);}
請注意這個方法的第二個參數android.view.,然后我們進入方法內部: 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 itclazz = 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 constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = 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 subclassInflateException 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);}}
這段代碼其實重點就是通過ClassLoader的loadClass方法將類加載進來,然后通過反射的方式獲取它的構造方法進行實例化,然后基本功能就完成了。有的同學可能會注意到這里有個mFilter屬性,這個屬性是用來定義這個類是否允許被加載的。
好,以上就是最基本的Inflate加載過程,其實實際過程不是這樣的,我們將在下一章查看詳細的加載過程。
總結
以上是生活随笔為你收集整理的从源码的角度说说Activity的setContentView的原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TypeError: can't pic
- 下一篇: Android官方开发文档Trainin