写点什么

Context 那些你不知道的事?,张口就来

用户头像
Android架构
关注
发布于: 刚刚

通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。

修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。

当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类。相反,修饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。一个修饰模式的示例是 JAVA 里的 Java I/O Streams 的实现。


Context 是基本的抽象类,无论是实现类,还是装饰类,都直接或间接的实现它。ContextImpl 是 Context 的直接实现类,但各个组件并不是直接继承 ContextImpl,而是通过装饰类 ContextWrapper 来持有 ContextImpl。这是为什么呢?对于 Activity 和 Service 来说,它们都需要系统上下文运行环境,但它们又是不同的。Activity 需要显示到前台,它有页面,它需要主题,于是有了继承自 ContextWrapper 的?ContextThemeWrapper,扩展了功能,给 Activity 提供了主题。同时,Activity、Service、Application 这些具体组件本身又扩展出了不同的生命周期功能。


所以,装饰器模式通过组合和扩展装饰类,来给不同的具体对象提供了不同的功能扩展。


ActivityServiceApplication?最终都是继承自装饰类?ContextWrapper?,ContextWrapper?通过?attachBaseContext()?方法来获取实际做事的?ContextImpl?对象。所以这些组


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


件的创建过程中,一定会在某一时机调用?attachBaseContext()?方法对?mBase?对象进行赋值,让我们从源码里面找找答案。

四大组件和 Context

Activity 和 Context

先说?Activity,Activity 的启动过程极其复杂,我们就直接从?ActivityThread?的?performLaunchActivity()?方法看起。


private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {


整理一下大致的执行流程:


  1. 获取 LoadedApk 对象,表示加载过的 Apk ,通常一个 App 对应着一个 LoadedApk

  2. 通过?createBaseContextForActivity()?方法创建 ContextImpl 对象

  3. 反射创建 Activity 对象

  4. 创建 Application 对象,这里也是用的反射。如果开发者没有声明自己的 Application 的话,就是默认的?androoid.app.Application

  5. 调用?activity.attach()?,这个方法很重要,后面详细说

  6. 回调?onCreate()


接着就是 Activity 正常的生命周期流程了。


重点看一下?createBaseContextForActivity()?方法和?attach()?方法。


private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {


调用了?ContextImpl.createActivityContext()?方法。


static ContextImpl createActivityContext(ActivityThread mainThread,


装饰类 ContextWrapper 真正需要的 ContextImpl 对象现在已经创建出来了,但是还没有绑定到 Activity 。继续看?Activity.attach()?方法,注意?attach()?方法的第一个参数就是刚刚创建出来的 ContextImpl 对象。


final void attach(Context context, ActivityThread aThread,


你对?attachBaseContext()?方法应该还有印象。ContextWrapper 正是通过这个方法给?mBase对象赋值,拿到真正的 ContextImpl 对象。到这里,整个逻辑就通顺了。


注意?attach()?方法中的?setWindowManager()?方法中的?mToken?参数,这决定了 Application Context 无法创建和显示 Dialog 。后续会进行详细分析。


再回头看看文章开头的问题。


Log.e("context", "getApplication in Activity: " + getApplication().getClass().getName());


第一个?getApplication()?,看下源码就知道了:


public final Application getApplication() {


getApplication()?返回的是当前的 Application 对象。开发者没有声明自己实现的 Application 的话,就是系统默认的?android.app.Application


第二个?getApplicationContext(),它并不是 Activity 中的方法,而是 ContextWrapper 的。直接看源码:


@Override


调用的是?ContextImpl.getApplicationContext()?。


@Override


所以返回的同样是 Application 对象。


第三个,getBaseContext()?,同样是 ContextWrapper 中的方法:


public Context getBaseContext() {


所以这里返回的是 ContextImpl 对象。


最后的打印语句是:


E/context: getApplication in Activity: luyao.android.App


关于 Activity 就说这么多了。下面来看看 Service 。

Service 和 Context

Service 其实和 Activity 的整体流程基本一致,创建服务的主要逻辑在?ActivityThread.handleCreateService()?方法中。这里我就不贴源码了,简单叙述一下:


  1. 创建 LoadedApk 对象

  2. 反射创建 Service 对象

  3. 调用 ContextImpl.createAppCntext() 创建 ContextImpl 对象

  4. 创建 Application 对象

  5. 调用 service.attach() 进行绑定

  6. 回调 service 的 onCreate() 方法


直接看一下?Service.attach()?方法:


public final void attach(


又看到了熟悉的?attachBaseContext()?方法。


Activity?和?Service?都是继承自?ContextWrapper?的,最后都是通过attachBaseContext()?对 ContextImpl 类型的?mBase?赋值。而?ContentProvider?和?BroadcastReceiver?都没有继承 Context,所以它们获取 Context 的方式会有一点不一样。

ContentProvider 和 Context

先来看?ContentProvider,创建 Provider 的逻辑在?Activity.installProvider()?方法中:


private ContentProviderHolder installProvider(Context context,


最后在?ContentProvider.attachInfo()?方法中进行了 ContextImpl 的赋值操作。


private void attachInfo(Context context, ProviderInfo info, boolean testing) {


这样 ContentProvider 也能拿到 Context 对象了。

BroadcastReceiver 和 Context

最后就是 BroadcastReceiver 了,对应?ActivityThread.handleReceiver()?方法:


private void handleReceiver(ReceiverData data) {


大多数步骤和 Activity 还是类似的,只是到最后回调?onReceive()?方法的时候,才会把 ContextImpl 对象传过去。注意,这里并不是直接返回原生的 ContextImpl 对象,而是调用?context.getReceiverRestrictedContext()?返回一个?受限制?的?ReceiverRestrictedContext,你无法使用这个 Context 对象启动 Service 。


这不正是?装饰者模式?的体现?想给广播的 Context 对象加点限制,那就再来一个装饰类?ReceiverRestrictedContext?,它继承了?ContextWrapper?, 重写部分方法以限制应用场景。通过增加和组合装饰类,而不是增加子类,来实现功能扩展。

Application 和 Context

四大组件说完了,别忘了?Application?也是?Context?的间接子类。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Context那些你不知道的事?,张口就来