Context 是什么鬼

1,490 阅读5分钟

感谢郭霖 Android Context完全解析,你所不知道的Context的各种细节
任玉刚Android源码分析-全面理解Context ###什么是 Context? 最初学习 Android 的时候, 对 Context 这个对象很疑惑. 为什么有的时候要传这个参数. 最初的理解就是上下文, Activity, Service, BroadcastReceiver 都属于这个.

仔细想想之后, Android 程序不像 Java 程序那样, 随便创建一个类, 写个 main() 方法之后就能跑了.它需要一个完整的 Andorid 工作环境.
在这个环境中, 我们有 Acticity, service, BroadcastReceiver, 这些组件并不是像 java 一样 new 一下对象就能创建实例了. 而是需要它们各自的上下文环境, 也就是我们这里讨论的 Context.
可以这么说, Context 是维持 Android 程序中各组件能够正常工作的一个核心功能类.

Context 的继承结构:

Context 的继承结构:

Context 的直系子类有 2 个:

  • ContextWrapper: 上下文功能的封装类.
  • ContextImpl: 上下文功能的实现类

ContextWrapper 的 3 个直接子类:

  • Application
  • ContextThemeWrapper: 一个带主题的封装类,而它有一个直接子类就是 Activity。
  • Service

所以 Context 一共有 3 种类型:

  • Application
  • Activity
  • Service

Context 可以实现哪些功能?
太多了.

  • 弹出 Toast
  • 启动 Activity
  • 启动 Service
  • 发送广播
  • 操作数据库
  • 等等...

Context 的具体用途都是 ContextImpl 类去实现的. 因此在绝大多数情况下, Activity, Service, Apllication 都是可以通用的.
有几种特殊的场景,比如:

  • 启动 Activity: 一个 Activiy 的启动必须建立在另一个 Activiy 之上, 以此形成返回栈.
  • 弹出 Dialog: 必须在一个 Activity 上面弹出(除非是 System Alert 类型的 Dialog).

这 2 个场景只能使用 Activity 类型的 Context, 否则会出错.

###Context 数量如何计算?

根据上面的 Context 类型可以推断出下面的公式:

Context 的数量 = Activity 的数量 + Service 的数量 + 1

这里的 1 代表的是 Application.因为一个 app 只能有一个 Application

###getApplication 和 getApplicationContext 的区别

基本上每一个应用程序都一个自己的 Application, 并让它继承系统的 Application 类, 然后在自己的 Application 类中去封装一些通用的操作. 其实这并不是 Google 所推荐的一种做法, 因为这样我们只是把 Application 当成了一个通用工具类来使用的, 而实际上使用一个简单的单例类也可以达到相同的功能.

App application = (App) getApplication();
Context applicationContext = getApplicationContext();
Logger.d(TAG,"application-->"+application);
Logger.d(TAG,"applicationContext-->"+applicationContext);  

打印出来的地址完全相同:

application-->lwj.bilibili.App@240f3f9
applicationContext-->lwj.bilibili.App@240f3f9  

getApplicationContext() 获取的是它本身的实例.

既然这 2 个方法得到的结果都是相同的, 为什么 Android 要提供 2 个重复的方法?

他们在作用域上有区别:

  • getApplication() 只有在 Activity 和 Service 中才能调用的到.

  • 在一些场景, 比如在 BroadCastReceiver 中获取 Application 的实例, 这时就可以借助 getApplicationContext().

    public class MyReceiver extends BroadcastReceiver {

      @Override  
      public void onReceive(Context context, Intent intent) {  
          MyApplication myApp = (MyApplication) context.getApplicationContext();  
          Log.d("TAG", "myApp is " + myApp);  
      }  
    

    }

getApplicationContext() 方法的作用域会更广一点, 任何一个 Context 的实例, 只要调用 getApplicationContext() 方法都可以拿到我们的 Application 对象

除了这 2 个还有一个 getBaseContext()
同样打印日志:

application-->lwj.bilibili.App@240f3f9
applicationContext-->lwj.bilibili.App@240f3f9
baseContext-->android.app.ContextImpl@b93ef3e   

getBaseContext() 得到的是一个 ContextImpl 对象. 这个 ContextImpl 就是之前 Context 继承图中的上下文功能的实现类.
Application, Activity 这样的类不会去具体实现 Context 的功能,而仅仅是做了一层接口封装.
Context 的具体功能都是由 ContextImpl 类去实现的.

这样的设计是如何实现的? 直接看 ContextWrapper 的源码:

/** 
  * Proxying implementation of Context that simply delegates all of its calls to 
  * another Context.  Can be subclassed to modify behavior without changing 
  * the original Context. 
  */  
public class ContextWrapper extends Context {  
    Context mBase;  
      
    /** 
     * Set the base context for this ContextWrapper.  All calls will then be 
     * delegated to the base context.  Throws 
     * IllegalStateException if a base context has already been set. 
     *  
     * @param base The new base context for this wrapper. 
     */  
    protected void attachBaseContext(Context base) {  
        if (mBase != null) {  
            throw new IllegalStateException("Base context already set");  
        }  
        mBase = base;  
    }  
  
    /** 
     * @return the base context as set by the constructor or setBaseContext 
     */  
    public Context getBaseContext() {  
        return mBase;  
    }  
  
    @Override  
    public AssetManager getAssets() {  
        return mBase.getAssets();  
    }  
  
    @Override  
    public Resources getResources() {  
        return mBase.getResources();  
    }  
  
    @Override  
    public ContentResolver getContentResolver() {  
        return mBase.getContentResolver();  
    }  
  
    @Override  
    public Looper getMainLooper() {  
        return mBase.getMainLooper();  
    }  
      
    @Override  
    public Context getApplicationContext() {  
        return mBase.getApplicationContext();  
    }  
  
    @Override  
    public String getPackageName() {  
        return mBase.getPackageName();  
    }  
  
    @Override  
    public void startActivity(Intent intent) {  
        mBase.startActivity(intent);  
    }  
      
    @Override  
    public void sendBroadcast(Intent intent) {  
        mBase.sendBroadcast(intent);  
    }  
  
    @Override  
    public Intent registerReceiver(  
        BroadcastReceiver receiver, IntentFilter filter) {  
        return mBase.registerReceiver(receiver, filter);  
    }  
  
    @Override  
    public void unregisterReceiver(BroadcastReceiver receiver) {  
        mBase.unregisterReceiver(receiver);  
    }  
  
    @Override  
    public ComponentName startService(Intent service) {  
        return mBase.startService(service);  
    }  
  
    @Override  
    public boolean stopService(Intent name) {  
        return mBase.stopService(name);  
    }  
  
    @Override  
    public boolean bindService(Intent service, ServiceConnection conn,  
            int flags) {  
        return mBase.bindService(service, conn, flags);  
    }  
  
    @Override  
    public void unbindService(ServiceConnection conn) {  
        mBase.unbindService(conn);  
    }  
  
    @Override  
    public Object getSystemService(String name) {  
        return mBase.getSystemService(name);  
    }  
  
    ......  
}    

这里面的 mBase 对象是什么?

我们来看第16行的attachBaseContext()方法.

这个方法中传入了一个 base 参数,并把这个参数赋值给了 mBase 对象。
而attachBaseContext() 方法其实是由系统来调用的,它会把 ContextImpl 对象作为参数传递到 attachBaseContext() 方法当中, 从而赋值给 mbase.

之后 ContextWrapper 里面所有的方法都是通过这种委托机制来实现的. 所以说 ContextImpl 是上下文功能的实现类是非常准确的.

getBaseContext() 返回就是 mBase 对象, 而 mBase 对象其实就是 ContextImpl 对象.

###如何使用 Application?

谷歌官方并不推荐我们自己自定义 Application, 但是现在基本所有的 app 都会自定义.

  • 不要把 Application 当成工具类使用
  • 只在里面做一些全局的操作
  • 不可以在 Application 构造方法做一些操作, 应该在 oncreate(), 只有在 oncreate() 的的时候开始调用 attachBaseContext() 把 ContextImp 传给 mBase. 不然会报空指针.

Application 执行过程