Context概述以及继承关系
Context意为上下文,是一个应用程序环境的入口。在开发中我们经常会用到Context比如:
- 1、使用Context调用方法,启动Activity、访问资源、调用系统服务等
- 2、将Context作为参数传入方法中,弹出Toast、创建dialog等
Activity、Service、Application都间接的继承自Context,那么一个应用程序进程中有多少个Context呢? 我们看一下Context的关系树就知道了。
由此可见
Context的数量=Activity的数量+Service的数量+1(application的数量)
这个类图看着是不是很眼熟呢?不错,这就是装饰者模式的类图,Context的设计就是基于装饰者模式的。(以后会单独解析装饰者模式)
再来看下源码中的类关系
- Context:是一个抽象类,主要提供一些通用的抽象方法
- ContextImpl:Context的具体实现类,在Android的源码中大量使用接口和抽象类,我们在刚开始阅读源码的时候,经常会找到顶层接口之后,就不知道下一步找谁了,遇到这种情况,一般就是要找接口的实现类,实现类中如何实现才是寻找答案的关键,通常这些实现类都是xxxxImpl.java以impl结尾的,这也算是阅读源码的一个小技巧吧。
- ContextWrapper:Context的包装类,内部持有一个ContextImpl的实例对象mBase,对Context的操作,最终都会引申到mBase中。
- ContextThemeWrapper:该类内部包含主题相关的方法。而Service不需要主题,所以Service继承自ContextWrapper,这也是Activity和Service的Context的主要区别点。
不同Context使用上的区别
为什么会有这些区别以及随意使用context会造成的问题
1:启动Activity:
如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错getApplication().startActivity(new Intent(this, NewActivity.class));
, 这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈
contextImpl的startActivity()方法中options == null
这是因为在源码中有这样一段话(基于 SDK 29 源码)
在Android7.0-8.0 的中加入了options != null的判断,options是Bundle类型,正常启动Activity如果不传Bundle的时候,就不会抛出异常提醒。后来在Android9.0上又改成options == null了。
下图是sdk基于26时的源码
2.创建dialog
使用application的context创建Dialog dialog = new Dialog(getApplicationContext());
会报这样的错误
这又是为什么呢? 首先看一下Activity的attach()方法:
//activity
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Applicat
ion application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
...
//创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
//设置windowMananger 其中第二个参数为token
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
}
activity被创建后,会创建PhoneWindow和并设置windowManager绑定token。 这里的 IBinder 对象 mToken 很重要。它是一个 Binder 对象,可以在 app 进程,system_server 进程之间进行传递。和我们通常所说的 Token 一样,这里也可以把它看做是一种特殊的令牌,用来标识 Window ,在对 Window 进行视图操作的时候就可以做一些校验工作。
所以,Activity 对应的 Window/WMS 都是持有这个 mToken 的。结合之前 Application 创建 Dialog 的报错信息,我们可以大胆猜测 Application Context 创建 Dialog 的过程中,并没有实例化类似的 token。
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
//获取context中的windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//创建window 并设置windowmanager
final Window w = new PhoneWindow(mContext);
w.setWindowManager(mWindowManager, null, null);
}
再来看看dialog的构造函数,从传入的context中拿到windowManager并创建新的window。我们知道dialog本质上也是一个window,显示的时候也是要通过WindowManager的addView()方式显示的。 看一下dialog的show()方法
最后
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!