一、Context概念理解
Google解释如下:
Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
Context是一个访问进程环境全局信息的接口,通过它可以访问进程特定的资源和类,也可以调用进程级别的方法。
上图列出了Context声明的一些主要方法:从中我们可以看出它既能获取进程id、包信息和权限等进程层面的信息;也能访问如Assets、Cache、Sp、Resource和Database甚至ContentResolver等资源文件;还能启动Activity、绑定Service、注册Receiver和发送Broadcast等。
那么为什么要设计Context呢?它的意义是什么呢?Context的字面意思是“上下文”。谁的“上下文”呢?是进程的“上下文”,也是我们开发App应用的“上下文”。Context声明了一个App最基本的权利。我们创建一个空项目,即使什么业务逻辑也不写,我们依然可以获取进程id,可以访问Resource,也可以启动一个Activity或者Service,这就是App的基本权利;但是我们获取进程id干什么,Resource里有什么资源,启动一个Activity要展示怎样的界面,绑定一个Service又想做哪些复杂的算法?这些都是我们基于基本权利之上的业务逻辑!当然,同时Context也规定了App的边界,App无法向系统要求声明之外的过分要求,比如从应用层面,我们不能修改UserId。简而言之,Context可以理解为系统和App之间的一份权利声明!
二、Context架构设计
Context 是一个纯抽象类,规范了App和系统之间的交互。ContextImpl真正实现Context所有函数。ContextWrapper则只是对Context做了简单的封装,其内部所有继承函数的实现都是由ContextImpl实例代理完成的。正因如此,所有ContextWrapper实例被系统创建时都会通过attachBaseContext( )方法将一个ContextImpl实例赋值给其全局变量mBase。
这样的分层设计在面向对象程序设计里非常普遍,从设计模式角度来讲,这是一个标准的代理模式!这样的设计可以让抽象层可以更加专注于问题领域的分析和设计,而不必纠缠于具体实现,职责清晰,扩展性强。
系统为App提供了三种主要的Context组件:Application,Service,Activity。ContextThemeWrapper主要包含了与主题相关的接口,只有Activity才需要主题。拿到这三种组件中任意一个就可以实现几乎所有Context声明的权利,在日常开发中我们几乎没有关注过mContext实例具体是哪种类型。但是在某些特殊情况下,可能会因为Context类型使用不当造成RuntimeException异常,后面会做详细解释。那么一个进程中到底有多少个Context呢?一般我们只考虑Application、Service和Activity这三种类型,因此:
$$Context数量=Activity数量+Service数量+1$$
Context数量是实例化的Activity和Service数量之和再加一个Application,而不是有些人误认为的一个App只有一个“上下文”。另外一个需要注意的是,Android中的Context对象并不是像Java中那样随意new出来的,而是由系统在需要时创建的,具体代码在ActivityThread类中。
三、Context实战应用
我们已经知道Context是App要求系统兑现权利的法宝,而且一个App进程中可能有很多这样的法宝,但是有些法宝却并非在所有场景中总能显灵。因为,出于Code规范或安全因素等,系统限制了某些类型Context履行某些功能。毕竟,权力是系统给的,它也有责任防止滥用而造成隐患!比如,如果想在Service里启动一个Activity,就会造成如下异常:
throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?");
因为启动Service是不会创建任务栈的,那么从Service中启动的Activity就无栈可存。如果强行加上FLAG_ACTIVITY_NEW_TASK新建一个栈,也不是标准的方式,不建议这样设计。
下图给出了不同类型Context具体使用范围和限制:
- 总体上Context的操作都是允许的
- 除Activity外其他类型Context不能直接startActivity( ),需要追加FLAG_ACTIVITY_NEW_TASK
- BroadcastReceiver中不允许绑定Service,这是因为静态注册的Receiver,系统返回的Context类型是ReceiverRestrictedContext,查看源码可以看到其中重写了bindService( )方法并直接抛出ReceiverCallNotAllowedException异常;动态注册的Receiver返回的是注册时的Context,另行讨论。
- BroadcastReceiver中允许通过registerReceiver(null, filter)方法来获取粘性广播,但不允许注册常驻的Receiver。否则,同样会收到ReceiverCallNotAllowedException异常。