Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工作环境,在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new来创建实例,而是要有它们各自的上下文环境,也就是我们这里讨论的context。
Context是维护Android程序中各组件能够正常工作的一个核心功能类。
Context到底是什么?
Context,即上下文
其在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。
源码中的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的:Context提供了关于应用环境全局信息的接口。
它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了这个抽象类的具体实现类;通过它,我们可以虎丘应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。
Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和ContextWrapper。启动ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用。
一句话总结:
Context的两个子类分工明确,其中ContextImplement是Context的具体实现类,ContextWrapper是Context的包装类。
“Activity、Application、Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper)”,但它们初始化的过程都会创建ContextImplement对象,由ContextImpl实现Context中的方法。
一个应用程序有几个Context
从上面的关系我们可以知道,在应用程序中Context的具体实现子类就是:Activity、Service和Application,那么Context数量=Activity数量+Service数量+1,。
当然如果你足够细心,可能会有疑惑:我们常说的四大组件,这里怎么只有Activity和Service持有Context,那BroadcastReceiver和ContentProvider呢?
BroadcastReceiver和ContentProvider并不是Context的子类,它们所持有的Context都是其他地方传过来的,所以并不计入Context总数。
Context能干啥?
Context到底可以实现哪些功能呢?
这个就实在太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到Context。
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);如何获取Context?
通常我们想要获取Context对象,主要有一下四种方法
View.getContext。返回当前View对象的Context对象,通常是当前正在展示的Activity对象。Activity.getApplication。获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程ContextContextWrapper.getBaseContext()。用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法。这个方法在实际开发中使用并不多,也不建议使用Activity.this。返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用Application.Context也可以。
getApplication()和getApplicationContext()区别
实际上这两个方法在作用域上有比较大的区别
getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只能在Activity和Service中才能调用到。
那么也许在绝大多数情况下我们都是在Activity和Service中使用Application的,但是如果在一些其他地方,比如BroadcastReceiver总也要获取Application的实例,这时可以借助getApplicationContext()方法。
public class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Contextcontext,Intentintent){
Application myApp= (Application)context.getApplicationContext();
}
}Context引起的内存泄漏
- 错误的单例模式,即线程不安全的懒汉式。该长生命周期的单例被短生命周期的实例引用容易造成内存泄漏;
- 非静态方法持有Context引用,就会导致Context对应的Activity或Service无法被GC系统回收
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁时,却因为被引用导致销毁失败,而Application的Context对可以接为随着进程存在的,所以我们总结出使用Context的正确姿势:
- 当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context
- 不要让生命周期长于Activity的对象持有到Activity的引用
- 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。