1、View.getContext()
@ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}
代碼很簡(jiǎn)單直接返回成員變量mContext,那么mContext是在哪里賦值的呢?搜索發(fā)現(xiàn)mContext只有一個(gè)賦值的地方:即View的構(gòu)造函數(shù)中
public View(Context context) {
mContext = context;
//.......
}
所以getContext()返回的對(duì)象,取決于創(chuàng)建View時(shí)傳什么參數(shù)。一般我們創(chuàng)建View的方式有兩種:
- 1、直接通過(guò)構(gòu)造new出對(duì)象。
- 2、通過(guò)xml解析創(chuàng)建View對(duì)象
第一種方式?jīng)]啥好講的,創(chuàng)建View時(shí)傳入什么對(duì)象,getContext()就返回什么。下面主要看下第二種方式。
2、通過(guò)xml解析創(chuàng)建View對(duì)象
開(kāi)發(fā)中我們經(jīng)常在xml中寫(xiě)布局,然后在Activity的onCreate()方法中通過(guò)setContentView(int layoutId)進(jìn)行布局的加載。xml中的控件是如何創(chuàng)建的呢?setContentView()在Activity和AppCompatActivity中是不同的。
2.1、Activity.setContentView()
###Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()返回PhoneWindow的對(duì)象。所以Activity的setContentView()不過(guò)是在調(diào)用PhoneWindow()的setContentView()方法。
###PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//installDecor()中創(chuàng)建DecorView,并將id為android.R.id.content的mContentParent添加到DecorView中。
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//省略部分代碼.....
}
代碼很簡(jiǎn)單,如果沒(méi)有FEATURE_CONTENT_TRANSITIONS標(biāo)記的話,則直接調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);加載出來(lái)。mLayoutInflater是在PhoneWindow的構(gòu)造函數(shù)中創(chuàng)建的,PhoneWindow是在Activity中的attach()方法中創(chuàng)建的。
###Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//省略部分代碼....
}
所以PhoneWindow中的context就是Activity本身。
- 問(wèn): LayoutInflater中的mContext和PhoneWindow中的context有什么關(guān)系?
- 答: LayoutInflater.from(context)最終會(huì)通過(guò)調(diào)用LayoutInflater的構(gòu)造函數(shù)
protected LayoutInflater(Context context) {
mContext = context;
}
對(duì)mContext進(jìn)行賦值。所以LayoutInflater中的mContext也是Activity本身。
- 問(wèn):LayoutInflater中的mContext和View中的mContext是什么關(guān)系?
- 答:這就需要看下inflate()方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//省略部分代碼...
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//省略部分代碼
}
主要看下createViewFromTag()方法
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//省略部分代碼
try {
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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
如果mFactory2、mFactory、mPrivateFactory不會(huì)空,則調(diào)用其onCreateView()進(jìn)行View的創(chuàng)建,繼承Activity的Activity mFactory2和mFactory均為空,而mPrivateFactory的實(shí)現(xiàn)類就是Activity本身,但是Activity中的onCreateView()方法直接返回null,所以會(huì)調(diào)用LayoutInflate的createView()去創(chuàng)建View,其中原理很簡(jiǎn)單就是通過(guò)反射進(jìn)行View的創(chuàng)建。此時(shí)LayoutInflater中的mContext就傳遞到View中了。
2.1.1、總結(jié)
說(shuō)了這么多有點(diǎn)亂,總結(jié)一下Activity傳遞到View中的過(guò)程:
- 1、在Activity的attach方法中創(chuàng)建了PhoneWindow將Activity傳遞到PhoneWindow中。
- 2、在PhoneWindow的構(gòu)造函數(shù)中創(chuàng)建了LayoutInflater,將Activity傳遞到LayoutInflater中。
- 3、通過(guò)反射創(chuàng)建View,傳遞到View中。
所以,在繼承Activity的頁(yè)面中通過(guò)xml加載的View的getContext()返回的對(duì)象一定是Activity。
2.2、AppCompatActivity.setContentView()
直接上源碼
###AppCompatActivity.java
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
@NonNull
public AppCompatDelegate getDelegate() {
if (this.mDelegate == null) {
this.mDelegate = AppCompatDelegate.create(this, this);
}
return this.mDelegate;
}
###AppCompatDelegate .java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
可以看到最終調(diào)用的是AppCompatDelegateImpl中的setContentView()
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
主要做了3件事:
- 1、確保 mSubDecor 的初始化
- 2、從mSubDecor 中找id為android.R.id.content的contentParent
- 3、通過(guò)inflate將id為resId的View添加到contentParent中。
2.2.1、mSubDecor的初始化
###AppCompatDelegateImpl.java
private void ensureSubDecor() {
if(!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor();
//省略部分代碼。。。
}
//省略部分代碼。。。
}
private ViewGroup createSubDecor() {
//...省略... 這部分主要針對(duì) AppCompat 樣式檢查和適配
// Now let's make sure that the Window has installed its decor by retrieving it
//這句代碼很重要哦
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
//...省略... 這部分主要針對(duì)不同的樣式設(shè)置來(lái)初始化不同的 subDecor(inflater 不同的布局 xml )
subDecor =inflater.inflate(XXX);
//...省略...
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
//...省略...
return subDecor;
}
- 1、通過(guò)mWindow.getDecorView();創(chuàng)建DecorView,并將id為android.R.id.content的ContentParent添加到DecorView中。
- 2、創(chuàng)建mSubDecor
-3 、將DecorView中的ContentParent的所有子View都添加到mSubDecor的子View contentView中,并清空ContentParent中所有子View,然后將ContentParent的id置成-1,將contentView的id置成android.R.id.content,依次來(lái)達(dá)到偷梁換柱的目的。 - 4、最后通過(guò)mWindow.setContentView(subDecor);將subDecor添加到DecorView中。
還記得我們要干什么嗎?當(dāng)然是看繼承AppCompatActivity的Activity中的View中的Context和AppCompatActivity的關(guān)系了,我們回到AppCompatDelegateImpl的setContentView()
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
上面分析了ensureSubDecor()做了些什么事,真正添加resId的仍然是通過(guò)
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
來(lái)完成的。
2.2.2、LayoutInflater.inflate()
在分析Activity的setContentView()時(shí)我們已經(jīng)對(duì)inflate做了分析,最終創(chuàng)建View的方法是LayoutInflater的createViewFromTag()
。
###LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//省略部分代碼
try {
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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
和Activity不同的是mFactory2不為空,mFactory2是在AppCompatActivity中的onCreate()中賦值的。
###AppCompatActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
//省略部分代碼...
}
### AppcompatDelegateImpl.java
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
if (layoutInflater.getFactory() == null) {
//最終調(diào)用LayoutInflater的setFactory2()
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
}
}
###LayoutInflater.java
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);
}
}
此時(shí)LayoutInflater中的mFactory和mFactory2已被賦值了,mFactory和mFactory2的實(shí)例就是AppCompatDelegateImpl,那么mPrivateFactory是何時(shí)賦值的呢?
/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
沒(méi)找到哪里調(diào)用的,從注釋可以看出是framework調(diào)用的。通過(guò)斷點(diǎn)可知mPrivateFactory 的實(shí)例就是AppCompatActivity本身。下面我們看下Factory2的實(shí)現(xiàn)類AppCompatDelegateImpl和AppCompatActivity中的onCreateView(View parent, String name, Context context, AttributeSet attrs)
方法到底做了什么?
2.2.3、AppCompatDelegateImpl的onCreateView()
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return this.createView(parent, name, context, attrs);
}
public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
//省略...這部分創(chuàng)建mAppCompatViewInflater
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
}
return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}
可以看到最終會(huì)調(diào)用AppCompatViewInflater的createView()方法
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;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
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);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
這段代碼主要為了兼容低版本,將name為T(mén)extView、ImageView等控件替換成AppCompatXXX,以TextView轉(zhuǎn)換成AppCompatTextView為例看下。
###AppCompatViewInflate.java
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
###AppCompatTextView.java
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
this.mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
this.mTextHelper = new AppCompatTextHelper(this);
this.mTextHelper.loadFromAttributes(attrs, defStyleAttr);
this.mTextHelper.applyCompoundDrawablesTints();
}
可以看到AppCompatTextView的構(gòu)造函數(shù)對(duì)context進(jìn)行了包裝:TintContextWrapper.wrap(context)
public static Context wrap(@NonNull Context context) {
if (shouldWrap(context)) {
//省略代碼...
TintContextWrapper wrapper = new TintContextWrapper(context);
sCache.add(new WeakReference(wrapper));
return wrapper;
}
} else {
return context;
}
}
如果shouldWrap(context)
返回true,則將context包裝成TintContextWrapper ,否則直接返回context。
private static boolean shouldWrap(@NonNull Context context) {
if (!(context instanceof TintContextWrapper) && !(context.getResources() instanceof TintResources) && !(context.getResources() instanceof VectorEnabledTintResources)) {
return VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
} else {
return false;
}
}
可以看到在VERSION.SDK_INT >= 21時(shí)肯定返回false,在VERSION.SDK_INT<21時(shí)會(huì)將context包裝成TintContextWrapper。所以我們可以得出結(jié)論:
- 1、在Android5.0之前,會(huì)使用TintContextWrapper創(chuàng)建name為T(mén)extView、ImageView等控件。
- 2、在Android5.0及以上直接使用context即AppCompatActivity創(chuàng)建View。
分析完了AppCompatDelegateImpl的onCreateView()
,下面看下AppComatActivity的onCreateView()
。
2.2.4、AppComatActivity的onCreateView()
AppComatActivity中并沒(méi)有重寫(xiě)onCreateView,而在其父類FragmentActivity做了重寫(xiě)
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = this.dispatchFragmentsOnCreateView(parent, name, context, attrs);
return v == null ? super.onCreateView(parent, name, context, attrs) : v;
}
可以看到對(duì)于非Fragment的控件會(huì)直接調(diào)用super.onCreateView(parent, name, context, attrs)
即Activity的onCreateView()。
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
Activity的處理很粗暴,直接return null;所以name不為T(mén)extView、ImageView等的控件,如LinearLayout,會(huì)通過(guò)反射進(jìn)行創(chuàng)建。
2.2.5、AppCompatActivity傳遞到View的過(guò)程總結(jié)
- 1、創(chuàng)建AppCompatDelegateImpl,將AppCompatActivity賦值給mContext。
- 2、然后通過(guò)
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
將AppCompatActivity傳遞給LayoutInflater。 - 3、在LayoutInflater中的
createViewFromTag()
方法中調(diào)用Factory2的實(shí)現(xiàn)類 AppCompatDelegateImpl中的createView()
將name為T(mén)extView、ImageView等的控件替換成AppCompat開(kāi)頭的控件,在Android5.0以下的創(chuàng)建AppCompatXX時(shí)傳遞的是TintContextWrapper。 - 4、對(duì)于name不為T(mén)extView、ImageView的控件依然調(diào)用LayoutInflater中的onCreateView,通過(guò)反射創(chuàng)建。
3、總結(jié)
- 1、繼承Activity的頁(yè)面中的View,getContext()直接返回Activity。
- 2、繼承AppCompatActivity的頁(yè)面中的View,
- 在Android5.0以下,如果name為T(mén)extView、ImageView等的控件的getContext()返回TintContextWrapper,否則則返回AppCompatActivity。
- 在Android5.0以上,直接返回AppCompatActivity。