zl程序教程

您现在的位置是:首页 >  其它

当前栏目

理解Context

理解 Context
2023-09-14 09:04:22 时间

第 7 章 理 解 Context
Context在应用程序开发中会经常被使用,在一般的计算机书籍中,Context被翻译为“上下文’
而笔者认为Andmid中的Context应该被翻译为“场景”。
Context是什么
r w,、 ,、、 7\111
一 个 Context意味着一个场景,一个场景就是用户和操作系统交互的一种过程。比如当你打电话时,
场景包括电话程序对应的界面,以及隐藏在界面后的数据;当你看短信时,场景包括短信界面,以及隐
藏在后面的数据。
读者可曾想过,从程序的角度来看,Context到底是什么?如果笔者告沂你,一 个 Activity就是一
个 Context,一 个 Service也是一个Context,你会觉得奇怪吗?
我们先不看代码,而从语义的角度来审视一下Context。Andmid程序员把“场景” 抽象为Context
类,如上所述,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是
有界面的场景,还有一些没有界面的场景,比如后台运行的服务 Service)。一个应用程序可以认为是
一个工作环境,用户在这个工作环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客
人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可称之为一个应
用程序。
接下来看代码。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类的定义将会像下面这个样子。
| c l a s s T a s k i m p l e m e n t s A c t i v i t y , C o n t e x t , …
可事实上,由于Context类就是为“场景应用” 而专门设计的,它包含了一个场景中的基本函数接
口,因此,Google程序员认为Task本质上就是Context,Activity尽管有一些特别的函数接口,但本质
上就是一个Context。所以,Context被设计为一个abstract类,Activity仅仅是基于Context而已。Google
程序员还认为Service本质上是一个Context,所 以 Service也是仅仅基于Context而已。
在这里插入图片描述
在这里插入图片描述
Context类本身是一个纯abstract类。
为了使用方便, 又定义了 ContextWrapper类, 如其名所言,这只是一个包装而已, ContextWrapper
构造函数中必须包含一个真正的Context引用,同时 ContextWrapper中提供了 attachBaseContext()用于
给 ContextWrapper对象中指定真正的Context对象,调 用 ContextWrapper的方法都会被转向其所包含的
真正的Context对象。
ContextThemeWrapper类,如其名称所示, 其内部包含了与主题 Theme)相关的接口,这里所说
的主题就是指在AndroidManifest.xml中通过android:theme为 Application元素或者Activity元素指定的
主题。
当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,
所以 Service 直接继承于 ContextWrapper。
Contextlmpl类真正实现了 Context中所有的函数,应用程序中所调用的各种Context类的方法,其
实现均来自于该类。

创建context
前面说每一个Activity本质上就是一个Context,因为Activity就是基于ContextWrapper类,然而
对 于 ContextWrapper类,必须为其指定真正的Context对象,而真正实现了 Context类的是Contextlmpl
类。所以本节就来研究这个“真正的Context” (Contextlmpl)是如何创建的。
每一个应用程序在客户端都是从ActivityThread类开始的,创 建 Context对象也是在该类中完成,
具体创建Contextlmpl类的地方一共有7 处,分别如下:
• 在 PackageInfo.makeApplication()中。
• 在 performLaunchActivity()中。
• 在 handleCreateBackupAgent()中
• 在 handleCreateService()中。
• 在 handleBindApplication()中。
• 还是在 handleBindApplication()中。
• 在 attach()方法中。
关于以上方法的调用时机请参照第1 0 章 中 Activity启动过程,本节主要介绍以上方法中创建
Contextlmpl的内部逻辑。其 中 attach()方法仅在Framework进程(system_server;)启动时调用,应用程序
运行时不会调用到该方法,因此本章不做介绍,详情请参照第9 章中关于Framework的启动。需要再
次明确的是system_server进程本身也是一个应用程序,所以其入口也是ActivityThread类,只是这个
ActivityThread和一些系统服务运行在同一个进程空间中而已。attach()方法中创建Contextlmpl对象的逻
辑与下面三节所描述的基本相同。

7.4.1 Application 对应的 Context
每个应用程序在第一次启动时,都会首先创建一个Application对象,默认的Application是应用程
序的包名,用户可以重载默认的Application。方法是在AndroidManifest.xml的 Application标签中声明
一个新的Application名称,然后在源码中添加该名称的类,该类的父类使用Application类即可。
程 序 第 一 次 启 动 时 ,会 辗 转 调 用 到 handleBindApplication()方 法 中 ,该方法中有两处创建了
Contextlmpl对象,但都是在if(data.instmmentationName != null)条件中。该条件在一般的程序执行时都
不会被执行到,只有当创建了一个Android Unit Test工程时,相应的Test程序会满足这个条件。关于
Android Unit Test请参照Android SDK文档,此处不做介绍。
而如果不是测试工程的话,则调用makeApplication()方法,如以下代码所示:
~ J4: A p p lic o tio n opp - doto sin fo .mokeAppIicotionCdeitQ ,
而 在 makeApplication()方法中,主要包含以下代码:
6’4^ C o n te x tliifip l o p p C o n te xt » n m C o n ttx tlin p IO ;
n y ll^ m A c ti¥ ity T h r© o d )i
b 14 app « 隱森c t iv it y T h r e o d ,_ In s t r u _ e n t a tio n , newApp
c l^ sppC Ioss^ appContent) i
b 'c (i : opp C o n te x t. s e tO u tmrCont e x t (opp ) ;
即创建一个Contextlmpl对象,然后调用init()方法,其中第一个参数this指的就是当前Packagelnfo
对象,该对象将赋值给Contextlmpl类中的重要成员变量mPakcagelnfo。
这里需要注意,这个Packagelnfo对象又是从何而来呢?这就要回溯到是谁调用了 makeApplicationO
方法,回溯流程如下。
首先,AmS通过远程调用到ActivityThread的 bindApplication()方法,该方法的参数包括两种。一
种是Applicationlnfo,这是实现了 Parcelable接口的数据类,意味着这个对象是由AmS创建的,通 过 IPC
传递到ActivityThread中,另一种是其他相关参数。
在 bindApplication()方法中,会用以上两种参数构造一个本地AppBindData数据类,然后再去调用
handleBindApplication()。
在 调 用 handleBindApplication()的时候,AppBindData对 象 中 的 info变量还是空值,然后会使用
data.info = getPackageInfoNoChecked()方 法 为 in fo 变 量 赋 值 ,而这个方 法 的 内 部 实 际 上 会 根 据
AppBindData中的Applicationlnfo中的mPackageName创建一个Packagelnfo对象,并把这个对象保存
为 ActivityThread类的全局对象。显然,一个应用程序中的所有Activity或 者 Application对象对应的
mPackageName都是一样的,即为包的名称,所 以 ActivityThread中会产生一个全局Packagelnfo对象。
接下来,就回到上面所说的调用data.info.makeApplication()方法了,这就 是 Packagelnfo对象的
来源。

在这里插入图片描述
7.4.2 Activity 对应的 Context
启动 Activity 时, AmS 会通过 IPC 调用到 ActivityThread 的 scheduleLaunchActivity()方法,该方法
包含两种参数。一种是Activitylnfo,这是一个实现了 Parcelable接口的数据类,意味着该对象是AmS
创建的,并通过IPC传递到ActivityThread;另一种是其他一些参数。
scheduleLaunchActivity()方 法 中 会 根 据 以 上 两 种 参 数 构 造 一 个 本 地 ActivityRecord数 据 类 ,
ActivityThread内部会为每一个Activity创 建 一 个 ActivityRecord对象,并使用这些数据对象来管理
Activity。
接 着 会 调 用 到 handleLaunchActivity(),然 后 再 调 用 到 performLaunchActivity(),该方法中创建
Contextlmpl的代码如下
在这里插入图片描述
在这里插入图片描述
7.4.3 Service 对应的 Context
启动 Service 时,AmS 首先会通过 IPC 调用到 ActivityThread 的 scheduleCreateService()方法,该方
法也包含两种参数。第一种是Servicelnfo,这是实现了一个Parcelable接口的数据类,意味着该对象由
AmS创建,并通过IPC传递到ActivityThread内部;第二种是其他参数。
在 scheduleCreateService()方法中,会使用以上两种参数构造一个CreateServiceData的数据对象,
ActivityThread会为其所包含的每一个Service创建该数据对象,并通过这些对象来管理Service。
接着,会执行handleCreateService()方法,其中创建Contextlmpl对象的代码如下:
在这里插入图片描述
在这里插入图片描述
7.4.4 C o n te x t之间的关系
从以上三节可以看出,创 建 Context对象的过程基本上是相同的,包括代码的结构也十分类似,所
不同的仅仅是针对Application、Activity、Service使用了不同的数据对象,可总结为如表7-1 所示。
表 7 - 1 不 同 Context子类中Pckagelnfo对象的来源
在这里插入图片描述
应用程序中包含多个Contextlmpl对象,而其内部变量mPackagelnfo却指向同一个Packagelnfo对
象,这种设计结构一般意味着Contextlmpl是一种轻量级类,而 Packagelnfo是一个重量级类,那么事
实是不是这样呢?通过查看Contextlmpl代码可知,的确是这样,Contextlmpl中的大多数重量级函数实
际上都是转向了 mPackagelnfo对象相应的方法,即事实上是调用了同一个Packagelnfo对象,因此,从
系统效率的角度来看也是合理的。