Context 详谈

Activity 能否使用如下写法:Activity mActivity = new Activity()
Android 的应用程序开发采用 Java 语言,Activity 本质上也是一个对象,但是,Android 程序不像 Java 程序一样,随便创建一个类,写个 main() 方法就能运行。Android 应用模型是基于组件的应用设计模式,组件的运行要有一个完整的 Android 工程环境,在这个环境下,Activity、Service 等系统组件才能够正常工作,而这些组件并不能采用普通的 Java 对象创建方式,而是要有它们各自的上下文环境,也就是这里所讨论的 Context。简言之,Context 是维持 Android 程序中,各组件能够正常工作的一个核心功能类。

一、概述

1、理解 Context
Context(开发中译为:“上下文”),在程序中,可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。
(比如:微信聊天,此时的“环境”是指:聊天的界面以及相关的数据请求与传输。Context 在加载资源、启动 Activity、获取系统服务、创建View 等操作中,都需要参与。)

Context 到底是什么?一个 Activity 就是一个 Context,一个 Service 也是一个 Context。Android 程序员把“场景”抽象为 Context 类,认为用户和操作系统的每一次交互都是一个场景,比如:打电话、发短信,这些都是一个有界面的场景。还有一些没有界面的场景,比如:后台运行的服务(Service)。
一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。

一个 Android 应用程序,可以理解为一部电影,Activity、Service、Broadcast Receiver、Content Provider 这四大组件就好比是这部戏里的四个主角。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄,他们必须通过镜头(Context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在 Context 环境下(摄像机镜头)。而 Button、TextView、LinearLayout 这些控件,就好比是这部戏里的配角,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以 new 一个对象),但是他们也必须要面对镜头(工作在 Context 环境下),所以 Button mButton = new Button(Context) 是可以的。

2、源码中的 Context

/**
 * 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.
 */
public abstract class Context {
    /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the same user ID).
     * @see #MODE_WORLD_READABLE
     * @see #MODE_WORLD_WRITEABLE
     */
    public static final int MODE_PRIVATE = 0x0000;

    public static final int MODE_WORLD_WRITEABLE = 0x0002;

    public static final int MODE_APPEND = 0x8000;

    public static final int MODE_MULTI_PROCESS = 0x0004;
    ...
}

源码中的解释为:Context 提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。
就是说,它描述一个应用程序环境的信息(即:上下文),是一个抽象类,Android 提供了该抽象类的具体实现类,通过它,我们可以获取应用程序的资源和类(包括:应用级别操作,如:启动Activity,发广播,接受 Intent 等)。

Context 的子类关系图如下:
在这里插入图片描述
Context 类本身是一个纯抽象类,有两个具体的实现子类:ContextImpl、ContextWrapper。
(1)ContextWrapper 类,这只是一个包装而已,ContextWrapper 构造函数中必须包含一个真正的 Context 引用,同时 ContextWrapper 中提供了attachBaseContext() 用于给 ContextWrapper 对象中指定真正的 Context 对象,调用 ContextWrapper() 方法都会被转向其所包含的真正的 Context 对象。
(2)ContextThemeWrapper类,其内部包含了与主题(Theme)相关的接口,这里所说的主题,就是指:在 AndroidManifest.xml 中通过android:theme 为 Application 元素或者 Activity 元素指定的主题。当然,只有 Activity 才需要主题,Service 是不需要主题的,因为 Service 是没有界面的后台场景,所以 Service 直接继承于 ContextWrapper,Application 同理。
(3)ContextImpl 类,真正实现了 Context 中的所有函数,应用程序中所调用的各种 Context 类的方法,其实现均来自于该类。

总结:Context 的两个子类分工明确,其中 ContextImpl 是 Context 的具体实现类,ContextWrapper 是 Context 的包装类。
Activity、Application、Service 虽然都继承自 ContextWrapper(其中,Activity 继承自 ContextWrapper 的子类 ContextThemeWrapper),但它们初始化的过程中都会创建 ContextImpl 对象,由 ContextImpl 实现 Context 中的方法。

3、一个应用程序中 Context 的数量
在应用程序中,Context 的具体实现子类就是:Activity、Service、Application。那么,Context 数量 = Activity 数量 + Service 数量 + 1。
为什么四大组件中,这里只有 Activity、Service 持有 Context,而 Broadcast Receiver、Content Provider 呢?
其实,Broadcast Receiver、Content Provider 并不是 Context 的子类,他们所持有的 Context 都是其它地方传过去的,所以并不计入Context 总数。

4、Context 的作用
(1)Context 可以实现的功能:
弹出 Toast、启动 Activity、启动 Service、发送广播、操作数据库等。

TextView tv = new TextView(getContext());
ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
getApplicationContext().getSharedPreferences(name, mode);
getApplicationContext().getContentResolver().query(uri, ...);
getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
getContext().startActivity(intent);
getContext().startService(intent);
getContext().sendBroadcast(intent);
...

(2)Context 的作用域:
由于 Context 的具体实例,是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service、Application 这三种类型的 Context 都是可以通用的。不过有几种场景比较特殊,比如:启动 Activity、弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。
在这里插入图片描述
从上图可以发现:Activity 所持有 Context 的作用域最广。因为 Activity 继承自 ContextThemeWrapper,而 Application 和 Service 继承自 ContextWrapper,很显然 ContextThemeWrapper 在 ContextWrapper 的基础上又做了一些操作,使得 Activity 变得更强大。

注意,上图中 Application 和 Service 不推荐的两种使用情况:
<1> 若用 Application Context 去启动一个 LaunchMode 为 standard 的 Activity 时,会报以下错误:
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
原因:非 Activity 类型的 Context 并没有所谓的任务栈,所以待启动的 Activity 就找不到栈了。
解决方法:为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动时,就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。( 所有这种用 Application 启动 Activity 的方式都不推荐使用,Service 同 Application。)

<2> 在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。

总结:凡是跟 UI 相关的操作 ,都应该使用 Activity 作为 Context 来处理,其他的一些操作,Service、Activity、Application等实例都可以,此外要注意 Context 引用的持有,防止内存泄漏。

5、获取 Context
(1)通常想要获取 Context 对象,主要有以下四种方法:
<1> View.getContext:返回当前 View 对象的 Context 对象,通常是当前正在展示的 Activity 对象。
<2> Activity.getApplicationContext:获取当前 Activity 所在的应用进程的 Context 对象,通常使用 Context 对象时,要优先考虑这个全局进程的 Context。
<3> ContextWrapper.getBaseContext():用来获取一个 ContextWrapper 进行装饰之前的 Context,该方法在实际开发中使用不多,不建议使用。
<4> Activity.this:返回当前的 Activity 实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用ApplicationContext 也可以。

(2)getApplication() 和 getApplicationContext() 的区别:
在这里插入图片描述
通过上面的代码,打印得出两者的内存地址都是相同的,因此这两种方法得到的是同一个对象。
说明:Application 本身就是一个 Context,所以通过 getApplicationContext() 得到的结果就是 Application 本身的实例。
但是,这两个方法在作用域上有比较大的区别:getApplication() 方法的语义性非常强,一看就知道是用来获取 Application 实例的,但是这个方法只有在 Activity 和 Service 中才能调用的到。但是在其它场景下,比如:BroadcastReceiver 中也想获得 Application 的实例,这时就可以借助 getApplicationContext() 方法了,如下所示:

public class MyReceiver extends BroadcastReceiver{
	@Override
	public void onReceive(Context context, Intent intent){
		Application myApp = (Application)context.getApplicationContext();
	}
}

6、Context 引起的内存泄漏
Context 并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面为两种错误的引用方式:
(1)错误的单例模式:
在这里插入图片描述
这是一个非线程安全的单例模式,instance 作为静态对象,其生命周期要长于普通的对象,其中也包含 Activity,假如 Activity A 去getInstance 获得 instance 对象,传入 this,常驻内存的 Singleton 保存了你传入的 Activity A 对象,并一直持有,即使 Activity 被销毁掉,但因为它的引用还存在于一个 Singleton 中,就不可能被 GC 掉,这样就导致了内存泄漏。

(2)View 持有 Activity 引用:
在这里插入图片描述
有一个静态的 Drawable 对象,当 ImageView 设置这个 Drawable 时,ImageView 保存了 mDrawable 的引用,而 ImageView 传入的 his是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

7、正确使用 Context
一般 Context 造成的内存泄漏,几乎都是当 Context 被销毁时,却因为被引用,导致销毁失败,而 Application 的 Context 对象可以理解为随着进程存在的。Context 的正确使用方法:
(1)当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的 Context。
(2)不要让生命周期长于 Activity 的对象持有到 Activity 的引用。
(3)尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,若使用了静态内部类,则需要将外部实例引用作为弱引用持有。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值