Android中的Context浅析

Context是Android中最常见的一个元素了,本文主要描述了Context的一些基础知识。

基本信息

  1. 描述应用程序环境信息,即上下文
  2. 抽象类,具体实现类是ContextImpl
  3. Context总数=Activity总数+Service总数+1(Apllication)
  4. 可以通过Context获取系统的资源和类,也可以实现一些应用级别操作,例如:启动一个Activity,发送广播,接收Intent信息等
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);
...
}

Context类型

  1. Application:它是应用程序的一个单例,它可以通过Activity或Service的getApplication()方法获取,也可以在任何继承Context类的的对象中通过getApplicationContext()来获取。不管它是怎么获取的,这些方法返回的都是App中同一个实例。
  2. Activity/Service:它们继承自ContextWrapper,ContextWrapper实现了Context同样的API,但是隐藏了内部Context对象的方法调用,Context也是ContextWrapper的父类。每当系统创建一个Activity或Service对象的时候,它也为它们创建了新的ContextWrapper对象。每个Activity或Service对象,包括他们对应的context对象都是唯一的。
  3. BroadcastReceiver:它并不拥有Context对象,但是系统在一个新的广播到来的时候通过onReceiver()方法传入一个Context对象,这是一个ReceiverRestrictedContext,它的两个主要方法,registerReceiver()和bindService()都被禁用了。每一次receiver处理一个广播,传入的Context对象都是一个新的实例。
  4. ContentProvider:同样也不是一个Context对象,但是在创建的时候会通过getContext()方法传入一个context对象。如果ContentProvider是在本地调用的话(在同一个进程中),那么这会返回一个应用单例。然而,如果是在不同的进程中调用的话,它会新建一个context对象表示当前provider运行的进程。

Context的作用

Application Activity Service ContentProvider BroadcastReceiver
Show a Dialog No Yes No No No
Start an Activity No Yes No No No
Layout Inflation No Yes No No No
Start a Service Yes Yes Yes Yes Yes
Bind to a Service Yes Yes Yes Yes NO
Send a Broadcast Yes Yes Yes Yes Yes
Register BroadcastReceiver Yes Yes Yes Yes NO
Load Resource Values Yes Yes Yes Yes Yes

类结构

context

  1. Context是一个抽象类。
  2. ContextImpl实现了具体的功能,但大部分工作都是其中的变量mPackageInfo完成的。
  3. ContextWrapper封装了一下,里面有一个变量Context mBase;所有的方法都是由mBase执行。再创建Application,Activity和Service的时候,调用attachBaseContext()方法给mBase赋值。
  4. ContextThemeWrapper里面有一个主题变量,只有Activity需要。

自定义Application

现在基本上每一个Android应用都会有一个自定义的Application,用来保存一些全局变量,数据库初始化啊之类的。
这里介绍一种常见的误用Application的情况,就是将Application定义为一种普通的单例工具类,代码如下:

1
2
3
4
5
6
7
8
9
10
public class MyApplication extends Application {
private static MyApplication app;

public static MyApplication getInstance() {
if (app == null) {
app = new MyApplication();
}
return app;
}
}

这种写法是有问题的,因为Application属于系统组件,而系统组件的创建是由系统去控制的,这里new了一个MyApplication对象,就将其看作了一个普通的java对象,而失去了其Context的能力。将这一个Application当做Context去使用,会爆发无止境的NullPointer问题。
标准写法如下,因为Application全局只有一个,本身已经是单例了,不需要用单例模式去做多重保护。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyApplication extends Application {
private static MyApplication app;

public static MyApplication getInstance() {
return app;
}

@Override
public void onCreate() {
super.onCreate();
app = this;
}
}

有了自定义的Application,我们就可以在程序中别的地方去得到我们自己的Application了:

1
2
3
4
5
6
7
8
9
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication myApp = (MyApplication) getApplication();
Log.d("TAG", "getApplication is " + myApp);
}
}

这边就获取了我们的MyApplication实例了。
Android还为我们提供了另外一个获取Application的方法——getApplicationContext()。
这两个方法看似有些关联,又有些区别。运行如下代码:

1
2
3
4
5
6
7
8
9
10
11
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication myApp = (MyApplication) getApplication();
Log.d("TAG", "getApplication is " + myApp);
Context appContext = getApplicationContext();
Log.d("TAG", "getApplicationContext is " + appContext);
}
}

运行出来的结果可以看到,两个方法得到的对象是完全一样的,内存地址都一样。
这是因为之前说过Application就是一个Context。
那这两个方法的区别又是什么呢?
原来getApplication()只能在Activity和Service中才能调用,其他的一些场景,比如在BroadcastReceiver想查看Context,就只能借助getApplicationContext()了。

注意点

  1. 生命周期长的对象,要引用Application的Context,而不是Activity的Context,不然容易内存泄漏
  2. 用Application唤起LayoutInflator会忽略样式和主题,因为,Activity 才是系统配置文件中的唯一持有主题和样式的Context,其他所有的Context都会使用系统默认的主题来渲染你的xml来生成View,最终就导致了界面并不是你想要的。