【Android】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的使用会有一些规则的限制。具体的限制方式我们可以参考下面这张表:

img

如何获取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中使用非静态的内部类。因为非静态的内部类会隐式持有外部类的引用。如果要使用静态内部类,使用弱引用来持有外部类的实例。
N0tExpectErr0r

N0tExpectErr0r

一名热爱代码的 Android 开发者

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>