从源码上解析Android Context

一、Context

1. 定义

Context,翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。和java程序不同,Android程序需要有一个完整的Android工程环境,在这个环境下,有像Activity、Service、BroadcastReceiver等系统组件,而这些组件要有它们各自的上下文环境,也就是Context。可以说Context是维持Android程序中各组件能够正常工作的一个核心功能类。

它是一个抽象类,是一个应用程序环境信息的接口。里面定义了各种抽象方法,包括获取系统资源、获取系统服务,发送广播,启动Activity,Service等。所以从源码角度看Context就是抽象出一个App应用所有功能的集合。

2. Context的使用场景

  1. 使用Context调用方法,比如启动Activity、访问资源、调用系统级服务等

  2. 调用方法时传入Context,比如弹出toast,创建dialog等等。

3. Context的关联类

它的关联类(直接子类)有:ContextWrapper(Context的封装类) ContextImpl(Context的实现类),接下来的类都是继承自ContextWrapper这个装饰类的具体装饰类,包括ContextThemeWrapper、 Application和Service。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。也就是说,Context一共三种类型,包括Application、Service和Activity。
下面这张图展示了Context的关联类之间的关系:


1.png

Context的几个子类在能力上的区别

2.jpg

数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。
ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
从表格中可以看到,和UI相关的方法基本都不建议或者不可使用 Application,并且,前三个操作基本不可能在Application中出现。实际上,凡是跟UI相关的,都应该使用 Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以。

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的具体能力是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity, 还有弹出Dialog、导入布局文件。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

ps:在Activity中某个地方需要弹出对话框时,通常会构造一个AlertDialog然后show一下,其中在构造AlertDialog的时候,传递了当前的Activity作为上下文context。

二、源码中的Context

1. 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系统提供。通过它我们可以访问特定于应用程序的资源和类【以及对应用程序级操作(如启动活动、广播和接收intents)的调用)】

抽象类,提供了一组通用的API。

2. ContextImpl实现类
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
 private Context mOuterContext;
 ......
}

注释说明:ContextImpl是对Context类的所有API的通用实现,并且为Activity和其他应用程序的组件提供了base Context object。(提供了Context类的对象mBase)

3. 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;
​
 public ContextWrapper(Context base) {
 mBase = base;
 }
​
 /**
 * 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;
 }
 ......
}

Context的代理实现,这里的实现只是简单的把它所有的调用委派给另一个context(也就是ContextWrapper源码里面的所有方法实现都是通过调用mBase的同名方法,也就是委派给ContextImpl)。可以在不更改原始Context类的情况下,子类化以修改行为(添加新的方法或者是删除某些方法)。
ContextWrapper类的构造函数包含了一个真正的Context引用(ContextImpl对象),然后就变成了ContextImpl的装饰者模式。

4. ContextWrapper的子类ContextThemeWrapper
 * A ContextWrapper that allows you to modify the theme from what is in the 
 * wrapped context. 
 */
public class ContextThemeWrapper extends ContextWrapper {
 ......
}

在Wrapperd context的基础上允许修改主题;该类的内部包含了主题Theme相关的接口。

三、Context的创建

1. Application context的创建过程

3.png
  1. ActivityThead类(应用程序进程的主线程管理类),会调用其内部类ApplicationThread的scheduleLaunchActivity方法来启动Activity。

    scheduleLaunchActivity方法内容:

    updateProcessState(); 
    ActivityClientRecord r=new ActivityClientRecord(); //新建一个记录对象,用来存放启动信息
    ...  //给r赋值
    sendMessage(H.LAUNCH_ACTIVITY,r); //向H类发送r消息
    

    ActivityClientRecord是activity的一个记录类,记录了很多启动activity的信息,暂且不管。
    H 是 ActivityThread 内部类,继承自 Handler。
    H类接收到消息之后,在handleMessage方法中对它进行了处理。
    handleMessage方法主要代码:

     r.packageInfo=getPackageInfoNoCheck(r.activityInfo.applicationInfo,r.compatInfo);
      handleLaunchActivity(r,null,"LAUNCH_ACTIVITY");
    

    第一行通过一个get方法从消息r中获取到LoadedApk类型的对象,并且把值赋给ActivityClientRecord记录类r的成员变量packageInfo, LoadedApk用来描述已加载的APK文件。
    第二行调用了ActivityThread中的handleLaunchActivity方法,其具体内容如下:

       Activity a=performLauchActivity(r, customIntent);
    

    上面同样是调用了ActivityThread中的另一个方法:performLauchActivity,其具体内容如下:

     //此方法返回一个Activity类的对象
        try{
         Application app=r.packageInfo.makeApplication(false, mInstrumentation);//r.packageInfo上面已经赋值了
         ...
          }
        ...
        return activity;
    

    可以看到,上面调用了LoadedApk的makeApplication方法,其内容如下:

     //返回一个Application类的对象
      if(mApplication!=null)
      {
        return mApplication; //这里是第一次启动,所以为空,不会返回;但是如果不是第一次启动,就会直接返回
      }
      ContextImpl   appContext=ContextImpl.createAppContext(mActivityThread,this);      //2
    app=mActivityThread.mInstrumentation.newApplication(cl,appClass,appContext);//3
      appContext.setOuterContext(app);//4
      mApplication=app; //5
    

    注释2处通过ContextImpl的createAppContext方法来创建ContextImpl。
    注释3处代码用于创建application, 在Instrumentation的newApplication方法中传入了ClassLoader类型的对象以及注释2处创建的ContextImpl。
    在注释4处把注释3中创建出来的Application赋值给ContextImpl的Context类型的成员变量mOuterContext,也就是让ContextImpl内部持有外部Application类的引用,用于注册系统服务或者是其他方法。
    最后,通过mApplication=app 将Application赋值给LoadedApk的成员变量mApplication。
    接下来,看一下注释3处的Application是如何创建的,Instrumentation的newApplication方法:

    Application app=(Application)clazz.newInstance();
     app.attach(context);
     return app;
    

    通过反射来创建Application,并且调用了Application中的attach方法,将ContextImpl传进去,最后返回Application。
    attach方法的主要内容是:

    attachBaseContext(context);
    mLoadedApk=ContextImpl.getImpl(context).mPackInfo;
    

    attchBaseContext方法在Application的父类ContextWrapper中实现,代码如下:

     protected void attachBaseContext(Context base){
         if(mBase!=null){
       throw new IllegalStateException("Base context already set");
     }
     mBase=base; //把ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase
    }
    

    这个base一路传递过来指的是ContextImpl,它是Context的实现类。
    Application通过父类ContextWrapper类的成员变量mBase指向了ContextImpl,让Application类真正实现了其祖父类Context抽象类中的所有抽象方法,这个过程是通过attachBaseContext方法来实现的。
    把ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase,这样在ContextWrapper中就可以使用Context的方法,而Application继承自ContextWrapper,同样可以使用Context的方法。

2. Activity context的创建过程

4.png

如果想要在Activity中使用Context提供的方法,务必要先创建Context。Activity的Context会在Activity的启动过程中被创建。

上面已经说过,ActivityThread会调用scheduleLaunchActivity方法来启动Activity,此方法把启动Activity的参数封装成ActivityClientRecord,sendMessage方法向H类发送类型为LAUNCH_ACTIVITY的消息,并将ActivityClientRecord传递过去。sendMessage方法的目的将启动Activity的逻辑放在主线程的消息队列中,这样启动Activity的逻辑就会在主线程中执行。然后H类的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,其中调用了ActivityThread的handleLaunchActivity方法,而在handleLaunchActivity方法中又调用了ActivityThread的performLaunchActivity方法,在此方法中首先通过createBaseContextForActivity方法来创建Activity的ContextImpl,然后再将ComImpl传入activity的attach方法中,并且调用了ContextImpl的setOuterContext方法,将此前创建的Activity实例赋值给ContextImpl的成员变量mOuterContext,这样ContextImpl也可以访问Activity的变量和方法。

Activity的attchBaseContext方法其父类ContextThemeWrapper中实现,然后此方法接着调用ContextThemeWrapper的父类ContextWrapper的attachBaseContext方法。

总结:在启动Activity的过程中创建ContextImpl, 并赋值给ContextWrapper的成员变量mBase。Activity继承自ContextWrapper的子类ContextThemeWrapper, 这样在Activity中就可以使用Context中定义的方法了。

3. Service的Context创建过程

与Activity的Context创建过程类似,是在Service的启动过程中被创建的。
总结:在Service的启动过程中创建ContextImpl,并且将ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase, 这样可以在ContextWrapper中就可以使用Context的方法,而Service继承自ContextWrapper,同样可以使用Context的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容