從 setContentView 入口,全方位分析 LayoutInflater

LayoutInflater 介紹

在 Android 中 LayoutInflater 是扮演着很重要的角色,不少時候咱們忽略了它的重要性,由於它的重要性完 全被隱藏起來了,能夠說是直接隱藏在了Activity , Fragment 等組件的光環之下了。java

from(mContext) 源碼解析

在 Android 系統中,咱們常常以 Context 獲取系統級別的服務,好比 AMS, WMS, LayoutInfoater 等,這些服務會在合適的時候註冊在系統中,在咱們須要的時候 getSS(String name) 經過系統的名字來獲取。咱們先來看一段代碼:node

這裏我就拿 Activity setContentView() 舉例android

@Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
複製代碼

繼續跟下去:緩存

/** * Should be called instead of {@link Activity#setContentView(int)}} */
    public abstract void setContentView(@LayoutRes int resId);
複製代碼

跟下去發現是一個抽象類,咱們找它的實現類:app

@Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
      //經過 LayoutInflater 加載 XML id 
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
複製代碼

咱們在 AppCompatDelegateImpl 類找到了實現類,眼神好的是否是發現了上面的 LayoutInflater ,沒錯咱們 Activity 最後也是經過 LayoutInflater 解析 XML 加載佈局的,繼續跟 from 函數:ide

/** * Obtains the LayoutInflater from the given context. */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) 
          //經過 Context 獲取服務
          context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
複製代碼

經過上面代碼能夠知道,LayoutInflater 是經過 Context 的 getSystemService(String name) 來獲取到的。context 的 getSS 函數怎麼獲取到的勒,下面咱們就來介紹下 Context 的源碼。函數

Context

其實在 Application,Activity,Service 中都會存在一個 Context 對象,咱們叫其上下文,能夠經過這個上下文,啓動 Activity,Service, 註冊一個廣播,獲取系統服務等等操做,那麼 Context 是怎麼建立出來的勒,先來看一段代碼:oop

public abstract class Context {...}
複製代碼

Context 是一個抽象類,咱們找下它的實現類,咱們知道在啓動 Activity 的時候有一個 Context 上下文,啓動 Activity 的入口在 ActivityThread main 函數,咱們就從這裏開始找佈局

//經過反射調用執行的
    public static void main(String[] args) {
      	...
        //主線程消息循環
        Looper.prepareMainLooper();
        //建立 ActivityThread 對象
        ActivityThread thread = new ActivityThread();
        //Application,Activity 入口
        thread.attach(false);
        Looper.loop();
      
				...
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼
private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        //不是系統級別的應用
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                //經過 IActivityManager。aidl 文件 底層經過 Binder 通訊,關聯 Application
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
           ...
        } else {
          代碼省略
           ....
    }
複製代碼

在 main 方法中,咱們建立了 ActivityThread 對象後,調用了其 attach 函數,而且參數爲 false。在 attach 函數中,參數爲 false 的狀況下是屬於非系統應用,會經過 Binder 機制與 AMS 通訊,而且最終調用 H 類的 LAUNCH_ACTIVITY - > handleLaunchActivity 函數,咱們看下該函數的實現:post

/** * *啓動 Activity * @param r * @param customIntent * @param reason */
    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
      //實施 啓動 Activity 的實例
        Activity a = performLaunchActivity(r, customIntent);

    }
複製代碼

繼續跟:

/***啓動 Activity 代碼*/
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 ....
        //1. 建立 Context 對象
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;

.....
        try {
            // 2. 製做 Application 對象
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                //3. 獲取 Context 對象
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                appContext.setOuterContext(activity);
                //4. 將 appContext 等對象依附在 Activity 的 attach 函數中
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
                //是不是持久化
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    //5. 調用 Activity 的 onCreate 方法
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
           ....
        return activity;
    }
複製代碼
/***Context 的實現類 ContextImp*/
    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        //1.建立 Activity 的 Context 對象, 到這裏點擊 ContextImpl 是 Context 實現類
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

      ....
        return appContext;
    }
複製代碼
/** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */
class ContextImpl extends Context {
  ...
}
複製代碼

經過上面代碼 1- 5 的註釋分析可知,Context 的實現類是 ContextImpl, 這裏咱們至關於又帶着你們複習了一遍 Application , Activity 啓動源碼了。

getSystemService

經過上面咱們得知 ContextImpl 是 Context 的實現類,咱們繼續看源碼

public class ContextImpl extends Context{
  ...
   
  /** * 經過服務名稱代號 Context.XXX 拿到系統各類服務 */
        @Override
	public Object getSystemService(String name) {
   return SystemServiceRegistry.getSystemService(this, name);
	}
  ...
}
複製代碼

這裏咱們發現返回的是 SystemServiceRegistry 類裏面的 getSystemService 函數,繼續跟:

/** * Gets a system service from a given context. */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
複製代碼
/** * 交給子類來實現獲取服務 * */
    static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

複製代碼
/** * 裝服務的容器 */
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
複製代碼

到了這裏咱們知道了經過容器緩存拿到了 LayInflater 服務,那麼何時註冊的?下面咱們繼續看該類源碼

registerService

final class SystemServiceRegistry {
  		/***裝系統各類服務的容器,這裏至關於容器單例類*/
      private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
  
    // Not instantiable.
    private SystemServiceRegistry() { }
  
      static {
		 .....
       //註冊 LayoutInflater 服務
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
     .....
    }
}
複製代碼
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
                if (service == null) {
                    try {
                        service = createService(ctx);
                        cache[mCacheIndex] = service;
                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);
                    }
                }
                return (T)service;
            }
        }

      //交給抽象實現去建立服務
        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }
複製代碼

經過上面的代碼能夠知道抽象實現返回的是 new PhoneLayoutInflater(ctx.getOuterContext()); 那麼這個 Phone... 到底什麼了? 咱們繼續跟

public class PhoneLayoutInflater extends LayoutInflater {
  
}
複製代碼

真相大白啊,PhoneLayoutInflater 就是繼承的 LayoutInflater。

總結

經過上面的代碼可知,在虛擬機第一次加載該類時,經過 靜態代碼塊 會註冊各類 ServiceFatcher, 這其中就包含了 LayoutInflater Service, 將這些服務以鍵值對的形式存儲在 Map 中, 用戶使用時只須要根據 key 來獲取對應的 ServiceFetcher, 而後經過 ServiceFetcher 對象的 getService 來獲取具體服務對象。當第一次獲取時,會調用 ServiceFetcher 的 createService 函數建立服務,而後緩存到一個列表中,下次再取直接從緩存中獲取,從而避免了重複建立對象,從而達到了單例的效果,這不就是我以前介紹的單例模式-容器單例模式嘛,經過容器的單例模式實現方式,系統核心服務以單例形式存在,減小了資源消耗。

inflate 源碼解析

@Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
複製代碼

跟 inflate

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        // root 不爲 null , 則會從resourec 佈局解析到 View ,並添加到 root 中
        return inflate(resource, root, root != null);
    }
複製代碼
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        //獲取 XMl 解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
複製代碼
/** * * @param parser xml 解析器 * @param root 解析佈局的父視圖 * @param attachToRoot 是否將要解析的視圖添加到父視圖中 * @return */
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            //Context 對象
            mConstructorArgs[0] = inflaterContext;
            //存儲父視圖
            View result = root;

            try {
                // Look for the root node.
                int type;
                //找到 root 元素
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                final String name = parser.getName();
                //1. 解析 Merge 佈局標籤
                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, inflaterContext, attrs, false);
                } else {

                    // 根據 Tag 來解析layout 跟視圖
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // 生成佈局參數
                        params = root.generateLayoutParams(attrs);
                        //若是 attachRoot 爲 false,那麼將給 temp 設置佈局參數
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    // 解析 temp 下全部子 View
                    rInflateChildren(parser, temp, attrs, true);
                    // 若是 Root 不爲空,且 attachToroot 爲 true ,那麼將 temp 添加到父佈局中
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //若是 root == null 且 attachToRoot 爲 false 那麼直接返回 temp
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
               ...
            return result;
        }
    }
複製代碼

上述 inflate 方法中,主要有下面幾步:

  1. 解析 xml 中的根標籤
  2. 若是根標籤是 merge ,那麼調用 rInflate 進行解析,rInflate 會將全部的子 View 添加到跟標籤中
  3. 若是標籤是普通元素,那麼調用 createViewFromTag 對元素進行解析;
  4. 調動 rInflateChildren 解析 temp 根元素下的全部子 View, 而且將這些子 View 都添加到 temp 下
  5. 返回解析到的根視圖;

咱們在看一段代碼,先從簡單的理解:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
        try {
            //1. 用戶能夠經過設置 LayoutInflater 的 factory 來自行解析 View,默認這些 Factory 都爲空,能夠忽略這段
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
           
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
            
            //2. 沒有 Factory 的狀況下經過 onCreateView 或者 createView 建立 View 
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 3. 內置 View 控件的解析
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //4 自定義 View 的解析
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
  		...
    }
複製代碼

本段代碼重點就在註釋 2 處,當這個 tag 的名字包含 「.」 時,認爲這是一個內置 View, 也就是

<TextView
        android:id="@+id/list_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />
複製代碼

這裏的 TextView 就是 XMl 標籤的名字,所以,在執行 infate 時就會調用註釋 3 處的 onCreateView 來解析 TextView 標籤。那麼,當咱們自定義 View 時,就會執行註釋 4

<com.t01.TextView
        android:id="@+id/list_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />
複製代碼

在上面的 PhoneLayoutInflater 重寫了 onCreateView 方法,該方法就是在 View 標籤名的前面設置了一個 「android.widget」 前綴,而後傳遞給 createView 解析。

那麼咱們來看下 createView 源碼具體實現吧

//根據完整的路徑的類名經過反射機制構造 View 對象
    public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
        //1. 經過緩存獲取構造函數
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            //2. 沒有緩存構造函數
            if (constructor == null) {
                // 若是 prefix 不爲空,那麼構造函數的 View 路徑,而且加載該類
                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);
                    }
                }
                //3. 從 Class 對象中獲取構造函數
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //4. 將構造函數存入緩存中
                sConstructorMap.put(name, constructor);
            } else {
               ...
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            //5. 經過反射構造 View
            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]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

  
    }
複製代碼

createView 至關來講還比較理解,若是有前綴,那麼就構造 View 的完整路徑,而且將該類加載到虛擬機中,而後獲取該類的構造函數而且緩存下來,在經過構造函數來建立該 View 的對象,最後將對象返回,這就是解析單個 View 的過程。而咱們的窗口中時一個視圖樹, LayoutInflater 須要解析完這棵樹,這個功能就交給 rInflateChildren 方法,看下面代碼

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        //1. 獲取樹的深度,優先遍歷
        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;
        //2. 挨個元素解析
        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)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) { // 解析 include 標籤
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { //解析到 merge 標籤,拋出異常,由於 merge 標籤必須是根視圖
                throw new InflateException("<merge /> must be the root element");
            } else {
                //3. 根據元素名進行解析,又回去了
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //遞歸調用進行解析
                rInflateChildren(parser, view, attrs, true);
                //將解析到的 View 添加進 ViewGroup 中,也就是它的 parent
                viewGroup.addView(view, params);
            }
        }
...
    }
複製代碼

rInflateChildren 經過深度優先遍從來構造視圖樹,每解析到一個 View 元素就會遞歸調用 rInflateChildren ,直到這條路徑的最後一個元素,而後在回溯過來將每個 View 元素添加進 parent 中,經過 rInflateChildren 解析以後,整棵樹就構建完畢了。當回調了 onResume 以後,setContentView 設置的內容就會出如今屏幕中了。

總結

LayoutInflater 涉及的知識源碼仍是挺多的,有 Application , Activity 的啓動,還有深度廣度遍歷,XML 節點解析,容器單例模式。這裏也至關於帶着你們溫習了一遍 Activity 啓動流程吧。

好了,到了這裏相信你們對 setContentView 以後幹了些什麼事兒,已經有必定了解了。

感謝你的閱讀,謝謝!

相關文章
相關標籤/搜索