Android Context 攻略

68 阅读11分钟

攻略大全

1. 粘贴攻略

1.1 概述

Activity mActivity =new Activity()

作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?

Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境。在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。

可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

那Context到底是什么呢?

Context意为上下文,是一个应用程序环境信息的接口。

在开发中我们经常使用Context,它的使用场景总的来说分为两大类,它们分别是:

  • 使用Context调用方法,比如启动Activity、访问资源、调用系统级服务等。
  • 调用方法时传入Context,比如弹出Toast、创建Dialog等。

一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。

Activity、Service和Application都间接地继承自Context,因此我们可以计算出一个应用程序进程中有多少个Context,这个数量等于Activity和Service的总个数加1,1指的是Application的数量。

1.2 Context的关联类

Context 是一个抽象类,它的内部定义了很多方法以及静态常量,它的具体实现类为ContextImpl。和Context相关联的类,除了ContextImpl,还有ContextWrapper、ContextThemeWrapper和Activity等,如图所示:

image.png 从图中我们可以看出,ContextImpl和ContextWrapper继承自Context,ContextWrapper内部包含Context类型的mBase对象,mBase具体指向ContextImpl。

ContextImpl 提供了很多功能,但是外界需要使用并拓展ContextImpl的功能,因此设计上使用了装饰模式,ContextWrapper是装饰类,它对ContextImpl进行包装,ContextWrapper主要是起了方法传递的作用,ContextWrapper中几乎所有的方法都是调用ContextImpl的相应方法来实现的。

ContextThemeWrapper、Service和Application都继承自ContextWrapper,这样它们都可以通过mBase来使用Context的方法,同时它们也是装饰类,在ContextWrapper的基础上又添加了不同的功能。

ContextThemeWrapper中包含和主题相关的方法(比如getTheme方法),因此,需要主题的Activity继承ContextThemeWrapper,而不需要主题的Service继承ContextWrapper。

Context的关联类采用了装饰模式,主要有以下的优点:

  • 使用者(比如Service)能够更方便地使用Context。
  • 如果ContextImpl发生了变化,它的装饰类ContextWrapper不需要做任何修改。
  • ContextImpl的实现不会暴露给使用者,使用者也不必关心ContextImpl的实现。
  • 通过组合而非继承的方式,拓展ContextImpl的功能,在运行时选择不同的装饰类,实现不同的功能。

1.3 Context的作用域

image.png 关于上图中Application和Service所不推荐的两种使用情况:

  1. 如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。

  2. 在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。 一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

1.4 获取Context

通常我们想要获取Context对象,主要有以下四种方法

  1. View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
  2. Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
  3. ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
  4. Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

getApplication()和getApplicationContext()的区别:

首先,两个方法所获取的是同一个对象。

因为Application本身就是一个Context,所以这里getApplicationContext()得到的结果就是Application本身的实例。

但是,实际上这两个方法在作用域上有比较大的区别。

getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。

2. 造火箭攻略

3. 拧螺丝攻略

3.1 Application Context的创建过程

Application Context的创建过程的时序图如图所示: image.png

ActivityThread 类作为应用程序进程的主线程管理类,它会调用它的内部类ApplicationThread的scheduleLaunchActivity方法来启动Activity,如下所示:

image.png

在ApplicationThread的scheduleLaunchActivity方法中向H类发送LAUNCH_ACTIVITY类型的消息,目的是将启动Activity的逻辑放在主线程的消息队列中,这样启动Activity的逻辑会在主线程中执行。我们接着查看H类的handleMessage方法对LAUNCH_ACTIVITY类型的消息的处理:

image.png

image.png H继承自Handler,是ActivityThread的内部类。在注释1处通过getPackageInfoNoCheck方法获得LoadedApk 类型的对象,并将该对象赋值给ActivityClientRecord的成员变量packageInfo,其中LoadedApk用来描述已加载的APK文件。在注释2处调用了ActivityThread的handleLaunchActivity方法,如下所示:

image.png 在handleLaunchActivity方法中调用了ActivityThread的performLaunchActivity方法:

image.png

在performLaunchActivity方法中有很多重要的逻辑,这里只保留了和Application Context相关的逻辑。ActivityClientRecord的成员变量packageInfo是LoadedApk类型的,我们接着来查看LoadedApk的makeApplication方法,如下所示:

image.png

image.png

在注释1处如果mApplication不为null则返回mApplication,这里假设是第一次启动应用程序,因此mApplication为null。

在注释2处通过ContextImpl的createAppContext方法来创建ContextImpl。

注释3处的代码用来创建Application,在Instrumentation的newApplication方法中传入了ClassLoader类型的对象以及注释2处创建的ContextImpl。

在注释4处将Application赋值给ContextImpl的Context类型的成员变量mOuterContext,这样ContextImpl中也包含了Application的引用。

在注释5处将Application赋值给LoadedApk的成员变量mApplication,这个mApplication是Application 类型的对象,它用来代表Application Context,在Application Context的获取过程中我们会再次提到mApplication。

下面来查看注释3处的Application是如何创建的,Instrumentation的newApplication方法如下所示:

image.png

Instrumentation中有两个newApplication重载方法,最终会调用上面这个重载方法。注释1处通过反射来创建Application,并调用了Application的attach方法,将ContextImpl传进去,最后返回该Application,Application的attach方法如下所示:

image.png

在attach方法中调用了attachBaseContext方法,它在Application的父类ContextWrapper中实现,代码如下所示:

image.png

这个base一路传递过来指的是ContextImpl,它是Context的实现类,将ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase,这样在ContextWrapper中就可以使用Context的方法,而Application继承自ContextWrapper,同样可以使用Context的方法。Application的attach方法的作用就是使Application可以使用Context的方法,这样Application才可以用来代表Application Context。

3.2 Application Context的获取过程

我们通过调用getApplicationContext方法来获得Application Context,getApplicationContext方法在ContextWrapper中实现,如下所示:

image.png

mBase指的是ContextImpl,我们来查看ContextImpl的getApplicationContext方法:

image.png

如果LoadedApk类型的mPackageInfo不为null,则调用LoadedApk的getApplication方法,否则调用AvtivityThread的getApplication方法。由于应用程序这时已经启动,因此LoadedApk不会为null,则会调用LoadedApk的getApplication方法,如下所示:

image.png

这里的mApplication我们应该很熟悉,它在上文LoadedApk的makeApplication方法的注释5处被赋值。这样我们通过getApplicationContext方法就获取到了Application Context。

3.3 Activity的Context创建过程

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

image.png ActivityThread是应用程序进程的主线程管理类,它的内部类ApplicationThread会调用scheduleLaunchActivity方法来启动Activity,scheduleLaunchActivity方法如下所示:

image.png

scheduleLaunchActivity 方法将启动Activity的参数封装成ActivityClientRecord,sendMessage方法向H类发送类型为LAUNCH_ACTIVITY的消息,并将ActivityClientRecord传递过去。sendMessage方法的目的是将启动Activity的逻辑放在主线程的消息队列中,这样启动Activity的逻辑就会在主线程中执行。H类的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,其中调用了ActivityThread的handleLaunchActivity方法,而在handleLaunchActivity方法中又调用了ActivityThread的performLaunchActivity方法,直接来查看ActivityThread的performLaunchActivity方法:

image.png

image.png

在performLaunchActivity方法中有很多重要的逻辑,这里只保留了Activity的Context相关的逻辑。

在注释2处用来创建Activity的实例。

在注释1处通过createBaseContextForActivity方法来创建Activity的ContextImpl,并将ContextImpl传入注释4处的activity的attach方法中。

在注释3处调用了ContextImpl的setOuterContext方法,将此前创建的Activity实例赋值给ContextImpl的成员变量mOuterContext,这样ContextImpl也可以访问Activity的变量和方法。

在注释5处m.Instrumentation的callActivityOnCreate方法中会调用Activity的onCreate方法。

我们查看注释1处的createBaseContextForActivity方法: image.png

在createBaseContextForActivity方法中会调用ContextImpl的createActivityContext方法来创建ContextImpl。我们回到ActivityThread的performLaunchActivity方法,查看注释4处的Activity的attach方法,如下所示:

image.png

image.png

在注释2处创建PhoneWindow,它代表应用程序窗口。PhoneWindow在运行中会间接触发很多事件,比如点击、菜单弹出、屏幕焦点变化等事件,这些事件需要转发给与PhoneWindow关联的Actvity,转发操作通过Window.Callback接口实现,Actvity实现了这个接口。

在注释3处将当前Activity通过Window的setCallback方法传递给PhoneWindow。

在注释4处为PhoneWindow设置WindowManager,

在注释5处获取WindowManager并赋值给Activity的成员变量mWindowManager,这样在Activity中就可以通过getWindowManager方法来获取WindowManager。

注释1处的attachBaseContext方法在ContextThemeWrapper中实现,如下所示: image.png

attachBaseContext 方法接着调用ContextThemeWrapper的父类ContextWrapper的attachBaseContext方法:

image.png

image.png

注释1处的base指的是一路传递过来的Activity的ContextImpl,将它赋值给ContextWrapper的成员变量mBase。这样ContextWrapper的功能就可以交由ContextImpl来处理,举个例子,如下所示:

image.png

当我们调用ContextWrapper的getTheme方法时,其实就是调用了ContextImpl的getTheme方法。Activity的Context创建过程就讲到这里。

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

3.4 Service的Context创建过程

Service的Context创建过程与Activity的Context创建过程类似,是在Service的启动过程中被创建的。ActivityThread的内部类ApplicationThread会调用scheduleCreateService方法来启动Service,如下所示:

image.png

sendMessage方法向H类发送CREATE_SERVICE类型的消息,H类的handleMessage方法会对CREATE_SERVICE类型的消息进行处理,其中调用了ActivityThread的handleCreateService方法:

image.png

在注释1处通过ContextImpl的createAppContext方法创建了ContextImpl,并将该ContextImpl传入注释2处service的attach方法中:

image.png 在注释1处调用了ContextWrapper的attachBaseContext方法,如下所示:

image.png

注释1处的base一路传递过来的是ContextImpl,将ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase,这样在ContextWrapper中就可以使用Context的方法,而Service继承自ContextWrapper,同样可以使用Context的方法。

4. 复制攻略

4.1 《Android开发艺术探索》

4.2 《Andriod进阶解密》