1. Context的代码层级

image.png

2. Context 作用以及作用域

  1. public abstract class Context {
  2. public abstract AssetManager getAssets();
  3. public abstract Resources getResources();
  4. public abstract PackageManager getPackageManager();
  5. public abstract ContentResolver getContentResolver();
  6. public abstract Looper getMainLooper();
  7. public Executor getMainExecutor() {
  8. return new HandlerExecutor(new Handler(getMainLooper()));
  9. }
  10. public abstract Context getApplicationContext();
  11. //...
  12. }

Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImpl和 ContextWrapper。
ContextWrapper类是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时 ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指 定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的 Context对象。
ContextThemeWrapper类,如其名所言,其内部包含了与主题 (Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过 android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity 才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以 Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实 现了Context中的所以函数,应用程序中所调用的各种Context类的方法,其实现均 来自于该类。
Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity, Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper 的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。

2.1 作用

  1. 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等。
  2. 获取系统 / 应用资源,包括 AssetManager、PackageManager、Resources、SystemService 以及 color、string、drawable 等。
  3. 文件、SharedPreference、数据库相关。
  4. 其他辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生。

    2.2 作用域

    虽然 Context 神通广大,但是并不是随便拿到一个 Context 实例就可以为所欲为,还是有一些限制的。在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是通用的,不过也有几种场景比较特殊,比如启动 Activity、弹出 Dialog。Android 是不允许 Activity 或 Dialog 凭空出现的,一个 Activity 的启动必须建立在另外一个 Activity 的基础之上,也就以此形成任务栈。而 Dialog 则必须在一个 Activity 的上面弹出(除非是 System Alert 类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类的 Context。
    所以,最常见的错误场景就是:使用 ApplicationContext 去启动一个 LaunchMode 为 standard 的 Activity
    会报错,因为非 Activity 类型的 Context 没呀所谓的任务栈。
Context作用域 Application Activity Service
Show a Dialog No YES NO
Start an Activity 不推荐 YES 不推荐
Layout Inflation 不推荐 YES 不推荐
Start a Service YES YES YES
Send a Broadcast YES YES YES
Register Broadcast Receiver YES YES YES
Load Resoure Values YES YES YES

凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一 些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

一个应用程序有几个Context

实这个问题本身并没有什么意义,关键还是在于对Context的理解,从上面的关系图我们已经可以得出答案了,在应用程序中Context的具体实现子类就是: Activity,Service,Application。那么 Context数量=Activity数量+Service数量 +1 。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有 Activity,Service持有Context,那Broadcast Receiver,Content Provider呢? Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的 Context都是其他地方传过去的,所以并不计入Context总数。上面的关系图也从 另外一个侧面告诉我们Context类在整个Android系统中的地位是多么的崇高,因为很显然Activity,Service,Application都是其子类,其地位和作用不言而喻。

Context能干什么?

Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动 Activity、启动Service、发送广播、操作数据库等等都需要用到Context。

如何获取Context?

通常有四种方式获取:

  1. View.getContext(),放回当前View对象的Context对象,通常是当前正在展开的Activity对象。
  2. Activity.getApplicationContext(),获取当前Activity所在应用进程Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Content。
  3. ContextWrapper.getBaseContext(),用来获取一个ContextWrapper进行装饰前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
  4. Activity.this,返回当前Activity的实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

    getApplication()和getApplicationContext()

    上面说道获取低昂钱Application对象用getApplicationContext,它与getApplication有什么区别?

    1. Application本身就是一个Context,所以获取getApplicationContext得到的结果就是Application本身的实例。
    2. 但是他们在作用域上有较大区别。getApplication只有在Activity和Service中才能调用的到,对于 BroadcastReceiver 和 ContentProvider 只能使用 getApplicationContext。
    3. 但是对于 ContextProvider 使用 getApplicationContext 可能会出现空指针问题。当同一个进程有多个 apk 的情况下,对于第二个 apk 是由 provide 方式拉起的,provide 创建过程并不会初始化所在 application,此时返回的结果是 null。
      1. public class MyReceiver extends BroadcastReceiver{
      2. @override
      3. public void onReceive(Context cotext,Intent intent){
      4. Application app = (Application)context.getApplicationContext();
      5. }
      6. }

Context引起的内存泄漏

Context并不能随意乱用,用的不好有可能会引起内存泄漏的问题,下面给出两种错误的引用方式。

错误的单例模式

  1. public class Singleton{
  2. private static Singleton instance;
  3. private Context mContext;
  4. private Singleton(Context context){
  5. this.context = context;
  6. }
  7. public static Singleton getInstance(Context context){
  8. if(instance == null){
  9. instance = new Singleton(context);
  10. }
  11. return instance;
  12. }
  13. }

这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,加入Activity A去getInstance对象,传入this,常驻内存的Singleton保存了他传人的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就会导致内存泄漏。

View持有Activity引用

  1. public class MainActivity extends Activity{
  2. private static Drawable mDrawable;
  3. protected void onCreate(Bundle saveInstanceState){
  4. super.onCreate(saveInstanceSatet);
  5. setContentView(R.layout.activity_main);
  6. ImageView iv = new ImageView(this);
  7. mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
  8. iv.setImageDrawable(mDrawable);
  9. }
  10. }

有一个静态的Drawable对象,当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入this是MainActiviy的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被注销时,也不能被GC掉,所以造成内存泄漏。

正确使用Context

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,缺因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

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