坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过。
一般我们被问到这样的问题,通常来说,答案都是否定的,但一定得知道其中的原因,不然回答肯定与否又有什么意义呢。
首先,显而易见这个问题有不少陷阱,比如这个 View 是自己构造出来的,那肯定它的 getContext() 返回的是构造它的时候传入的 Context 类型。
它也可能返回的是 TintContextWrapper那,如果是 XML 里面的 View 呢,会怎样?可能不少人也知道了另外一个结论:直接继承 Activity 的 Activity 构造出来的 View.getContext() 返回的是当前 Activity。但是:当 View 的 Activity 是继承自 AppCompatActivity,并且在 5.0 以下版本的手机上,View.getContext() 得到的并非是 Activity,而是 TintContextWrapper。
不太熟悉 Context 的继承关系的小伙伴可能也会很奇怪,正常来说,自己所知悉的 Context 继承关系图是这样的。
我们可以先看看 Activity.setContentView() 方法:
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }不过是直接调用 Window 的实现类 PhoneWindow 的 setContentView() 方法。看看 PhoneWindow 的 setContentView() 是怎样的。
@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. 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 { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }假如没有 FEATURE_CONTENT_TRANSITIONS 标记的话,就直接通过 mLayoutInflater.inflate() 加载出来。这个如果有 mLayoutInflater 的是在PhoneWindow 的构造方法中被初始化的。而 PhoneWindow 的初始化是在 Activity 的 attach() 方法中:
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 本身。
在回到我们前面分析的 PhoneWindow 的 setContentView() 方法,如果有 FEATURE_CONTENT_TRANSITIONS 标记,直接调用了一个 transitionTo() 方法:
private void transitionTo(Scene scene) { if (mContentScene == null) { scene.enter(); } else { mTransitionManager.transitionTo(scene); } mContentScene = scene; }在看看 scene.enter() 方法。
public void enter() { // Apply layout change, if any if (mLayoutId > 0 || mLayout != null) { // empty out parent container before adding to it getSceneRoot().removeAllViews(); if (mLayoutId > 0) { LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); } else { mSceneRoot.addView(mLayout); } } // Notify next scene that it is entering. Subclasses may override to configure scene. if (mEnterAction != null) { mEnterAction.run(); } setCurrentScene(mSceneRoot, this); }基本逻辑没必要详解了吧?还是通过这个 mContext 的 LayoutInflater 去 inflate 的布局。这个 mContext 初始化的地方是:
public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) { SparseArray<Scene> scenes = (SparseArray<Scene>) sceneRoot.getTag( com.android.internal.R.id.scene_layoutid_cache); if (scenes == null) { scenes = new SparseArray<Scene>(); sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes); } Scene scene = scenes.get(layoutId); if (scene != null) { return scene; } else { scene = new Scene(sceneRoot, layoutId, context); scenes.put(layoutId, scene); return scene; } }即 Context 来源于外面传入的 getContext(),这个 getContext() 返回的就是初始化的 Context 也就是 Activity 本身。
AppCompatActivity.setContentView()