Android中的Context

405 阅读5分钟

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对象,主要有一下四种方法

  1. View.getContext返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
  2. Activity.getApplication获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context
  3. ContextWrapper.getBaseContext()。用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法。这个方法在实际开发中使用并不多,也不建议使用
  4. 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的正确姿势:

  1. Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context
  2. 不要让生命周期长于Activity对象持有到Activity的引用
  3. 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有