Context是Android中最常见的一个元素了,本文主要描述了Context的一些基础知识。
基本信息
- 描述应用程序环境信息,即上下文
- 抽象类,具体实现类是ContextImpl
- Context总数=Activity总数+Service总数+1(Apllication)
- 可以通过Context获取系统的资源和类,也可以实现一些应用级别操作,例如:启动一个Activity,发送广播,接收Intent信息等
1 | public abstract class Context { |
Context类型
- Application:它是应用程序的一个单例,它可以通过Activity或Service的getApplication()方法获取,也可以在任何继承Context类的的对象中通过getApplicationContext()来获取。不管它是怎么获取的,这些方法返回的都是App中同一个实例。
- Activity/Service:它们继承自ContextWrapper,ContextWrapper实现了Context同样的API,但是隐藏了内部Context对象的方法调用,Context也是ContextWrapper的父类。每当系统创建一个Activity或Service对象的时候,它也为它们创建了新的ContextWrapper对象。每个Activity或Service对象,包括他们对应的context对象都是唯一的。
- BroadcastReceiver:它并不拥有Context对象,但是系统在一个新的广播到来的时候通过onReceiver()方法传入一个Context对象,这是一个ReceiverRestrictedContext,它的两个主要方法,registerReceiver()和bindService()都被禁用了。每一次receiver处理一个广播,传入的Context对象都是一个新的实例。
- 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是一个抽象类。
- ContextImpl实现了具体的功能,但大部分工作都是其中的变量mPackageInfo完成的。
- ContextWrapper封装了一下,里面有一个变量Context mBase;所有的方法都是由mBase执行。再创建Application,Activity和Service的时候,调用attachBaseContext()方法给mBase赋值。
- ContextThemeWrapper里面有一个主题变量,只有Activity需要。
自定义Application
现在基本上每一个Android应用都会有一个自定义的Application,用来保存一些全局变量,数据库初始化啊之类的。
这里介绍一种常见的误用Application的情况,就是将Application定义为一种普通的单例工具类,代码如下:1
2
3
4
5
6
7
8
9
10public 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
13public 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
9public 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
11public 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()了。
注意点
- 生命周期长的对象,要引用Application的Context,而不是Activity的Context,不然容易内存泄漏
- 用Application唤起LayoutInflator会忽略样式和主题,因为,Activity 才是系统配置文件中的唯一持有主题和样式的Context,其他所有的Context都会使用系统默认的主题来渲染你的xml来生成View,最终就导致了界面并不是你想要的。