View Fragment Window 的 getContext() 一定返回 Activity 吗?

article/2025/9/12 3:52:16

目录


1. 问题分析

1.1 Context 有哪些?

首先,我们回顾一下 Context 以及它的子类,在之前的这篇文章里,我们曾经讨论过:《Android | 一个进程有多少个 Context 对象(答对的不多)》。简单来说:Context 使用了装饰模式,除了 ContextImpl 外,其他 Context 都是 ContextWrapper 的子类。

我们熟悉的 Activity & Service & Application,都是 ContextWrapper 的子类。调用getBaseContext(),可以获得被代理的基础对象:

ContextWrapper.java

 

Context mBase;public ContextWrapper(Context base) {mBase = base;
}public Context getBaseContext() {return mBase;
}  

需要注意的是,Activity 也是可以作为被代理的对象的,类似这样:

 

Activity activity = ...;
Context wrapper = new ContextThemeWrapper(activity, themeResId);wrapper.startActivity(...); // OKwrapper instanceOf Activity // false

这个时候,代理对象wrapper可以使用 Activity 的能力,可以用它 startActivity(),也可以初始化 View,然而它却不是 Activity。看到这里,我们似乎找到了问题的一点苗头了:getContext() 可能返回 Activity 的包装类,而不是 Activity。

1.2 问题延伸

网上讨论得比较多的,主要还是View#getContext()的返回值,在这篇文章里,我们将延伸一下,以下几种情况我都会归纳,以便帮助你建立更为清晰全面的认识:

  • View#getContext()
  • Fragment#getContext()
  • Window#getContext()
  • Dialog#getContext()

2. View#getContext() 的返回值

我们来看View#getContext()的源码,可以看到,View#getContext()返回值是在构造函数中设置的,源码里未发现其它赋值语句。所以,这个问题的关键是看:实例化 View 时传入构造器的 Context 对象

View.java

@hide
protected Context mContext;public final Context getContext() {return mContext;
}public View(Context context) {mContext = context;...  
}
...

在使用 View 的过程用,有两种方式可以实例化 View :

  • 方法1:代码调用,类似这样:new TextView(Context)

很明显,只要你传入什么对象,将来你调用 getContext(),得到的就是同一个对象。回顾 第 1 节 的讨论,你可以传入 Activity,也可以传入包装类。诶,那可以传入 Service、Application、ContextImpl 吗?还真的可以,只是你要保证 getContext() 后的行为正确,一般不会这么做。

new TextView(Activity)
new TextView(ContextWrapper)
new TextView(Service)     一般不会这么做
new TextView(Application) 一般不会这么做
new TextView(ContextImpl) 一般不会这么做
  • 方法2:布局文件,类似这样:<TextView ...>

这种方式其实是利用了 LayoutInflater 布局解析的能力,在之前的这篇文章里,我们曾经讨论过:《Android | 带你探究 LayoutInflater 布局解析原理》,如果你对 LayoutInflater 布局解析的流程还不熟悉,可以先复习下,相同的地方不再重复提。在这里,我们只关注使用反射实例化 View 的地方:

createViewFromTag(...) 示意图

可以看到,实例化 View 的地方使用了反射,而Constructor#newInstance(...)的首个参数即为将来 getContext() 返回的对象。那么,mConstructorArgs[0]到底是什么对象呢,是 Activity 吗?我们逆着源码找找看:

LayoutInflater.java

public final View createView(@NonNull Context viewContext, @NonNull String name,@Nullable String prefix, @Nullable AttributeSet attrs){...疑问:viewContext 到底是什么呢?mConstructorArgs[0] = viewContext; final View view = constructor.newInstance(mConstructorArgs);...
}createViewFromTag() -> createView()(已简化)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {1. 应用 ContextThemeWrapper 以支持 android:themeif (!ignoreThemeAttr) {final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);final int themeResId = ta.getResourceId(0, 0);if (themeResId != 0) {1.1 注意:这里使用了包装类context = new ContextThemeWrapper(context, themeResId);}ta.recycle();}2. 先使用 Factory2 / Factory 实例化 View,相当于拦截3. 使用 mPrivateFactory 实例化 View,相当于拦截4. 调用自身逻辑if (view == null) {view = createView(name, null, attrs);}return view;     
}// inflate() -> createViewFromTag()public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {...注意:使用了 mContextfinal Context inflaterContext = mContext;...final View temp = createViewFromTag(root, name, inflaterContext, attrs);...
}  protected LayoutInflater(Context context) {mContext = context;initPrecompiledViews();
}

AppCompatViewInflater.java

2. 先使用 Factory2 / Factory 实例化 View,相当于拦截
final View createView(...) {final Context originalContext = context;2.1 应用 ContextThemeWrapper 以支持 android:theme / app:themeif (readAndroidTheme || readAppTheme) {context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);}if (wrapContext) {2.2 应用 ContextThemeWrapper 以支持矢量图 tintcontext = TintContextWrapper.wrap(context);}View view = null;switch (name) {case "TextView":2.3 实例化 AppCompatTextViewview = createTextView(context, attrs);break;...default:view = createView(context, name, attrs);}return view;
}-> 2.1 应用 ContextThemeWrapper 以支持 android:theme(已简化)
private static Context themifyContext(Context context, AttributeSet attrs, boolean useAndroidTheme, boolean useAppTheme) {// 事实上,分支 1.1 已经处理了,这里是兼容 Android 5.0 以前。return new ContextThemeWrapper(context, themeId);
}-> 2.2 应用 ContextThemeWrapper 以支持矢量图 android:tint(已简化)
public static Context wrap(@NonNull final Context context) {return new TintContextWrapper(context);
}

AppCompatTextView.java

-> 2.3 实例化 AppCompatTextView
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
}

以上代码已经十分简化了,当然你也可以选择直接看结论:

小结:

  • 分支 1.1:应用 ContextThemeWrapper 以支持android:theme,此时 View#getContext() 返回这个包装类;
  • 分支 2.1:应用 ContextThemeWrapper 以支持android:theme(事实上,分支 1.1 已经处理了,这里是兼容 Android 5.0 前),同样也是返回包装类;
  • 分支 2.2:应用 ContextThemeWrapper 以支持矢量图android:tint,这是为了兼容 Android 5.0 以前不支持 tint,同样也是返回包装类;
  • 分支 2.3:实例化 AppCompatTextView,同样也是返回包装类;
  • 分支 4:返回的是 LayoutInflater#mContext,这个是LayoutInflater.from(Context)传入的参数。在 《Android | 带你探究 LayoutInflater 布局解析原理》里,我们讨论过:在 Activity / Fragment / View / Dialog 中,获取LayoutInflater#getContext(),返回的就是 Activity。

第 2 节讨论完后,下面这几节就容易多了。


3. Dialog & Window 的 getContext() 的返回值

直接看源码:

Window.java

private final Context mContext;public final Context getContext() {return mContext;
}public Window(Context context) {mContext = context;mFeatures = mLocalFeatures = getDefaultFeatures(context);
}

Activity.java

final void attach(Context context, ActivityThread aThread,...){...注意:mContext 为 Activity 本身mWindow = new PhoneWindow(this, window, activityConfigCallback);...
}

Dialog.java

public Dialog(@NonNull Context context, @StyleRes int themeResId) {this(context, themeResId, true);
}Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {if (createContextThemeWrapper) {if (themeResId == Resources.ID_NULL) {final TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);themeResId = outValue.resourceId;}包装为 ContextThemeWrappermContext = new ContextThemeWrapper(context, themeResId);} else {mContext = context;}...final Window w = new PhoneWindow(mContext);...
}

小结:

  • Dialog#getContext() 返回 ContextThemeWrapper;
  • 在 Activity 中,Window#getContext() 返回 Activity;在 Dialog中,Window#getContext() 返回 ContextThemeWrapper;

4. Fragment#getContext() 的返回值

直接看源码:

Fragment.java

FragmentHostCallback mHost;public Context getContext() {return mHost == null ? null : mHost.getContext();
}

FragmentHostCallback.java

Context getContext() {return mContext;
}FragmentHostCallback(FragmentActivity activity) {this(activity, activity /*context*/, activity.mHandler, 0 /*windowAnimations*/);
}FragmentHostCallback(Activity activity, Context context, Handler handler, int windowAnimations) {mActivity = activity;mContext = Preconditions.checkNotNull(context, "context == null");mHandler = Preconditions.checkNotNull(handler, "handler == null");mWindowAnimations = windowAnimations;
}

FragmentActivity.java

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());class HostCallbacks extends FragmentHostCallback<FragmentActivity> {public HostCallbacks() {super(FragmentActivity.this /*fragmentActivity*/);}...
}

小结:

  • Fragment#getContext() 返回 Activity;

5. 从 View#getContext() 获得 Activity 对象

在很多场景中,经常需要通过 View 来获得 Activity 对象,经过前面几节内容的讨论,我们已经知道View#getContext()的返回值总共有以下五种情况:

Activity
ContextWrapper
Service 一般不会
Application 一般不会
ContextImpl 一般不会

那么,要获得 Activity 则只要不断得获取 Context 的被代理对象(基础对象),就可以获得 Activity;当然了,下面 Service & Application & ContextImpl几种情况是返回空的,所以我们用@Nullable修饰。

递归写法:
@Nullable
private static Activity findActivity(Context context) {if (context instanceof Activity) {return (Activity) context;} else if (context instanceof ContextWrapper) {return findActivity(((ContextWrapper) context).getBaseContext());} else {return null;}
}
迭代写法:
@Nullable
public static Activity findActivity(Context context){Context cur = context;while (true){if (cur instanceof Activity){return (Activity) cur;}if (cur instanceof ContextWrapper){ContextWrapper cw = (ContextWrapper) cur;cur = cw.getBaseContext();}else{return null;}}
}

6. 总结

  • 应试建议
    • 遇到此问题,答案应为:可能是Application、Service、ContextImpl、ContextWrapper、Activity的任何一个;
    • 应该对Context类型、LayoutInflater 布局解析、View 体系等源码有一定熟悉度,不仅仅能够解答本文问题,更多有意思/深度的问题也能迎刃而解。

 


http://chatgpt.dhexx.cn/article/aJpw8J02.shtml

相关文章

HTML5 canvas 参考手册

HTML5 <canvas> 参考手册 描述 HTML5 <canvas> 标签用于绘制图像&#xff08;通过脚本&#xff0c;通常是 JavaScript&#xff09;。 不过&#xff0c;<canvas> 元素本身并没有绘制能力&#xff08;它仅仅是图形的容器&#xff09; - 您必须使用脚本来完成实…

Unix/Linux编程:getcontext、setcontext

ucontext该结构提供了所谓的用户上下文信息&#xff0c;用于描述调用信号处理器函数前的进程状态&#xff0c;其中包括上一个进程信号掩码以及寄存器的保存值&#xff0c;例如程序计数器&#xff08;cp&#xff09;和栈指针寄存器&#xff08;sp&#xff09;&#xff0c;使用结…

Andriod getContext和getActivity

原创文章&#xff0c;如有转载&#xff0c;请注明出处&#xff1a;http://blog.csdn.net/myth13141314/article/details/62045162 MainActivity.this&#xff1a;表示MainActivity对象&#xff0c;一般用在内部类中指示外面的this&#xff0c;如果在内部类直接用this&#xff…

岭回归模型|机器学习|回归算法

目录 1.岭回归模型1.1背景1.2损失函数 2.相关代码2.1RidgeRegression类2.2求解代码2.3绘图代码 3.直接调库使用 1.岭回归模型 1.1背景 对于回归问题来说&#xff0c;它们的基本内容基本上都是相同的&#xff0c;所以岭回归模型与线性回归模型类似&#xff1a; y θ 0 x 0 …

机器学习学习笔记(13)----岭回归(Ridge回归)

在《机器学习学习笔记&#xff08;4&#xff09;----线性回归的数学解析》&#xff0c;我们通过计算线性模型的损失函数的梯度&#xff0c;得到使得损失函数为最小值的的解析解&#xff0c;被称之为普通最小二乘法&#xff1a; (1) 公式(1)能够求得的前提是是满秩矩阵&#xf…

Python机器学习教程—岭回归的原理和实现

在某些场景下&#xff0c;线性回归无法给出一个效果好的预测模型&#xff0c;那么就需要使用线性回归的升级版&#xff0c;去面对更复杂的应用场景&#xff0c;本文所记录的岭回归便是线性回归的一个升级版。 目录 强势样本对模型的影响 实例 岭回归定义 岭回归的实现 岭…

岭回归和Lasso回归

偏差和方差 机器学习算法针对特定数据所训练出来的模型并非是十全十美的&#xff0c;再加上数据本身的复杂性&#xff0c;误差不可避免。说到误差&#xff0c;就必须考虑其来源&#xff1a;模型误差 偏差&#xff08;Bias&#xff09; 方差&#xff08;Variance&#xff09; …

基于Python的岭回归模型

本文是使用Python进行岭回归模型分析&#xff0c;消除多重共线性 一、岭回归原理 自变量之间存在多重共线性&#xff0c;当时&#xff0c;加上一个正的常数矩阵(K>0)&#xff0c;岭回归估计定义公式&#xff1a; &#xff08;k为岭参数&#xff09; 的奇异程度比奇异程度小&…

用SPSS进行岭回归分析

打开SPSS将数据输入到SPSS中。 点击左上角文件->新建->语法 输入 * Encoding: UTF-8. INCLUDE D:\Program Files\IBM\SPSS\Statistics\27\Samples\Simplified Chinese\Ridge regression.sps. ridgereg enter x1 x2 x3 x4 x5 x6 x7 x8 x9 x10/depy.include内容需要自己寻找…

R语言与岭回归

岭参数的一般选择原则 选择k&#xff08;或lambda&#xff09;值&#xff0c;使得&#xff1a;各回归系数的岭估计基本稳定用最小二乘估计时符号不合理的回归系数&#xff0c;其岭回归的符号变得合理回归系数没有不合乎实际意义的绝对值残差平方和增大的不多 用R语言进行岭回归…

岭回归(Ridge Regression)及实现

岭回归(Ridge Regression)及实现 https://blog.csdn.net/google19890102/article/details/27228279 一、一般线性回归遇到的问题 在处理复杂的数据的回归问题时&#xff0c;普通的线性回归会遇到一些问题&#xff0c;主要表现在&#xff1a; 在处理复杂的数据的回归问题时&…

岭回归(R语言)

代码实现如下&#xff1a; data3.3<-read.csv("C:/Users/Administrator/Desktop/data3.3.csv",headTRUE) datas<-data.frame(scale(data3.3[,1:6])) # 对样本数据进行标准化处理并转换为数据框的存储格式 library(MASS) ridge3.3<-lm.ridge(y~.-1,datas,l…

多元线性回归-岭回归

目录 1.精确相关关系 2.高度相关关系 3.多重共线性与相关性 4.岭回归 5.linear_model.Ridge 5.1.案例1&#xff1a;加利福尼亚房屋价值数据 5.2.案例2:波士顿房价数据集 6.选取最佳正则化参数取值 1.精确相关关系 精确相关关系&#xff0c;即完全相关。如矩阵A并不是满…

回归——岭回归

1、作用 岭回归是一种专用于共线性数据分析的有偏估计回归方法&#xff0c;实质上是一种改良的最小二乘估计法&#xff0c;通过放弃最小二乘法的无偏性&#xff0c;以损失部分信息、降低精度为代价获得回归系数更为符合实际、更可靠的回归方法&#xff0c;对病态数据的拟合要强…

【机器学习04】岭回归

4 岭回归 4.1 简介 普通线性回归模型使用基于梯度下降的最小二乘法&#xff0c;在最小化损失函数的前提下&#xff0c;寻找最优模型参数&#xff0c;在此过程中&#xff0c;包括少数异常样本在内的全部训练数据都会对最终模型参数造成程度相等的影响&#xff0c;异常值对模型…

机器学习——岭回归

岭回归的简单介绍 什么是岭回归&#xff1f;什么时候要用到岭回归&#xff1f;岭回归是一种解决标准方程法不能求逆矩阵时的办法。我们都知道&#xff0c;用标准方程法最大的一个缺点就是当数据的特征多于数据的样本时&#xff0c;标准方程法是不能使用的&#xff0c;因为不能…

数学建模学习:岭回归和lasso回归

线性回归 在多元线性回归模型中&#xff0c;估计回归系数使用的是OLS&#xff0c;并在最后讨论异方差和多重共线性对模型的影响。事实上&#xff0c;回归中自变量的选择大有门道&#xff0c;变量过多可能会导致多重共线性问题导致回归系数不显著&#xff0c;甚至造成OLS估计失…

python机器学习| 岭回归介绍及实现

岭回归介绍及实现 1 岭回归的引入2 岭回归的原理2.1 原理介绍2.2 原理代码实现 3 API 实现 1 岭回归的引入 在线性回归-正规方程和梯度下降中&#xff0c;我们介绍了基于正规方程或者梯度下降的优化算法&#xff0c;来寻找最优解。 在正规方程解中&#xff0c;它是基于直接求导…

岭回归与lasso回归算法

模型压缩与正则化主要包含岭回归&#xff08;Ridge regression&#xff09;和Lasso两种方法&#xff0c;二者的主要原理是将系数往等于0的方向压缩。 岭回归 lasso 全称&#xff1a;Least absolute shrinkage and selection operator最小绝对缩减和选择算子 一、岭回归示…

岭回归-回归实操

python 岭回归算法之回归实操 基本概念 正则化 正则化是指对模型做显式约束&#xff0c;以避免过拟合。本文用到的岭回归就是L2正则化。&#xff08;从数学的观点来看&#xff0c;岭回归惩罚了系数的L2范数或w的欧式长度&#xff09; 正则化的具体原理就不在这里多叙述了&a…