Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中將xml布局文件轉化為View樹的過程分析(下)-- LayoutInflater源碼分析

Android中將xml布局文件轉化為View樹的過程分析(下)-- LayoutInflater源碼分析

編輯:關於Android編程

在Android開發中為了inflate一個布局文件,大體有2種方式,如下所示:       // 1. get a instance of LayoutInflater, then do whatever you want     LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);              // 2. you're in some View class, then just call View's static inflate method      View.inflate(context, R.layout.xxx_xml, someViewGroup/null); 我們來看看這2種方式的具體源碼:   復制代碼     <!-- View.java -->     /**      * Inflate a view from an XML resource.  This convenience method wraps the {@link      * LayoutInflater} class, which provides a full range of options for view inflation.      *      * @param context The Context object for your activity or application.      * @param resource The resource ID to inflate      * @param root A view group that will be the parent.  Used to properly inflate the      * layout_* parameters.      * @see LayoutInflater      */     public static View inflate(Context context, int resource, ViewGroup root) {         LayoutInflater factory = LayoutInflater.from(context);         return factory.inflate(resource, root);     }       <!-- LayoutInflater.java -->     /**      * Obtains the LayoutInflater from the given context.      */     public static LayoutInflater from(Context context) {         LayoutInflater LayoutInflater =                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);         if (LayoutInflater == null) {             throw new AssertionError("LayoutInflater not found.");         }         return LayoutInflater;     } 復制代碼 現在我們看到實質上都是方法1中的做法,View.inflate只是個helper方法而已(少敲幾行代碼)。那麼我們就先來看看   Context.getSystemService的具體實現,這裡我們直接去ContextImpl.java文件中的相關代碼:   復制代碼     /**      * Override this class when the system service constructor needs a      * ContextImpl.  Else, use StaticServiceFetcher below.      */     /*package*/ static class ServiceFetcher {         int mContextCacheIndex = -1;           /**          * Main entrypoint; only override if you don't need caching.          */         public Object getService(ContextImpl ctx) {             ArrayList<Object> cache = ctx.mServiceCache;             Object service;             synchronized (cache) {                 if (cache.size() == 0) {                     // Initialize the cache vector on first access.                     // At this point sNextPerContextServiceCacheIndex                     // is the number of potential services that are                     // cached per-Context.                     for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {                         cache.add(null);                     }                 } else {                     service = cache.get(mContextCacheIndex); // 先從cache中找,                     if (service != null) { // 如果已經存在了直接返回                         return service;                     }                 }                 service = createService(ctx); // 否則創建並加入到cache中,只會調用1次                 cache.set(mContextCacheIndex, service);                 return service;             }         }           /**          * Override this to create a new per-Context instance of the          * service.  getService() will handle locking and caching.          */         public Object createService(ContextImpl ctx) {             throw new RuntimeException("Not implemented");         }     }       /**      * Override this class for services to be cached process-wide.      */     abstract static class StaticServiceFetcher extends ServiceFetcher {         private Object mCachedInstance;           @Override         public final Object getService(ContextImpl unused) {             synchronized (StaticServiceFetcher.this) {                 Object service = mCachedInstance;                 if (service != null) {                     return service;                 }                 return mCachedInstance = createStaticService();             }         }           public abstract Object createStaticService(); // 它不需要ContextImpl參數     }       private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =             new HashMap<String, ServiceFetcher>(); // 全局system service的map       private static int sNextPerContextServiceCacheIndex = 0;     private static void registerService(String serviceName, ServiceFetcher fetcher) {         if (!(fetcher instanceof StaticServiceFetcher)) {             fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;         }         SYSTEM_SERVICE_MAP.put(serviceName, fetcher); // 放到全局的靜態map中     }       // 還有很多registerService的調用,這裡都省略了,我們現在只關心LAYOUT_INFLATER_SERVICE     registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {                 public Object createService(ContextImpl ctx) { // 我們前一篇文章中提到過會new一個                     return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); // PhoneLayoutInflater的對象返回                  }}); 復制代碼   到這裡我們就清楚了Context.getSystemService方法的具體實現了,接下來我們將注意力轉移到LayoutInflater類。關鍵代碼如下:   復制代碼     /**      * Obtains the LayoutInflater from the given context.      */     public static LayoutInflater from(Context context) {         LayoutInflater LayoutInflater =                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);         if (LayoutInflater == null) {             throw new AssertionError("LayoutInflater not found.");         }         return LayoutInflater;     }       /**      * Inflate a new view hierarchy from the specified xml resource. Throws      * {@link InflateException} if there is an error.      *       * @param resource ID for an XML layout resource to load (e.g.,      *        <code>R.layout.main_page</code>)      * @param root Optional view to be the parent of the generated hierarchy.      * @return The root View of the inflated hierarchy. If root was supplied,      *         this is the root View; otherwise it is the root of the inflated      *         XML file.      */     public View inflate(int resource, ViewGroup root) { // 實際上調用3個參數的版本,從這裡我們可以看出客戶端代碼         return inflate(resource, root, root != null); // 沒必要這樣寫(root!= null):inflate(resource, root, true);     }       /**      * Inflate a new view hierarchy from the specified xml node. Throws      * {@link InflateException} if there is an error. *      * <p>      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance      * reasons, view inflation relies heavily on pre-processing of XML files      * 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.      * @return The root View of the inflated hierarchy. If root was supplied,      *         this is the root View; otherwise it is the root of the inflated      *         XML file.      */     public View inflate(XmlPullParser parser, ViewGroup root) { // 不太常用,我們一般使用layout文件的版本,但實質都一樣         return inflate(parser, root, root != null);             // 下面的代碼中inflate一個include tag時調用了此版本     }       /**      * Inflate a new view hierarchy from the specified xml resource. Throws      * {@link InflateException} if there is an error.      *       * @param resource ID for an XML layout resource to load (e.g.,      *        <code>R.layout.main_page</code>)      * @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(int resource, ViewGroup root, boolean attachToRoot) {         if (DEBUG) System.out.println("INFLATING from resource: " + resource);         XmlResourceParser parser = getContext().getResources().getLayout(resource);         try {             return inflate(parser, root, attachToRoot);         } finally {             parser.close();         }     }       /**      * Inflate a new view hierarchy from the specified XML node. Throws      * {@link InflateException} if there is an error.      * <p>      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance      * reasons, view inflation relies heavily on pre-processing of XML files      * 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; // 此方法最後的返回值,初始化為傳入的root               try {                 // Look for the root node.                 int type;                 while ((type = parser.next()) != XmlPullParser.START_TAG &&                         type != XmlPullParser.END_DOCUMENT) {                     // Empty                 }                 // 能走到這裡,說明type是START_TAG 或 END_DOCUMENT                 if (type != XmlPullParser.START_TAG) { // 如果一開始就是END_DOCUMENT,那說明xml文件有問題                     throw new InflateException(parser.getPositionDescription()                             + ": No start tag found!");                 }                 // 能到這裡,那type一定是START_TAG,也就是xml文件裡的root node                 final String name = parser.getName(); // 獲得當前start tag的name                                  if (DEBUG) {                     System.out.println("**************************");                     System.out.println("Creating root view: "                             + name);                     System.out.println("**************************");                 }                   if (TAG_MERGE.equals(name)) { // 處理merge tag的情況                     if (root == null || !attachToRoot) { // root必須非空且attachToRoot為true,否則拋異常結束                         throw new InflateException("<merge /> can be used only with a valid "                                 + "ViewGroup root and attachToRoot=true"); // 因為merge的xml並不代表某個具體的view,只是將它                     }                                                 // 包起來的其他xml的內容加到某個上層ViewGroup中                       rInflate(parser, root, attrs, false); // 遞歸的inflate                 } else {                     // Temp is the root view that was found in the xml                     View temp; // xml文件中的root view                     if (TAG_1995.equals(name)) {                         temp = new BlinkLayout(mContext, attrs);                     } else {                         temp = createViewFromTag(root, name, attrs); // 根據tag節點創建view對象                     }                       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); // 根據root生成合適的LayoutParams實例                         if (!attachToRoot) { // 如果不attach的話就調用view的setLayoutParams方法                             // 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); // 遞歸inflate剩下的所有children                     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非空且指定了要attachToRoot                         root.addView(temp, params); // 將xml文件的root view 加到用戶提供的root裡                     }                       // Decide whether to return the root that was passed in or the                     // top view found in xml.                     if (root == null || !attachToRoot) {                         result = temp; // 否則我們將返回xml裡發現的root view:temp,而不是方法中傳遞進來的root對象                     }                 }               } 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; // 返回參數root或xml文件裡的root view         }     } 復制代碼 接下來我們看看inflate各種不同節點的方法:   復制代碼     /**      * Recursive method used to descend down the xml hierarchy and instantiate      * views, instantiate their children, and then call onFinishInflate().      */     void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,             boolean finishInflate) throws XmlPullParserException, IOException { // 深度優先inflate,所有才能保證你在onFinish                                                                  // Inflate()裡可以通過findViewById找到已經創建完畢的孩子view         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;             }             // 確保是一個START_TAG node             final String name = parser.getName(); // 拿到tagName                          if (TAG_REQUEST_FOCUS.equals(name)) { // 處理REQUEST_FOCUS tag                 parseRequestFocus(parser, parent);             } else if (TAG_INCLUDE.equals(name)) { // 處理include tag                 if (parser.getDepth() == 0) { // include節點不能是根節點,否則就拋異常了。。。                     throw new InflateException("<include /> cannot be the root element");                 }                 parseInclude(parser, parent, attrs);             } else if (TAG_MERGE.equals(name)) { // merge節點必須是xml文件裡的根節點,也就是說到這裡的時候不應該再出現merge節點了                 throw new InflateException("<merge /> must be the root element");             } else if (TAG_1995.equals(name)) {                 final View view = new BlinkLayout(mContext, attrs);                 final ViewGroup viewGroup = (ViewGroup) parent;                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);                 rInflate(parser, view, attrs, true);                 viewGroup.addView(view, params);                             } else { // 一般情況,各種Android view、widget或用戶自定義的view節點                 final View view = createViewFromTag(parent, name, attrs);                 final ViewGroup viewGroup = (ViewGroup) parent;                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);                 rInflate(parser, view, attrs, true);                 viewGroup.addView(view, params);             }         }           if (finishInflate) parent.onFinishInflate(); // parent的所有孩子節點都inflate完畢的時候,調用onFinishInflate回調     }       private void parseRequestFocus(XmlPullParser parser, View parent)             throws XmlPullParserException, IOException {         int type;         parent.requestFocus();         final int currentDepth = parser.getDepth();         while (((type = parser.next()) != XmlPullParser.END_TAG || // 忽略此節點剩下的所有內容,直到下一個新的START_TAG                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {             // Empty         }     }       private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)             throws XmlPullParserException, IOException {           int type;           if (parent instanceof ViewGroup) {             final int layout = attrs.getAttributeResourceValue(null, "layout", 0); // include節點中必須指定layout屬性的值             if (layout == 0) {                 final String value = attrs.getAttributeValue(null, "layout");                 if (value == null) {                     throw new InflateException("You must specifiy a layout in the"                             + " include tag: <include layout=\"@layout/layoutID\" />");                 } else {                     throw new InflateException("You must specifiy a valid layout "                             + "reference. The layout ID " + value + " is not valid.");                 }             } else {                 final XmlResourceParser childParser =                         getContext().getResources().getLayout(layout);                   try {                     final AttributeSet childAttrs = Xml.asAttributeSet(childParser);                       while ((type = childParser.next()) != XmlPullParser.START_TAG &&                             type != XmlPullParser.END_DOCUMENT) {                         // Empty.                     }                       if (type != XmlPullParser.START_TAG) {                         throw new InflateException(childParser.getPositionDescription() +                                 ": No start tag found!");                     }                       final String childName = childParser.getName();                       if (TAG_MERGE.equals(childName)) { // 處理include xml裡包含merge的情況                         // Inflate all children.                         rInflate(childParser, parent, childAttrs, false);                     } else { // 處理一般的include layout文件,創建此xml文件的root view                         final View view = createViewFromTag(parent, childName, childAttrs);                         final ViewGroup group = (ViewGroup) parent;                           // We try to load the layout params set in the <include /> tag. If                         // they don't exist, we will rely on the layout params set in the                         // included XML file.                         // During a layoutparams generation, a runtime exception is thrown                         // if either layout_width or layout_height is missing. We catch                         // this exception and set localParams accordingly: true means we                         // successfully loaded layout params from the <include /> tag,                         // false means we need to rely on the included layout params.                         ViewGroup.LayoutParams params = null;                         try {                             params = group.generateLayoutParams(attrs);                         } catch (RuntimeException e) {                             params = group.generateLayoutParams(childAttrs);                         } finally {                             if (params != null) {                                 view.setLayoutParams(params); // 設置其layoutParams                             }                         }                           // Inflate all children.                         rInflate(childParser, view, childAttrs, true); // 遞歸inflate剩下的節點                           // Attempt to override the included layout's android:id with the                         // one set on the <include /> tag itself.                         TypedArray a = mContext.obtainStyledAttributes(attrs,                             com.android.internal.R.styleable.View, 0, 0);                         int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);                         // While we're at it, let's try to override android:visibility.                         int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);                         a.recycle();                           if (id != View.NO_ID) {                             view.setId(id); // override id,如果include節點提供了                         }                           switch (visibility) { // 同樣的,override visibility,如果include節點提供了                             case 0:                                 view.setVisibility(View.VISIBLE);                                 break;                             case 1:                                 view.setVisibility(View.INVISIBLE);                                 break;                             case 2:                                 view.setVisibility(View.GONE);                                 break;                         }                           group.addView(view); // 將include的xml文件裡的root view加到上層group中                      }                 } finally {                     childParser.close();                 }             }         } else { // include節點必須是某個ViewGroup的子節點             throw new InflateException("<include /> can only be used inside of a ViewGroup");         }           final int currentDepth = parser.getDepth();         while (((type = parser.next()) != XmlPullParser.END_TAG || // skip掉include節點剩下的內容                 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {             // Empty         }     } 復制代碼   最後我們看看根據節點創建對應View的相關方法:   復制代碼 /*      * default visibility so the BridgeInflater can override it.      */     View createViewFromTag(View parent, String name, AttributeSet attrs) {         if (name.equals("view")) {             name = attrs.getAttributeValue(null, "class");         }           if (DEBUG) System.out.println("******** Creating view: " + name);           try {             View view;             // 這裡我們忽略掉了各種factory的onCreateView,有興趣的讀者可自行研究             if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);             else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);             else view = null;               if (view == null && mPrivateFactory != null) {                 view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);             }                          if (view == null) {                 if (-1 == name.indexOf('.')) { // 創建android.view.*裡的任何view,如TextView,ImageView等等                     view = onCreateView(parent, name, attrs); // 其子類PhoneLayoutInflater override了此方法用來                 } else {                                      // 創建android.widget.*/android.webkit.*裡的任何對象                     view = createView(name, null, attrs); // 創建用戶自定義的各種View(如com.xiaoweiz.browser.MyCustomView)                 }             }               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;         }     }       /**      * 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) // 用戶自定義的view不需要prefix,因為             throws ClassNotFoundException, InflateException { // name中已經有所有需要的信息了;系統的prefix則是android.view.         Constructor<? extends View> constructor = sConstructorMap.get(name); // 或android.widget. 或 android.webkit.         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( // 加載class文件                         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); // 拿到此類型的ctor                 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; // 需要2個參數Context,AttributeSet的版本,所以如果你不打算動態inflate             args[1] = attrs;                  // 你的view,則沒必要提供此版本的ctor。               final View view = constructor.newInstance(args); // new一個View(可能是其子類)的對象,可能為null             if (view instanceof ViewStub) { // ViewStub的特殊處理                 // always use ourselves when inflating ViewStub later                 final ViewStub viewStub = (ViewStub) view;                 viewStub.setLayoutInflater(this);             }             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);         }     }
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved