1.1 View.getContext()
Context context = imageView.getContext();
if (context instanceof Activity) {
Activity activity = (Activity)context;
// …
}
从上面的代码举例中可以看到,从 imageView
控件里获取到 context
,转化为 Activity
来继续操作。这个 imageView
是来自 XML 布局中的一个控件,但在实际项目运行时有的手机并未走到转换类型的 if
分支里去,表明这个 context
并非 Activity
类型。这个就很奇怪了,为什么呢?
/**
- Simple constructor to use when creating a view from code.
- @param context The Context the view is running in, through which it can
-
access the current theme, resources, etc.
*/
public View(Context context) {
mContext = context;
//…省略
}
@ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}
我们点进去看下 View.getContext()
方法,返回 mContext
成员变量,而且 mContext
赋值只有在构造函数里。依据印象,这个 imageView
是写在 XML 中的,在 setContextView(R.layout.xxx)
时候,实际调用的应该就是 PhoneWindow
里的 setContextView()
方法,那构建使用的 context
应该就是 Activity
类型啊?
这时候我又回去仔细 Debug 了一回,发现出现问题的都是在 5.0 以下的手机里。所以上面的印象是有问题的,在 5.0 以下,这个 imageView.getContext()
获取到的 context
类型不是我一开始以为的 Activity
类型,而是 TintContextWrapper
类型。
1.2 Context 类型
这个 TintContextWrapper 是什么 Wrapper ?我印象中 Context 的继承关系中没有这个啊。 关于 Context 类型 www.jianshu.com/p/94e0f9ab3… 的讲解,不清楚的小伙伴可以自行搜索下,这里就不展开了,网上能讲清楚的也不少,这里贴个图看下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRPsR006-1638084177819)(https://user-gold-cdn.xitu.io/2018/1/9/160dac4188ef008e?imageView2/0/w/1280/h/960/ignore-error/1)]
确实也没有这个 TintContextWrapper 这个类型,从名字看应该也是个 Wrapper 类型的 Context ,还和 Tint 有关系。那剩下的线索还有这个 imageView
,再 Debug 一次,发现这个 imageView
的类型也不是原先在 XML 中定义的 ImageView 类型,而是 AppCompatImageView 类型。
猛然醒悟,控件所在的 Activity 是继承自 AppCompatActivity ,这个 context
类型的变化一定是和 v7 包里的 AppCompatActivity 有关系。之前所谓的印象已经出了两次错误,何不读源码解惑?
注意:下面的文章并不是完全依照查问题时的顺序来的,而是阅读完相关源码后,整理出来的相关知识点。已经清楚的小伙伴可以挑着阅读。
二、Activity 中 setContentView() 与 addContentView() 的区别
如果多次调用 setContentView() ,则之后每次都会清空 mContentParent
容器。然后组装资源 layoutResID
。
如果多次调用 addContentView() ,则之后每次都会将 View 添加到 mContentParent
容器中。最后产生 View 的叠加效果。
这个 mContentParent
存在于 PhoneWindow 中。
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
三、AppCompatActivity 和 Activity 的 setContentView() 方法的区别?
setContentView() 方法有两类,其中一类的必要参数是 XML 布局 id ,另一类的必要参数是 View 类型。
setContentView(@LayoutRes int layoutResID)
setContentView(View view)
这里我们以参数为 View 类型的代码讨论。
3.1 Activity
3.1.1 Activity.setContentView()
// Activity代码
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
Activity 中 setContentView()
代码,获取 window
来 setContentView()
。
// Window代码
public abstract void setContentView(View view);
而这个 window
其实就是 PhoneWindow
,看下面的代码。
// Activity代码
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) {
//…省略
mWindow = new PhoneWindow(this, window);
//…省略
}
3.1.2 PhoneWindow.setContentView()
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
//…省略
}
代码第12行,确保 mContentParent
已经初始化过。
第14行,如果没有 FEATURE_CONTENT_TRANSITIONS
,先清空 mContentParent
里内容。
第22行, mContentParent
将 view
当子孩子添加进来。
第17行,如果有 FEATURE_CONTENT_TRANSITIONS
,调用 transitionTo(newScene)
。这部分不展开了,最终也是调用以下代码,逻辑步骤都是一样的。
//Scene 代码
//mSceneRoot 就是刚才的 mContentParent
//mLayout 就是 setContentView 方法传进来的 view
public Scene(ViewGroup sceneRoot, View layout) {
mSceneRoot = sceneRoot;
mLayout = layout;
}
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);
}
}
//…省略
}
3.2 AppCompatActivity
可以看到 Activity 中 setContentView() 流程还是比较简单的,基本上就是调用了PhoneWindow 里的相应方法。下面我们来看看 AppCompatActivity 中有什么特别的。
3.2.1 AppCompatActivity.setContentView() 方法
// Ap