浅探 Android 中的 Context

文章目录
  1. 1. 简介 Context
  2. 2. 探索 Context
  3. 3. 参考文献

Nobody can go back and start a new beginning, but anyone can start today and make a new ending.

Context 在我们的 Android 开发中,真是无处不在:加载资源、启动一个新的 Activity、获取系统服务和创建 View 操作等。那么问题来了,Context 究竟是什么,具体怎么使用,又有哪些注意事项呢,作为新年第一篇博客,这次来探索 Android 中的 Context。

简介 Context

Context,是一个应用程序环境中全局信息的接口。它是一个抽象类,实现由 Android 系统提供。它允许访问特定于应用程序的资源和类,以及对应程序级操作如启动活动、广播和接收意图等的上调。

以上来自官网,总结来说,分为以下三点:

i. Context 描述的是一个应用程序环境的信息,即上下文;

ii. Context 类是一个抽象类,Android 为该抽象类提供了具体的实现类 (ContextImpl);

iii. 通过 Context 能获取应用程序的资源和类,也包括一些应用级别的操作,如启动一个 Activity、发送广播和接收 Intent 信息等。

上面的描述还是有些抽象,不妨这么形象地阐述下,也许不太恰当:

Android 应用程序如同一场篮球比赛,传球、抢篮板和得分等(类比于启动一个 Activity、发送广播等一切发生在应用程序里的行为),都得发生在 Context 这块场地上,没有 Context 这块场地,精彩的比赛就无法进行。

探索 Context

Context 的类型

由前文的简单介绍,我们已经知道,虽然 Android 应用程序是由 Java 语言编写的,但是 Android 程序不像 Java 程序那样,创建一个类,写个 main() 方法就能运行了,还要配套有一个完整的工程环境。这样,Activity、Service 和 BroadcastReceiver 等系统组件才能正常工作。Context 可以说是 Android 程序中维持各组件正常运行的一个核心功能类。

简单的结构图如下:

Context 有两个子类,分别为 ContextWrapper 和 ContextImpl。

Context 部分源码:

1
2
3
4
5
6
7
8
9
public abstract class Context {  
...
public abstract Object getSystemService(String name); // 获得系统级服务
public abstract void startActivity(Intent intent); //通过 Intent 启动 Activity
public abstract ComponentName startService(Intent service); //启动 Service
// 根据文件名得到 SharedPreferences 对象
public abstract SharedPreferences getSharedPreferences(String name, int mode);
...
}

ContextWrapper 为 Context 的包装类,构造函数包含了一个真正的 Context 引用,即 ContextImpl 对象。部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ContextWrapper extends Context {  
// 该属性指向一个 ContextImpl 实例,一般在创建 Application、Service、Activity 时赋值
Context mBase;

// 创建 Application、Service、Activity 时,会调用该方法给 mBase 属性赋值
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent); //调用 mBase 实例方法
}
}

ContextImpl 为 Context 的实现类,其大部分功能都是直接调用其属性 mPackageInfo 去完成,部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** 
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/

class ContextImpl extends Context{
// 所有 Application 程序公用一个 mPackageInfo对象
/*package*/
ActivityThread.PackageInfo mPackageInfo;

@Override
public Object getSystemService(String name){
...
else if (ACTIVITY_SERVICE.equals(name)) {
return getActivityManager();
}
else if (INPUT_METHOD_SERVICE.equals(name)) {
return InputMethodManager.getInstance(this);
}
}

@Override
public void startActivity(Intent intent) {
...
// 开始启动一个 Activity
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);
}
}

而 ContextWrapper 又有三个子类,ContextThemeWrapper、Service 和 Application。该类内部包含主 (Theme) 相关的接口,是 android:theme 属性指定的。Activity 需要主题,Service 不需要,故 Activity 继承该 ContextThemeWrapper 类。部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ContextThemeWrapper extends ContextWrapper {  
private Context mBase;

public ContextThemeWrapper(Context base, int themeres) {
super(base);
mBase = base;
mThemeResource = themeres;
}

@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
mBase = newBase;
}
}

总结一下,Context 共有三种类型,分为 Application、Activity 和 Service,其具体 Context 的功能由 ContextImpl 实现。绝大多数场景下,Activity、Service 和 Application 三种类型的 Context 可以通用。不过,也有些例外情况,比如启动一个 Activity 或 弹出一个 Dialog。Android 中,不允许 Activity 或 Dialog 凭空出现,一个 Activity 的启动须建立在另一个 Activity 的基础上,以此形成返回栈。对于 Dialog,必须在 Activity 上弹出 (系统 Alert 类型的 Dialog 除外),这种情况下,必须使用 Activity 类型的 Context。

Context 的数量

Context 中共有 Application、Activity 和 Service 三种类型,故一个应用程序中 Context 数量的计算公式如下:

1
N(Context) = N(Activity) + N(Service) + 1

N 代表数量,1 即 Application 的数量,一个 Android 应用程序只有一个 Application。上述公式意义即 Context 的数量为应用程序中多个 Activity 的数量加上多个 Service 的数量,再加上 1。

注意:常说的四大组件中,BroadcastReceiver 和 ContentProvider 并不是 Context 的子类,它们持有的 Context 都是其他地方传过去的,并不计入 Context 总数。

Context 的作用域

总结如下:

注意到 NO 右上角有数字,其实从能力上来说是 YES 的,关于标明 NO 的解释如下:

数字1:应用可以从这里启动一个 Activity,但要求创建一个新的任务。这样,或许适用于特殊的使用实例,但是会在应用程序里创建非标准的返回栈行为,通常不推荐。

数字2:用在这里是可以的,但会在运行的系统上加载默认的主题,而不是加载应用程序里自定义的主题。

数字3:在 Android 4.2+ 版本上,Receiver 为空时允许,用来获取粘性广播的当前值。

重点看 Activity 和 Application,注意以下几点:

i. 和 UI 相关的方法不建议或不能使用 Application,并且,表格中前三个动作基本不可能出现于 Application 中。

ii. 凡是和 UI 相关的,都应该使用 Activity 作为 Context 来处理;其他一些动作,Service,Activity 和 Application 等实例都可以。

Context 的使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TextView text = new TextView(getContext());

Adapter adapter = new Adapter(getApplicationContext(), ...);

AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);

getApplicationContext().getSharedPreferences(name, mode);

getApplicationContext().getContentResolver().query(uri, ...);

getContext().getResources().getDisplayMetrics().widthPixels * 5 / 6;

getContext().startActivity(intent);

getContext().startService(intent);

getContext().sendBroadcast(intent);

Context 的使用注意事项

I.需要 Context 的时候,若是在 Activity 中,大多直接传递个 this;若是在匿名内部类中,由于 this 不能用,需要写 XXActivity.this,有些人会直接使用 getApplicationContext()。实际上,XXActivity 和 getApplicationContext() 返回的肯定不是同一个对象,前者为当前 Activity 的实例,后者是应用程序 Application 的实例。区别很明显,各自的使用场景不同,乱用会带来问题。

II.注意使用 Context 不当引起的内存泄漏。举例如下:

不当的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private static Singleton mInstance;
private Context mContext;

private Singleton(Context context) {
this.mContext = context;
}

public static Singleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new Singleton(context);
}
return mInstance;
}
}

这是一个非线程安全的单例模式,mInstance 作为静态对象,其生命周期比普通的对象长。其中包含 Activity,假设 BBActivity 调用 getInstance() 方法获得 mInstance 对象,传入 this,这时,常驻内存的 Singleton 保存了传入的 BBActivity 对象,并且一直持有。即使 Activity 被销毁掉,由于其引用还存在于 Singleton 中,不会被 GC 掉,如此便导致内存泄漏。

View 持有 Activity 的引用

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends Activity {
private static Drawable mDrawable;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView image = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
image.setImageDrawable(mDrawable);
}
}

将一个静态的 Drawable 对象当 ImageView,设置这个 Drawable 时,ImageView 保存了 mDrawable 的引用,而 ImageView 传入的 this 是 MainActivity 的 mContext,由于 static 修饰的 mDrawable 是常驻内存的,MainActivity 是它的间接引用,当 MainActivity 被销毁时,无法被 GC 掉,故造成内存泄漏。

III. 使用 Context 的正确姿势

一般使用 Context 造成的内存泄漏,几乎都是当 Context 销毁时,由于其被引用导致销毁失败。总结得出使用 Context 的正确方法如下:

(1) 由于 Application 的 Context 对象是随着应用程序进程存在的,故在 Application 的 Context 适用的情况下,对于生命周期长的对象,优先使用 Application 的 Context。

(2) 不要让生命周期比 Activity 长的对象持有 Activity 的饮用。

(3) 最好不要在 Activity 中使用非静态内部类,由于非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

至此,浅探 Android 中的 Context 到此结束。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://blog.csdn.net/guolin_blog/article/details/47028975

2.http://www.jianshu.com/p/94e0f9ab3f1d

3.http://blog.csdn.net/lmj623565791/article/details/40481055