关于Context的理解与总结——什么是Context?
作为一个Android开发者,我们在Android开发中经常会使用到Context这个类。它在加载资源、启动Activity、获取系统服务、创建View等活动中都需要参与。
但Context到底是什么,我就很少去关注了…那么我们该如何理解去Context呢?它到底是什么呢?
什么是Context
翻译角度
Context翻译为中文,有:上下文、背景、环境等翻译,我们可以把Context理解成一种环境。Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境 。
因此Android不像普通Java程序一样,随便创建一个类,写上main方法就可以运行,每个组件需要有自己工作的环境,才能正常运行。而Context,就是我们这里所说的环境。
比如,当我们需要创建一个Button时,也需要给它提供一个环境:Button button = new Button(context);
一个Activity可以是一个Context,一个Service也可以是一个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 {
...
}
我们可以看到Context源码中的注释,里面说到Context提供了关于应用环境的全局信息的接口,它是抽象类,调用由Android系统来进行。它可以获取有应用特征的资源和类,可以执行一些应用级别的操作(如启动Activity,发送广播,接收Intent等等)
Context是一个抽象类,它有两个实现的子类——ContextImpl 和 ContextWrapper。
ContextWrapper类仅仅是一个包装类,它的构造函数需要传递一个真正的Context的引用。并且它提供了attachBaseContext() 方法来指定真正的Context。调用ContextWrapper最终都会调用它包含的真正的Context对象的方法。
ContextImpl才是真正的实现类,它实现了Context中的所有方法。平时我们调用的所有Context的方法的实现均来自这个类。
Activity、Application、Service三个类均继承自ContextWrapper,而在具体初始化过程中,则会构造ContextImpl对象,来实现Context中的方法。
Context的作用域
Context在我们日常开发中使用的非常广泛,但是我们并不是拿到了Context就可以为所欲为。Context的使用会有一些规则的限制。具体的限制方式我们可以参考下面这张表:
如何获取Context
要获取一个Context,有下面的四种方法:
- **View.getContext():**返回当前View对象的Context对象,通常是正在展示的Activity对象。
- **Activity.getApplicationContext():**获取当前Activity所在的Application的Context对象。(通常我们使用Context对象时,要优先考虑这个全局的进程Context)
- **ContextWrapper.getBaseContext()**要获取一个ContextWrapper装饰前的Context,可以使用这个方法。
- **Activity.this():**返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象。但是Toast实际上使用ApplicationContext也可以。
Context引起的内存泄漏
Context使用的时候要注意使用方式,否则很可能造成内存泄漏
例子1
比如下面这种错误的单例:
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
上面是一种线程不安全的单例,instance是它的静态对象,生命周期比普通对象长。假如我们使用Activity去调用getInstance获取instance,则Singleton类保存了Activity的引用,导致Activity被销毁后仍然不能被GC回收。
例子2
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView ivImage = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
ivImage.setImageDrawable(mDrawable);
}
}
在上面这个例子中,Drawable是静态的。调用ImageView的setImageDrawable设置Drawable时,ImageView就会持有这个Drawable的引用。而ImageView同时还持有Activity的引用,并且它持有的Drawable是常驻内存的,导致MainActivity被销毁时,无法被GC回收。
如何避免
一般Context所导致的内存泄漏,都是由于Context被销毁时,由于它的引用导致无法被回收。但是我们可以使用Application的引用,因为Application的生命周期是随着进程存在的。
因此我们尽量在使用Context的时候用如下的姿势:
- 生命周期长的对象,并且Application的Context可以满足使用时,优先使用Application的Context。
- 不要让声明周期比Activity长的对象持有Activity的引用
- 尽量不要在Activity中使用非静态的内部类。因为非静态的内部类会隐式持有外部类的引用。如果要使用静态内部类,使用弱引用来持有外部类的实例。