Android 之 setContentView 源碼閱讀

Activity 中的 setContentView

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

setContentView 方法如下所示,調用的是 window 中的 setContentView,但是 window 中的只是一個抽象方法:

public abstract void setContentView(View view, ViewGroup.LayoutParams params);

而在 window 類的最開始也說了,window 唯一的實現類是 PhoneWindow。所以這裏調用的是 PhoneWindow 中的 setContentView,如下:

@Override
public void setContentView(int layoutResID) {
   	//父容器如果爲 null 則進行創建
    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 {
        //將傳入的資源Id 加載到 mContentParent 上
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // return new DecorView(context, featureId, this, getAttributes());
            //最終會創建一個 DecorView 賦值給 mDecor
            mDecor = generateDecor(-1);
            //.....
        } else {
            mDecor.setWindow(this);
        }
    	//mContentParent是一個	VeiwGroup
        if (mContentParent == null) {
            //最終會根據當前窗口樣式加載一個資源文件,並且加載到 mDecor 中,
            //並返回資源文件中 id 爲: @android:id/content 的ViewGroup,類型爲 FrameLayout
            mContentParent = generateLayout(mDecor);
        }
    }


protected ViewGroup generateLayout(DecorView decor) {
        // 獲取自當前主題的數據
        TypedArray a = getWindowStyle();
		//.......
    
        // 裝飾 decor,判斷當前窗口的屬性,將符合條件的佈局添加到 decor

        int layoutResource;
        int features = getLocalFeatures();
      
        else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
           
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
                //加載資源
                layoutResource = R.layout.screen_custom_title;
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
                layoutResource = res.resourceId;
                layoutResource = R.layout.screen_title;
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }

        mDecor.startChanging();
    	//調用 mDecor 的方法,將剛纔找到的系統佈局文件加載到 DecorView 中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    	// ID_ANDROID_CONTENT 這個ID 就是資源文件中的 Id
    	//點開findViewById ,就可以看到裏面用的 View 是 DecorView 
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
		//......
        mDecor.finishChanging();

        return contentParent;
    }

在 setContentView 方法中,調用了 installDecor(),下面分析一下這個方法。

首先會創建一個 DecorView ,這是一個 繼承子 Fragment 的View 。也就是說 Window 類中包含了一個 DecorView。

接着就會判斷 mContentParent 是否爲 null,如果爲 null,就會調用 generateLayout 去創建,mContentParent 是一個 ViewGroup。在 generateLayout中會調用系統的資源,判斷系統當前的窗口模式。然後加載對應的佈局。最終就會將這個資源文件加載到 DecorView 中。並且會調用 findViewById 找到一個 ViewGroup,並返回,點開findViewById ,就可以看到裏面用的 View 是 DecorView 。至於加載的是那個 id,如下所示:

一般情況下,加載的資源layout中都有會 framelayout 這個 View,並且可以看到 id 爲 @android:id/content。你可以複製佈局名稱然後全局搜索查看一下這個佈局。

佈局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

到這裏,installDecor()方法中比較重要的地方已經看完了。梳理一下:

在 setContentView() 中創建 DecorView ,接着根據系統窗口的類型獲取到一個資源 layout。接着講這個資源文件 加載到 DecorView 中,並 通過findViewById 獲取了 資源文件中 id 爲 @android:id/content 的控件,將其強轉爲 ViewGroup 並返回。

在這裏插入圖片描述

在 setContentView 方法中,調用完 installDecor()方法後,往下還有非常重要的一句話

mLayoutInflater.inflate(layoutResID, mContentParent);

這個 layoutResID 就是 調用 setContentView 時傳入的。這裏將這個資源加載到了 mContentParent 上面,通過上面的分析我們可以知道 contentParent 就是 DecorView 中 id 爲 @android:id/content 的 Framelayout 佈局。

在這裏插入圖片描述
最終大致的邏輯如上圖

我們可以做一個測試,看一下我們分析的有沒有問題:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(layout())

        val view = LayoutInflater.from(this).inflate(layout(), null)
        val decorView = window.decorView
        val frameLayout = decorView.findViewById<FrameLayout>(android.R.id.content)
        frameLayout.addView(view)

}

這是一個 activity 的 onCreate 方法,我註釋掉了 setContentView 方法,並直接加載了一個佈局。然後調用 window 中的 decorView。獲取到其中的 frameLayout,然後將 佈局添加進去。

最終運行顯示的效果是正常的。

下面給一張圖,清楚的展示了佈局加載的流程

在這裏插入圖片描述

AppCompatActivity 中的 setContentView

其實相比於 Activity 的 setContentView 還是有一些區別。主要是爲了兼容低版本的一些東西。

接下來就看一下源碼吧

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    getDelegate().setContentView(view, params);
}
@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

這裏調用的 setContentView 也是一個抽象方法,最終調用的是 AppCompatDelegateImpl 中的

@Override
public void setContentView(View v) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) 		        			 	mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    contentParent.addView(v);
    mOriginalWindowCallback.onContentChanged();
}
private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
        }
}
private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        final LayoutInflater inflater = LayoutInflater.from(mContext);
    	//空的 ViewGroup
        ViewGroup subDecor = null;
    
    	//根據一系列的判斷,最後加載一個 layout 到 ViewGroup 上
        if (!mWindowNoTitle) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
            } else if (mHasActionBar) {
              
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }
    
    //獲取 subDecor 中的id
     final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
		
    //這裏獲取的是 Window 中的 DecorView 
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            //設置一個 空的 Id
            windowContentView.setId(View.NO_ID);
            //給 contentView 設置一個新的 id
            contentView.setId(android.R.id.content);
        }

		//最終還是調用了 window 中的 setContentView 方法
        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        return subDecor;
    }

看流程,可以發現最終還是調用的 window 中的 setContentView,但是在這裏有多了一層

他首先會創建一個ViewGroup,然後根據一系列的判斷添加一個layout。最後調用 window 的 setContentView 。接着就會將我們傳入的佈局 添加到這個 ViewGroup 中,相比於 Activity。他中間多了一個 ViewGroup。

AppCompatActivity 的兼容性

一個小例子:新建一個 activity,在佈局中創建一個 ImageView。

<ImageView
    android:id="@+id/test_iv"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginTop="50dp"
    android:src="@drawable/image"/>

接着讓 activity 首先繼承 Activity ,最後繼承 AppCompatActivity,然後打印 ImageView

android.widget.ImageView
androidx.appcompat.widget.AppCompatImageView

發現如果是繼承自 AppcompatActivity,則 iamgeView 最終創建的是 AppCompatImageView ,具有兼容性的 ImageView。這個是爲啥呢,下面分析一下源碼:


源碼分析:

首先在 AppCompatActivity 的 onCreate 方法中 調用了一個非常重要的方法,如下:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    //安裝View 的工廠,這裏的 delegate 就是 AppCompatDelegateImpl 
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

AppCompatDelegateImpl 實現了一個接口 LayoutInflater.Factory2

@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        //把 LayoutInflater 的 Factory 設置爲 this,也就是說創建 View 就會掉自己的 onCreateView 方法
        //如果沒看懂就看一下 LayoutInflater 的源碼,LayoutInflater.from(mContext) 其實是一個單例
        //如果設置了 Factory,那麼每次創建 View 時都會先執行 onCreateView  方法
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
}

LayoutInflater.from(mContext) 其實是一個單例。他會將 LayoutInflater 的 Factory 設置爲 this。

當我們在使用這種 : LayoutInflater.from(this).inflate(layout(), parent) 代碼時,就會調用到 AppCompatDelegateImpl 類中 實現 LayoutInflater.Factory2的 onCreateView 方法,

public abstract class LayoutInflater {
	@UnsupportedAppUsage(trackingBug = 122360734)
	@Nullable
	public final View tryCreateView(@Nullable View parent, @NonNull String name,
	    @NonNull Context context,
	    @NonNull AttributeSet attrs) {
 	   if (name.equals(TAG_1995)) {
    	    // Let's party like it's 1995!
   	 }

  	  View view;
  	  if (mFactory2 != null) {
        //這裏,LayoutInflater.from(this).inflate(layout(), null),如果使用 AppCompatActivity ,就會給 mFactory2 設置一個值,最終這裏就會調用到 AppCompatDelegateImpl 中的 onCreateView 中。
  	      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);
 	   }
		return view;
	}
}

通過上面可以看到 在使用 LayoutInflater.from(this).inflate(layout(), null) 時,是如何調用到 AppCompatDelegateImpl 中的 onCreateView 中的。

接着看一下 onCreateView

/**
 * From {@link LayoutInflater.Factory2}.
 */
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return createView(parent, name, context, attrs);
}

 @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
    //.......

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP,     true,    VectorEnabledTintResources.shouldBeUsed() 
        );
    }
final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    final Context originalContext = context;
	//.......

    View view = null;
	//在這裏進行了替換
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageView":
            view = createImageView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "Button":
            view = createButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "EditText":
            view = createEditText(context, attrs);
            verifyNotNull(view, name);
            break;
        case "Spinner":
            view = createSpinner(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageButton":
            view = createImageButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckBox":
            view = createCheckBox(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RadioButton":
            view = createRadioButton(context, attrs);
            verifyNotNull(view, name);
            break;
        case "CheckedTextView":
            view = createCheckedTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "AutoCompleteTextView":
            view = createAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "MultiAutoCompleteTextView":
            view = createMultiAutoCompleteTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "RatingBar":
            view = createRatingBar(context, attrs);
            verifyNotNull(view, name);
            break;
        case "SeekBar":
            view = createSeekBar(context, attrs);
            verifyNotNull(view, name);
            break;
        default:
            // The fallback that allows extending class to take over view inflation
            // for other tags. Note that we don't check that the result is not-null.
            // That allows the custom inflater path to fall back on the default one
            // later in this method.
            view = createView(context, name, attrs);
    }

 
    return view;
}

//替換後的 AppComptImageView
@NonNull
protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
    return new AppCompatImageView(context, attrs);
}

最終將替換後的 View 進行返回

這裏比較重要的是 View 的創建首先會走 mFactory2,然後纔會走 mFactory,只要不會 null,就會執行 Factory 的 onCreateView 方法。否則最後就會走 系統的 createView 方法。


LayoutInflater

​ 主要用來實例化我我們的 layout 佈局

​ 使用的方式

View.inflate(this,R.layout.activity_main,null) //1
LayoutInflater.from(this).inflate(R.layout.activity_main,null) //2
LayoutInflater.from(this).inflate(R.layout.activity_main,null,false) //3
//1
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
//2
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}
//3
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
	//......
}

​ 從源碼中可以看到,第一種調用的是第二種,第二種調用的是第三種,根據有沒有傳入根佈局來傳入第三個參數。所以我們只需要看第三個方法就 ok

看一下 LayoutInflater.from()

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    return LayoutInflater;
}

############################# ContextImpl ############
@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

//一個靜態的 Map
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

可以看到這裏拿到的是一個系統的服務

接着往下看就可以看到這個服務是從一個 靜態的 Map 中獲取的。那麼這個 Map 是怎麼初始化的呢?

ContextImpl 中有一個靜態代碼塊,專門用來註冊各種服務,LAYOUT_INFLATER_SERVICE 也是其中的一個。

由此我們可以得知 LayoutInflater.from(this) 是一個系統服務,並且他是一個單例。

接着看一下是怎樣實例化 View 的

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    //獲取資源文件
    final Resources res = getContext().getResources();
   
    //XmlResourceParser  的解析器
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

    	//...........
        //保存傳進來的 Viwe
        View result = root;

        try {
            advanceToRootNode(parser);
            final String name = parser.getName();
		   //如果是 merge 標籤 就調用 rInflate,否則執行 else 
            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");
                }
			   //這裏直接加載界面,忽略 marge 標記,直接傳入 root 進 rInflate 進行加載子 View
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp是在xml中找到的根視圖,創建 View
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;
			   // root 如果不爲空,則設置 layoutParams	
                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);
                    }
                }
                //先獲取到了temp,再把temp當做root傳進去rInflateChildren,進行加載temp後面的子view
                rInflateChildren(parser, temp, attrs, true);

              	//把 View 添加到 root 佈局並設置佈局參數
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
			//...
            }

        } catch (XmlPullParserException e) {
       		//...
        }

        return result;
    }
}
//創建 View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
    	
		//......
        try {
            //創建 View
            View view = tryCreateView(parent, name, context, attrs);
		   //如果沒有創建成果
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    //判斷 name 是否爲 全類名,最終創建反射創建 View
                    //如果不是全類別,就需要進行拼接
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
       		//....
        }
    }

 public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
      
        View view;
     	//mFactory2 如果不爲 空,則直接調用 mFactory2 的 onCreateView
     	// AppCompatActivity 中就設置了 mFactory2
        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);
        }
        return view;
    }

在 AppCompatDelegateImp 中 爲什麼能走自己的 onCreateView 方法,就是因爲他設置了 mFactory ,所以纔可以攔截 View 的創建

如果說 mFactory 都等於 空,最後會自己創建 view,如果不爲空,則 View 的創建會被攔截,去執行對應 mFactory 中的方法

接着我們看下沒有使用 mFactory 的 View 創建

//默認的 View 創建流程
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
        @NonNull String name, @Nullable AttributeSet attrs)
        throws ClassNotFoundException {
    return onCreateView(parent, name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    //添加全類名
    return createView(name, "android.view.", attrs);
}

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
      
    	//從緩存中獲取
        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);
		   //如果緩存總沒有,則反射進行創建,並加入緩存	
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
			   //拿到構造函數mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
            	//拿到爲兩個參數的構造函數
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
               //.......
            }

            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            try {
                //反射創建 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]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }catch (Exception e) {
           //......
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

大致看完了源碼,需要知道一些幾個問題

1,如果獲取 LayoutInflater

​ 通過獲取系統的服務,並且是一個單例

2,如果使用 LayoutInflater

​ 三種使用方式,在開頭說過了

3,佈局是如果被實例化的

​ 最終佈局是通過反射進行實例化的

4,mFactory 的作用

​ 攔截 View 的創建,使 View 的創建走自定義的流程,如 AppCompatView 的 setContentView 中。

5,xml 和 直接 new 出來的有啥區別

佈局文件中的VIew 創建調用的是兩個參數的構造,而直接 new 的是通過一個參數的構造。並且 xml 中定義的佈局最終是通過反射進行創建的,所以儘量不要多重嵌套

攔截 View 的創建

​ 按照上面的分析可以知道,如果要攔截 View 的創建,就需要給 LayoutInflater 設置 Factory 。

 override fun onCreate(savedInstanceState: Bundle?) {
        intercept()
        super.onCreate(savedInstanceState)
    }

    private fun intercept() {
        val layoutInflater = LayoutInflater.from(this)
        LayoutInflaterCompat.setFactory2(layoutInflater, object : LayoutInflater.Factory2 {
            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
                return onCreateView(null, name, context, attrs)
            }

            override fun onCreateView(
                parent: View?, name: String, context: Context, attrs: AttributeSet
            ): View? {
                Log.e("BaseSkinActivity", "攔截到 View 的創建")
                //攔截 View 的創建
                return if (name == "Button") {
                    val button = Button(this@BaseSkinActivity)
                    button.id = R.id.test_btn
                    button.text = "攔截"
                    return button
                } else null
            }

        })
    }

上面給 LayoutInflater 設置了一個 Factory,攔截了 VIew 的創建

在 onCreateView 中,判斷如果是 Button,就修改他顯示的內容。

最終的結果就是攔截成功了。


到這裏整片文章就弄完了,如果有問題還請指出!,對你有幫助的還請點個贊!謝謝

參考自 紅橙Darren 的視頻

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