android之LayoutInflater以及setFactory源碼解讀

1、概述

     LayoutInflater setFactory是什麼,幹什麼用的,這裏不多說,這裏推薦鴻洋大神的一篇以及上一篇換膚技術博客,相信解讀完之後你就知道它有什麼魔力,以及能熟練運用Factory。

      用法:http://blog.csdn.net/lmj623565791/article/details/51503977

      換膚技術:http://blog.csdn.net/zhongwn/article/details/52891902

      LayoutInflater setFactory確實是個好東西,v7包新組建的新特性向下兼容都是通過這玩意來做到的,也很佩服谷歌這幫工程師的設計,因爲這個Factory就像是加載view的後門,我相信他們也是想到了後續可能用到而設計的框架。好了不多說了,接下來我們就一探究竟,看看它有什麼魅力與魔力。

2、源碼解讀

     既然我們知道Factory是在LayoutInflater中,那麼我們就從LayoutInflater開始解讀。
     作爲android猿,我們都知道LayoutInflater是用來加載xml佈局的,一般來說我們都是通過以下兩種來調用
     (1) 
LayoutInflater.from(this).inflate(R.Layout.xml,null);
     (2)
View.inflate(this, android.R.layout.activity_list_item,null);
而第二種我們跟進去看看
public static View inflate(Context context, int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
發現也是調用第一種方式來加載佈局。那麼我們就來分析分析第一種方式,而且也是順着加載佈局開始一點點來分析。接下來看看from()這個靜態方法
/**
 * 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;
}
這裏只是通過context從系統服務中獲取LayoutInflater一個實例,所以我們重點放在inflate()方法中,進入LayoutInfater中你會發現裏邊有4個inflate方法,其中有2個分別調用2個,而最後都統一調用一個方法來解析,這裏貼着兩個方法:
<1>
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();
    }
}
   這個方法很短,一看就知道它只是簡單的獲取Resource資源,然後通過我們傳進來的R.layout.id獲取一個xml解析器,最後還是調用到第二個方法中;其中有兩個方法值得關注與思考如下:
getContext().getResources();
res.getLayout(resource);
它是怎麼獲取Resources實例以及getLayout是怎麼創建這個parser解析器實例的,這裏我們暫時不做分析,留給讀者自行分析。這裏我們只要知道它獲取的是R.layout.xml佈局的解析器,接下來我們有理由相信所有的開始源於第二個方法咯。瞅瞅看便知咯

<2>
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;
    }
}
這個方法有點長,我們挑重點看:
final AttributeSet attrs = Xml.asAttributeSet(parser);
這裏通過解析器獲取整個xml屬性集合attrs即將xml存儲在attrs,它相當於xml佈局的文檔樹。
簡單說明西夏attrs的由來,由於parser並沒有實現AttributeSet接口,所以attrs最終是由XmlPullAttribute組合parser實現的。繼續往下走。
// 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!");
}

這裏是一個空循環,主要目的是獲取xml佈局最外層的View,也就是xml的root。root也是我們要解析佈局的開始標籤START_TAG,若是這個佈局不是這個跟標籤開始的,則認爲佈局有問題,直接拋異常。按正常流程走,
final String name = parser.getName();
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);
首先獲取節點名(第一次時是根節點,其它都是子節點),假設不是是以merge包含一個佈局的,那麼就會走else流程,接下來會執行=一個比較重要的方法:createViewFromTag():
View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
   //省略多餘代碼…………
   
   if (name.equals(TAG_1995)) {
      // Let's party like it's 1995!
      return new BlinkLayout(viewContext, attrs);
   }

   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) {
         //省略多餘代碼…………
            if (-1 == name.indexOf('.')) {
               view = onCreateView(parent, name, attrs);
            } else {
               view = createView(name, null, attrs);
            }
         //省略多餘代碼…………
      }
      return view;
第一個if判斷當前節點(第一次時是根節點,其它都是子節點)是否是TAG_1995,
TAG_1995 = "blink"
這個當然不是,我們的組件中從沒見過blink這個節點也沒用過,只知道是繼承自FrameLayout喜歡探究的自行研究,所以我們已正常的思路走下去。往下看,亮點來了……
吶吶吶~~一系列的Factory,每個Factory的功能基本都是一樣的,看看他們的定義
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;
public interface Factory {
   public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
   public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
功能基本一樣,都是同一個Factory。
當我們沒賦值給任何一個Factory時,所有都是空的,所以我們view是空的
 //省略多餘代碼…………
            if (-1 == name.indexOf('.')) {
               view = onCreateView(parent, name, attrs);
            } else {
               view = createView(name, null, attrs);
            }
         //省略多餘代碼…………

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

這裏首先會判斷當前是否是系統組件,如果是則執行oncreateView,如果不是則執行createView();這兩個都是通過反射獲取獲取view的實例返回;唯一不同的是一個是自定義view一個系統組件,最後onCreateView也是通過調用createView方法來實現,只是將組件包名傳過去。。最終都會獲取一個view返回。
當Factory爲null的時候,這裏的節點view(這裏是第一個根節點,後續會是一個遞歸獲取子節點)獲取就結束了
當Factory不爲null的時候,這會執行Factory的回調onCreateView(由開發者自行實現,如換膚功能就是通過這個實現的),若其中Factory返回的View不爲null,則這個view就是返回給window粘貼顯示在界面上的視圖。若爲null,則執行上述分析的後續流程。
所以Factory算是一個鉤子,專門攔截xml的view節點,而且是每解析xml的一個節點就會執行一次,所以通過它可以實現換膚以及替換view 如將xml的textview替換成imageview,達到偷天換日的效果。
看看賦值的方法分別對應3個,其中有一個已經hide表示只用於系統,不對開發者開放,沒關係,因爲剩下的兩個優先級高於hide方法中privateFactory,從代碼可以看出來:三個方法如下:
public void setFactory(Factory factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}

/**
 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
 * interface.
 */
public void setFactory2(Factory2 factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

/**
 * @hide for use by framework
 */
public void setPrivateFactory(Factory2 factory) {
    if (mPrivateFactory == null) {
        mPrivateFactory = factory;
    } else {
        mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
    }
}

你會發現setPrivateFactory這得隱藏了,再看看其它兩個基本都是一樣的,這裏可以解讀到的信息如下:
 1、一個InflateLayout只能調用一次setFactory()或setFactory2()方法,否則會直接拋異常
 
 2、若是通過InflateLayout的構造方法獲取實例並將Factory的一個實例傳進來,那麼當調用setFactory時,將會用FactoryMerge將這兩個Factory實例保留,詳見類的實現。比較簡單看看代碼應該很容易懂
private static class FactoryMerger implements Factory2 {
    private final Factory mF1, mF2;
    private final Factory2 mF12, mF22;
    
    FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
        mF1 = f1;
        mF2 = f2;
        mF12 = f12;
        mF22 = f22;
    }
    
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View v = mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF2.onCreateView(name, context, attrs);
    }

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
                : mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
                : mF2.onCreateView(name, context, attrs);
    }
}



接下來看看是怎麼遞歸循環獲取它的所有子節點的:思路回到最初的地方,即使inflate()方法中:



            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還是else都有會調用如下方法:
     // Inflate all children under temp
      rInflate(parser, temp, attrs, true, true);
其中玄妙,箇中原理以及查找所有子節點,都在這個方法裏。迫不及待的進去一瞧:
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();
}
分兩步走:
 一:這裏假設這個佈局沒有使用merge和include
在這裏也開始通過while循環開始讀取xml的所有節點,留心的你,這次while循環體裏邊會執行所有view的操作,那麼就會執行到最後一個else,你會發現
相繼調用createViewFromTag()創建view,這個過程就是上述Factory,爲什麼每執行一個節點就會被回調一次的原因。然後繼續執行rInflate()方法,由於解析器還是原來的那一個,所以可以遞歸查找下一個子節點。
二:如果包含了merge或include來引用佈局,則會執行如下兩個方法中的一個
 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);
這兩個方法:最終也還是會調用rflate()方法進行遞歸查找,這裏就不一一分析了。
到此InflaterLayout也就解讀結束了,也應該清楚Factory的面貌,至於怎麼用就由開發者自行定義咯。

若有不對的地方,請不吝指正!!!



            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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章