從 setContentView 入口,全方位分析 LayoutInflater

LayoutInflater 介紹

在 Android 中 LayoutInflater 是扮演着很重要的角色,很多時候我們忽略了它的重要性,因爲它的重要性完 全被隱藏起來了,可以說是直接隱藏在了Activity , Fragment 等組件的光環之下了。

from(mContext) 源碼解析


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

這裏我就拿 Activity setContentView() 舉例
@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);

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

@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 函數:

/**
 * 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 是怎麼創建出來的勒,先來看一段代碼:

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 函數,我們看下該函數的實現:

  /**
     *
     *啓動 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 下
    返回解析到的根視圖;

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

  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 之後幹了些什麼事兒,已經有一定了解了。

感謝你的閱讀,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章