文章目录
- 介绍
- Context 数量
- 如何获取
- Context引起的内存泄露
- 正确使用Context
介绍
Android 应用都是使用 Java 语言来编写的,思考一下,一个 Android 程序和一个 Java 程序,他们最大的区别在哪里?其实简单点分析,Android 程序不像 Java 程序一样,随便创建一个类,写个main()
方法就能跑了,而是要有一个完整的 Android 工程环境,在这个环境下,我们有像 Activity、Service、BroadcastReceiver 等系统组件,而这些组件并不是像一个普通的 Java 对象 new 一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context 是维持 Android 程序中各组件能够正常工作的一个核心功能类。
Context的继承结构:
我们可以看到 Activity,Service、Application 都是 Context 的子类,所以用到 Context 的时候,我们就可以在类中写this
我们观察 Context.class,这是个抽象类,里边定义了很多常量,和抽象方法。ContextWrapper 是上下文功能的封装类,而 ContextImpl 则是上下文功能的实现类。
Context 能实现很多功能,例如:弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等。我们可以把 Context 看成是一个工具,因为 Activity 继承了 Context 里边的抽象方法,所以可以直接使用,省去了我们自己实现。
不过有几种场景比较特殊,比如启动 Activity,还有弹出 Dialog。出于安全原因的考虑,Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须要建立在另一个 Activity 的基础之上,也就是以此形成的返回栈。而 Dialog 则必须在一个 Activity 上面弹出(除非是 System Alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。
Context 数量
Context 一共有 Application、Activity 和 Service 三种类型,因此一个应用程序中 Context 数量的计算公式就可以这样写:
Context数量 = Activity数量 + Service数量 + 1
上面的 1 代表着 Application 的数量,因为一个应用程序中可以有多个 Activity 和多个 Service,但是只能有一个 Application。
如何获取
有以下四种方法:
1、View.getContext
返回当前 View 对象的 Context 对象,通常是当前正在展示的 Activity 对象。
2、Activity.getApplicationContext
获取当前 Activity 所在的(应用)进程的 Context 对象,通常我们使用 Context 对象时,要优先考虑这个全局的进程 Context。
3、ContextWrapper.getBaseContext()
用来获取一个 ContextWrappe r进行装饰之前的 Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4、Activity.this
返回当前的Activity实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用 ApplicationContext 也可以。
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,假如 Activity A 去 getInstance 获得i nstance 对象,传入 this,常驻内存的 Singleton 保存了你传入的 Activity A 对象,并一直持有,即使 Activity 被销毁掉,但因为它的引用还存在于一个 Singleton 中,就不可能被 GC掉,这样就导致了内存泄漏。
2、View 持有 Activity 引用
public class MainActivity extends Activity {
private static Drawable mDrawable;
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
有一个静态的 Drawable 对象当 ImageView 设置这个 Drawable 时,ImageView 保存了 mDrawable 的引用,而 ImageView 传入的 this 是 MainActivity 的 mContext,因为被 static 修饰的 mDrawable 是常驻内存的,MainActivity 是它的间接引用, MainActivity 被销毁时,也不能被 GC 掉,所以造成内存泄漏。
正确使用Context
一般 Context 造成的内存泄漏,几乎都是当 Context 销毁的时候,却因为被引用导致销毁失败, 而Application 的 Context 对象可以理解为随着进程存在的,所以我们总结出使用 Context 的正确姿势:
1、当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的 Context。
2、不要让生命周期长于 Activity 的对象持有到 Activity 的引用。
3:尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
参考:
Android Context完全解析,你所不知道的Context的各种细节Context都没弄明白,还怎么做Android开发?