【Android】深入理解Context

深入理解Context

Context在应用程序开发中会经常被使用,在一般的计算机书籍中,Context被翻译为”上下文”,而笔者认为Android中的Context应该被翻译为”场景”.

  1. Context是什么

    一个Context意味着一个场景,一个场景就是用户和操作系统交互的一种过程.比如当你打电话时,场景包括电话程序对应的界面,以及隐藏在界面后的数据;当你看短信时,场景包括短信界面,以及隐藏在后面的数据.

    读者可曾想过,从程序的角度来看,Context到底是什么?如果笔者告诉你,一个Activity就是一个Context,一个Service也是一个Context,你会觉得奇怪吗?

    我们先不看代码,而从语义的角度来审视一下Context.Android程序员把”场景”抽象为Context类,如上所述,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话,发短信,这些都是有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Serivce).一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可称之为一个应用程序.

    接下来看代码.Activity类的确是基于Context,而Service类也是基于Context.Activity除了基于Context类外,还实现了一些其他重要接口,从设计的角度来看,interface仅仅是某些功能,而extends才是类的本质,即Activity的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充后的类称之为一个Activity或者Service.

    也可以从另一个角度来理解Context和Activity的关系.

    大家都知道Activity内部包含一些特别的方法,比如onCreate(),onPause(),onStart()等方法,这些只有Activity才有的.那么试想一下,假如Activity类是一个interface的话,那么就可以提出一种真正意义上的场景类,假设命名为Task,同时假设Context类不是一个abstract类,而是一个interface,那么Task类的定义将会像下面这个样子.

    class Task implements Activity,Context.....
    

    可事实上,由于Context类就是为”场景应用”而专门设计的,它包含了一个场景中的基本函数接口,因此,Google程序员认为Task本质上就是Context,Activity尽管有一些特别的函数接口,但本质上就是一个Context.所以,Context被设计为一个abstract类,Activity仅仅是基于Context而已.Google程序员还认为Service本质上是一个Context,所以Service也是仅仅基于Context而已.

  2. 一个应用程序中包含多少个Context对象

    在以往的应用程序开发中,经常会调用Context的一些方法,这些方法看起来似乎会返回一些全局的对象,而不仅仅是某个Activity,于是有的读者就有点疑惑,一个应用程序到底有多少个Context对象呢?比如,Context.getResources()返回该应用程序所对应的Resources类对象,无论从哪个Activity中调用,都会返回同一个Resources对象.

    这里可以明确的是:

    • 一个Activity就是一个场景(Context),一个Service也是一个场景,所以,应用程序中有多少个Activity或者Service,就会有多少个Context对象.
    • getResources()等方法的确返回的是同一个全局对象.至于这是如何实现的,请看下面介绍.
  3. Context相关类的继承关系

    Context相关的类的继承关系如图所示:

    Context类本身是一个纯abstrace类.

    为了使用方便,又定义了ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象.

    ContextThemeWrapper类,如其名称所示,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题.

    当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper.

    ContextImpl类真正实现了Context中所有的函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类.

  4. 创建Context

    前面说每一个Activity本质上就是一个Context,因为Activity就是基于ContextWrapper类,然而对于ContextWrapper类,必须为其指定真正的Context对象,而真正实现了Context类的是ContextImpl类.所以本节就要来研究这个”真正的Context”(ContextImpl)是如何创建的.

    每一个应用程序在客户端都是从ActivityThread类开始的,创建Context对象也是在该类中完成,具体创建ContextImpl类的地方一共有7处,分别如下:

    • 在PackageInfo.makeApplication()中.
    • 在performLaunchActivity()中.
    • 在handleCreateBackupAgent()中.
    • 在handleCreateService()中.
    • 在handleBindApplication中.
    • 还是在handleBindApplication()中.
    • 在attach()方法中.

    关于以上方法的调用时机,请参照后面博文Activity启动过程,本节主介绍以上方法中创建ContextImpl的内部逻辑.其中attach()方法仅在Framework进程(system_server)启动时调用,应用程序运行时不会调用到该方法,因此本博文不做介绍,详情请参照接下来的博文关于Framework的启动.需要再次明确的是system_server进程本身也是一个应用程序,所以其入口也是ActivityThread类,只是这个ActivityThread和一些系统服务运行在同一个进程空间中而已.attach()方法中创建ContextImpl对象的逻辑与下面三节所描述的基本相同.

    4.1 Application对应的Context

    每个应用程序在第一次启动时,都会首先创建一个Application对象,默认的Application是应用程序的包名,用户可以重载默认的Application.方法是在AndroidManifest.xml的Application标签中声明一个新的Application名称,然后在源码中添加该名称的类,该类的父类使用Application即可.

    程序第一次启动时,会辗转调用到handleBindApplication()方法中,该方法中有两处创建了ContextImpl对象,但都是在if(data.instrumentationName != null)条件中.该条件在一般的程序执行时都不会被执行到,只有当创建了一个Android Unit Test工程时,相应的Test程序会满足这个条件.关于Android Unit Test请参照Android SDK文档,此处不做介绍.

    而如果不是测试工程的话,则调用makeApplication()方法,如以下代码所示:

    Application app = data.info.makeApplication(data)
    

    而在makeApplication()方法中,主要包含以下代码:

    ContextImpl appContext = new ContextImpl();
    appContext.init(this,null,mActivityThread);
    app = mActivityThread.mInsturmentation.newApp
                    cl,appClass,appContext);
    appContext.setOuterContext(app);
    

    即创建一个ContextImpl对象,然后调用init()方法,其中第一个参数this指的就是当前PackageInfo对象,该对象将赋值给ContextImpl类中的重要成员变量mPackageInfo.

    这里需要注意,这个PackageInfo对象又是从何而来呢?这就要回溯到是谁调用了makeApplication()方法,回溯流程如下.

    首先,AmS通过远程调用到ActivityThread的bindApplication()方法,该方法的参数包括两种.一种是ApplicationInfo,这是实现了Parcelable接口的数据类,意味着这个对象是由AmS创建的,通过IPC传递到ActivityThread中,另一种是其他相关参数.

    在bindApplication()方法中,会用以上两种参数构造一个本地AppBindData数据类,然后再去调用handleBindApplication().

    在调用handleBindApplication()的时候,AppBindData对象中的info变量还是空值,然后会使用data.info = getPackageInfoNoChecked()方法为info变量赋值,而这个方法的内部实际上会根据AppBindData中的ApplicationInfo中的mPackageName创建一个PackageInfo对象,并把这个对象保存为ActivityThread类的全局对象.显然,一个应用程序中的所有Activity或者Application对象对应的mPackageName都是一样的,即为包的名称,所以ActivityThread中会产生一个全局PackageInfo对象.

    接下来,就回到上面所说的调用data.info.makeApplication()方法了,这就是PackageInfo对象的来源.

    以上流程可总结为下图所示.

    创建Context时PackageInfo对象的来源

    4.2 Activity对应的Context

    启动Activity时,AmS会通过IPC调用到ActivityThread的scheduleLaunchActivity()方法,该方法包含两种参数.一种是ActivityInfo,这是一个实现了Parcelable接口的数据类,意味着该对象是AmS创建的,并通过IPC传递到ActivityThread;另一种是其他一些参数.

    scheduleLaunchActivity()方法中会根据以上两种参数构造一个本地ActivityRecord数据类,ActivityThread内部会为每一个Activity创建一个ActivityRecord对象,并使用这些数据对象来管理Activity.

    接着会调用到handleLaunchActivity(),然后再调用到performLaunchActivity(),该方法中创建ContextImpl的代码如下:

    ContextImpl appContext = new ContextImpl();
    appContext.init(r.packageInfo,r.token,this);
    appContext.setOuterContext(activity);
    

    这段代码与上一节基本相同.同样要问到一个问题,appContext.init()方法中第一个参数r.packageInfo是如何而来的,答案如以下代码所示:

    ActivityInfo aInfo = r.activityInfo;
    if(r.packageInfo == null){
        r.packageInfo = getPackageInfo(aInfo.applicationInfo,Context.CONTEXT_INCLUDE_CODE);
    }
    

    即在performLaunchActivity()开始执行时,首先为r.packageInfo变量赋值,getpackageInfo方法的执行逻辑和getPackageInfoNoChecked()基本相同.所以,r.packageInfo对象的PackageInfo对象和Application对应的packageInfo对象是同一个.

    以上流程可总结为如下图所示.

    4.3 Service对应的Context

    启动Service时,AmS首先会通过IPC调用到ActivityThread的scheduleCreateService()方法,该方法也包含两种参数.第一种是ServiceInfo,这是实现了一个Parcelable接口的数据类,意味着该对象由AmS创建,并通过IPC传递到ActivityThread类部;第二种是其他参数.

    在scheduleCreateService()方法中,会使用以上两种参数构造一个CreateServiceData的数据对象,ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service.

    接着,会执行handleCreateService()方法,其中创建ContextImpl对象的代码如下:

    ContextImpl context = new CotnextImpl();
    context.init(packageInfo,null,this);
    
    Application app = packageInfo.makeApplication(false....
    context.setOuterContext(Service);
    

    这与前两节基本相同,调用context.init()方法的第一个参数packageInfo赋值的代码如下:

    PackageInfo packageInfo = getPackageInfoNoCheck(data.info.applicationInfo);
    

    赋值代码同样使用了getPackageInfoNoCheck()方法,这就意味着Service对应的Context对象内部的mPackageInfo与Activity,Application中是完全相同的.

    其流程总结为下图所示:

    4.4 Context之间的关系

    从以上三节可以看出,创建Context对象的过程基本上是相同的,包括代码的结构也十分类似,所不同的仅仅是针对Application,Activity,Service使用了不同的数据对象,可总结为下表所示.

    不同Context子类中PackageInfo对象的来源

    从以上三节也可以看出,实际上一个应用程序包含的个数应该为:
    Context个数 = Service个数+Activity个数+1
    最后一个1代表的是Application本身也会对应一个Context对象,而Context对象的结构关系可如下图所示:

    应用程序中包含多个ContextImpl对象,其内部变量mPackageInfo却指向同一个PackageInfo对象,这种设计结构一般意味着ContextImpl是一种轻量级类,而PackageInfo是一个重量级类,那么事实是不是这样呢?通过查看ContextImpl代码可知,的确是这样的,ContextImpl中的大多数重量级函数实际上都是转向了mPackageInfo对象相应的方法,即事实上是调用了同一个PackageInfo对象,因此,从系统效率的角度来看也是合理的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值