Android学习之路(14) Context详解

一. 简介

在 Android 开发中、亦或是面试中都离不开四大组件的身影,而在创建或启动这些组件时,并不能直接通过 new 关键字后跟类名来创建实例对象,而是需要有它们各自的上下文环境,也就是本篇文章要讨论的 Context。

1.1 Context 概述

Context,字面意思:语境、环境、上下文,在 Android 系统中,可以理解为当前对象在应用程序中所处的工作环境。其内部定义很多访问应用程序环境中全局信息的接口,通过它可以访问到应用程序的资源有关的类,如:Resources、AssetManager、Package 及权限相关信息等。还可以通过它调用应用程序级的操作,如:启动 Activity 和 Service、发送广播等。

再来看一下官方对于 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 提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被 Android 系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。

1.2 Context 体系结构

  • Context:是一个抽象类,定义一系列与系统交互的接口。
  • ContextWrapper:继承自 Context 抽象类,是 Context 类的包装类(装饰器模式),内部维护一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextWrapper 里面的方法调用是通过 mBase 来调用 ContextImpl 里面的方法,这里用到了代理模式。
  • ContextImpl:继承自 Context 抽象类,实现了 Context 类中的抽象方法,是 Context 类的具体实现类。它为 Activity 及其它应用组件提供上下文环境,应用中使用到的 Context 的方法就是其实现的。
  • ContextThemeWrapper:继承自 ContextWrapper 类,在 ContextWrapper 的基础上增加与主题 Theme 相关的逻辑,即可以指定 Theme 的 Context 包装类,用于在 View 构造时为其提供 Theme 属性集。

ContextImpl 实现类中涉及的主要核心类是:ActivityThread、LoadedApk、PackageManager 和 ResourcesManager,这几个类都是单例的,一个应用程序进程中是共用同一个对象的。

Contextlmpl 是一种轻量级类,而 LoadedApk 是一个重量级类,Contextlmpl 中的大多数进行包操作的重量级函数实际上都是转向了 LoadedApk 对象相应的方法。
Activity 继承自 ContextThemeWrapper,Application、Service 继承自 ContextWrapper,它们直接或间接的继承自 ContextWrapper 类,因此也拥有了一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextImpl 是 Context 类的具体实现类,所以也都拥有了 Context 提供的所有功能。
代理模式:属于结构型模式,是指为其他对象提供一种代理以控制对这个对象的访问,代理模式又分为静态代理和动态代理。

装饰器模式:又叫包装模式,也是结构型模式,是指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

1.3 Context 作用域

注意:需要加载布局显示界面的,尽可能的使用 Activity 作为 Context 域,虽然加载布局、启动 Activity 可以使用 Application 和 Service 作为 Context 域,但是不推荐,以免报错或 UI 莫名的使用系统默认的主题 Theme 来展示。

1.4 总结

通过本小节的分析,对 Context 的概念、体系结构及作用域都有了简单的了解,那么接下来就来深入探索吧!

二. Context 详解

前面讲到 Context 的体系结构时,了解到其最终实现类有:Application、Service 和 Activity,它们都持有 ContextImpl 这个 Context 抽象类的真正实现,接下来对这三个实现类分别进行讨论分析。

2.1 Application Context

Application 是 Android 系统框架中的一个系统组件,当 Android 应用程序启动时系统会创建一个 Application 类的对象且只创建一个,用来存储系统的一些信息,即 Application 是单例的。

通常在开发过程中是不需要指定一个 Application 的,系统自动帮开发者创建,如果要创建应用自定义的 Application,只需创建一个类继承 Application 并在 AndroidManifest.xml 文件中的 application 标签中进行注册(只需给 application 标签增加 name 属性,并添加自定义的 Application 的名字即可)。

通常自定义 Application 的目的是在应用程序启动时做一些全局的初始化工作,当应用程序启动时,Application 同步创建并启动,系统会创建⼀个 PID,即进程ID,所有的 Activity 都会在此进程上运⾏,因此都可以取到这些初始化的全局变量的值,且由于 Application 对象在整个应用程序运行期间会一直存在,有开发者就会在 Application 中编写一些工具方法,全局获取使用,但是切记不要这样把 Application 当工具类使用。注意:这严重违背 Google 设计 Application 的原则,也违背设计模式中的单一职责原则。

2.1.1 自定义 Application 实例

open class TestApplication : Application() {
    // 全局 context
    companion object{
        lateinit var context: Context
    }
    override fun onCreate() {
        super.onCreate()
        context = this
        initSDKs() // 全局初始化
    }

    private fun initSDKs() {...}
}

继承 Application 并重写 onCreate() 方法,在 Application 创建的时候调用,一般用于全局初始化,如第三方 SDK 的初始化、环境的配置等等,同时可以通过 TestApplication # context 来获取 Application 类型的全局 Context 对象。

2.1.2 获取 Application 实例

class TestActivity : Activity() {
    private val TAG: String = TestActivity::class.java.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val applicationContext = TestActivity@this.getApplicationContext()
        val application = TestActivity@this.getApplication()

        Log.e(TAG, "application: $application")
        Log.e(TAG, "applicationContext: $applicationContext")
    }
}

获取 Application 的方法一般有两个:

  • Activity # getApplication() 或 Service # getApplication()
  • Context # getApplicationContext()

通过 getApplication() 和 getApplicationContext() 都可以获取到 Application,那它们区别是什么呢?

通过日志输出可以看到,它们获取到的是同一个对象,但有同学要问了,那为什么还要提供两个功能一样的方法?因为 getApplication() 方法更加直观,但只能在 Activity 和 Service 场景中调用。getApplicationContext() 方法适用范围更广,任意场景中通过 Context 对象皆可以调用此方法。

2.1.3 Application Context 创建过程

Application 的 Context 是在应用被创建的时候创建的,要追踪其创建需要从应用程序的启动流程出发来探索,即从点击桌面应用图标开始到应用第一个界面展示出来的过程中的某一步,具体哪一步创建的,可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程

简述一下过程:

  • ActivityThread 类作为应用初始化类,在其入口方法 main() 方法中调用 ActivityThread # attach() 方法中,然后通过 Binder 通信跨进程调用到 system_server 进程中 AMS 的 attachApplication() 方法,并将 ApplicationThread 作为参数传递过去。
  • 通过传进来的 ApplicationThread,跨进程通信调用应用进程中 ApplicationThread # bindApplication() 方法绑定 Application。
  • ApplicationThread # bindApplication() 方法中,构建 AppBindData 对象,然后通过内部类 H 发送 BIND_APPLICATION 类型的 Handler 消息,进而调用到 ActivityThread # handleBindApplication() 方法创建并绑定 Application。

2.1.4 时序图

面试题:ActivityThread 是不是一个 Thread?

ActivityThread 类是应用初始化类,它的 main() 方法是应用的入口方法,它也是我们说的"主线程",但是 ActivityThread 本身不是一个线程,之所以称它为"主线程",是因为它运行在主线程中。所以说 ActivityThread 是主线程的一部分,但不并能代表主线程。其作用如下:

  • ActivityThread 负责创建 Application 对象以及管理其生命周期方法调用。
  • ActivityThread 管理着四大组件的生命周期方法调用。

2.1.5 源码解析

通过过程简述与时序图可知,Application 的 Context 的创建是在 ActivityThread # handleBindApplication() 方法中创建的,跟踪查看源码进行详细解析。

2.1.5.1 ActivityThread # handleBindApplication()

ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
 	@UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
		......
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        updateLocaleListFromAppContext(appContext,
                mResourcesManager.getConfiguration().getLocales());
        ......
        if (ii != null) {
            ......
            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                    appContext.getClassLoader(), false, true, false);
            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,
                    appContext.getOpPackageName());
            try {
            	// 获取 ClassLoader 加载类文件
                final ClassLoader cl = instrContext.getClassLoader();
                // 获取 Instrumentation 类并构建实例对象
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            }
			......
            final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
			......
        } 
        ......
        Application app;
		......
        try {
        	// 创建 Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
			......
            mInitialApplication = app;
			......
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            ......
            try {
           		// 内部调用 Application # onCreate() 的方法
                // 故 Application # onCreate() 比 ActivityThread 的 main() 方法慢执行
                // 但是会比所有该应用 Activity 的生命周期先调用,因为此时的 Activity 还没启动
                mInstrumentation.callApplicationOnCreate(app);
            }
            ......
        }
    }
    ......
}

ActivityThread # handleBindApplication() 方法的参数 AppBindData 是 AMS 传给应用程序的启动信息,其中包含 LoadedApk、ApplicationInfo 等,然后通过 LoadedApk 实例对象创建 ContextImpl 和 Application 实例对象。

2.1.5.2 LoadedApk # makeApplication()

LoadedApk.java  (api 30)
public final class LoadedApk {
	......
   @UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
       		// 如果 mApplication 已经存在则直接返回
            return mApplication;
        }
		......
        Application app = null;
        // 获取 AndroidMenifest 中 application 标签指定的 Application 类
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
        	// 获取 ClassLoader
            final java.lang.ClassLoader cl = getClassLoader();
            ......
            // 创建 ContextImpl 实例
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            ......
            // 利用类加载器 ClassLoader 创建 AndroidMenifest 指定的 Application 类
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
			// 将 Application 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Application
            appContext.setOuterContext(app);
        }
        ......
        mActivityThread.mAllApplications.add(app);
        // app 赋值给 mApplication,当我们调用 Context.getApplicationContext() 就是获取这个对象
        mApplication = app;
        if (instrumentation != null) {
            try {
            	// 由于 instrumentation 此时为空所以不会回调 Application 的 onCreate 方法
                instrumentation.callApplicationOnCreate(app);
            }
            ......
        }
		......
        return app;
    }
    ......
}

执行流程如下:

  • 首先判断 LoadedApk 对象中的 mApplication 是否存在,如果已经存在则直接返回。如果不存在,则先获取 AndroidMenifest 中 application 标签中 name 属性指定的 Application 类名。然后获取 ClassLoader,创建 ContextImpl 实例。
  • 通过类加载器 ClassLoader 创建 Application 类实例,并将 Application 实例赋值给 ContextImpl 的成员变量 mOuterContext,以便 ContextImpl 通过 mOuterContext 访问 Application 实例。同时将 Application 实例赋值给 mApplication,调用 Context # getApplicationContext() 方法获取的就是该实例对象。

2.1.5.3 ContextImpl 创建

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @UnsupportedAppUsage
    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        return createAppContext(mainThread, packageInfo, null);
    }

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
            String opPackageName) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,
                0, null, opPackageName);
        context.setResources(packageInfo.getResources());
        context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
        return context;
    }
    ......
}

创建一个 ContextImpl 实例对象,同时给 ContextImpl 赋值访问系统资源相关的"权限"对象 -- ActivityThread、LoadedApk 等。

2.1.5.4 Application 创建

回头继续看 LoadedApk # makeApplication() 的 Application 类实例的创建,代码如下:

Instrumentation.java (api 30)
public class Instrumentation {
	......
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
		// 获取 LoadedApk 的 AppComponentFactory,然后通过 ClassLoader 
		// 加载 Application 类,并创建类的实例对象
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
		// 将 ContextImpl 实例绑定到 Application 实例对象的 mBase 成员变量
        app.attach(context);
        return app;
    }

    private AppComponentFactory getFactory(String pkg) {
        ......
        LoadedApk apk = mThread.peekPackageInfo(pkg, true);
        // This is in the case of starting up "android".
        if (apk == null) apk = mThread.getSystemContext().mPackageInfo;
        return apk.getAppFactory();
    }
    ......
}

AppComponentFactory.java (api 30)
public class AppComponentFactory {
	......
    public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
            @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		// 通过 ClassLoader 加载 Application 类,并创建类的实例对象
        return (Application) cl.loadClass(className).newInstance();
    }
    ......
}

获取 LoadedApk 的 AppComponentFactory,然后通过 ClassLoader 加载 Application 类,并创建类的实例对象。接下来将 ContextImpl 实例赋值给创建的 Application 实例对象的 mBase 成员变量。

2.1.5.5 Application 绑定 ContextImpl

Application.java (api 30)
public class Application extends ContextWrapper implements ComponentCallbacks2 {
	......
    @UnsupportedAppUsage
    /* package */ final void attach(Context context) {
    	// 这里 context 为 ContextImpl 实例对象
    	// 调用 ContextWrapper # attachBaseContext() 方法
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        // 将 ContextImpl 实例对象赋值给成员变量 mBase
        mBase = base;
    }
    ......
}

在 Application 的 attach() 方法中调用 ContextWrapper 的 attachBaseContext() 方法,将 ContextImpl 实例对象赋值给其成员变量 mBase。

2.2 Service Context

Service 是 Android 系统框架中四大组件的其中之一,它是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。可由其他应用组件启动(如:Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响,接下来分析其 Context 实例的由来。

2.2.1 Service Context 创建过程

Service 的 Context 是在 Service 被其他应用组件启动的时候创建的(如:Activity 中启动),这里不详细分析 Service 的启动过程,也不是本篇文章的重点。具体可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Service 启动过程

简述一下过程:

  • 通常都是调用 Context # startService() 方法启动 Service,通过上面的分析可知,这里将调用其实现类 ContextImpl # startService() 方法,然后通过 Binder 通信跨进程调用到 system_server 进程中 AMS 的 startService() 方法,在系统进程中经过一系列调用后,流程走到 ApplicationThread 的 scheduleCreateService() 方法,在方法中将 AMS 进程中创建的 ServiceInfo 等封装成 CreateServiceData 对象。
  • ApplicationThread # scheduleCreateService() 方法中将 CreateServiceData 实例对象通过内部类 H 发送 CREATE_SERVICE 类型的 Handler 消息,进而调用到 ActivityThread # handleCreateService() 方法创建 Service,同时创建 Service 的 Context。

2.2.2 时序图

2.2.3 源码解析

通过过程简述与时序图可知,Service 的 Context 的创建是在 ActivityThread # handleCreateService() 方法中创建的,跟踪查看源码进行详细解析。

2.2.3.1 ActivityThread # handleCreateService()

ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
    @UnsupportedAppUsage
    private void handleCreateService(CreateServiceData data) {
		......
		// 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
  			......
  			// 创建 ContextImpl 实例,即 Service 的上下文环境 Context[参见 2.1.5.3 节]
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            // 创建 Application 实例[参见 2.1.5.2 节]
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            // 获取 ClassLoader 实例对象
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            // 加载 Service 类并创建实例对象
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
			......
            context.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
			// 将 Service 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Service
            context.setOuterContext(service);
            // 将 ContextImpl 实例绑定到 Service 实例对象的 mBase 成员变量
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
			// 回调 Service # onCreate() 方法
            service.onCreate();
            // 将 service 对象加入到 mService 集合中,key 值为 data.token
            mServices.put(data.token, service);
            try {
            	// 跨进程调用 AMS # serviceDoneExecuting() 方法通知 AMS,Service 已经启动完毕
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } 
        ......
    }
    ......
}

执行流程如下:

  • 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等,然后创建一个 ContextImpl 实例对象,即 Service 的上下文环境,同时给 ContextImpl 赋值访问系统资源相关的"权限"对象 -- ActivityThread、LoadedApk 等。
  • 获取类加载器 ClassLoader 实例对象,并使用它加载 Service 类并创建实例对象,将创建的 Service 实例赋值给 ContextImpl 的成员变量 mOuterContext,以便 ContextImpl 通过 mOuterContext 访问 Service 实例。
  • 调用 Service # attach() 方法将 ContextImpl 实例绑定到 Service 实例对象的 mBase 成员变量,然后回调 Service # onCreate() 方法,最后通过跨进程调用 AMS # serviceDoneExecuting() 方法通知 AMS,Service 已启动完毕。

2.2.3.2 Service 绑定 ContextImpl

Service.java (api 30)
public abstract class Service extends ContextWrapper implements ComponentCallbacks2,
        ContentCaptureManager.ContentCaptureClient {
	......
    @UnsupportedAppUsage
    public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
		// 继续调用 Service # attachBaseContext() 方法
        attachBaseContext(context);
        ......
        setContentCaptureOptions(application.getContentCaptureOptions());
    }
    
    @Override
    protected void attachBaseContext(Context newBase) {
    	// 调用父类 ContextWrapper # attachBaseContext() 方法
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setContentCaptureOptions(getContentCaptureOptions());
        }
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        // 将 ContextImpl 实例对象赋值给成员变量 mBase
        mBase = base;
    }
    ......
}

在 Service 的 attach() 方法中继续调用 Service # attachBaseContext() 方法,然后继续调用父类 ContextWrapper # attachBaseContext() 方法将 ContextImpl 实例对象赋值给成员变量 mBase。

2.3 Activity Context

Activity 是 Android 系统框架中四大组件中使用频率最多的,是用来给用户展示内容的界面,并与用户直接进行交互的组件。日常开发中 Activity 的 Context 会被经常用到,如通过 Context 启动新的 Activity、启动 Service 及注册广播接收器等,下面一起来看一下 Activity 的 Context 实例的由来。

2.3.1 Activity Context 创建过程

Activity 的 Context 是在 Activity 组件启动的时候创建的,这里不详细分析 Activity 的启动过程,也不是本篇文章的重点,感兴趣的同学可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程

简述一下过程:

  • 通常是调用 Activity # startActivity() 方法来启动 Activity,然后通过 Binder 通信跨进程调用到 system_server 进程中 ATMS 的 startActivity() 方法,在系统进程中经过一系列调用后,流程走到 ApplicationThread 的 scheduleTransaction() 方法。
  • ApplicationThread # scheduleTransaction() 方法中根据生命周期状态,来调度启动 Activity 的事务 LaunchActivityItem,在 LaunchActivityItem # execute() 方法中调用到 ActivityThread # handleLaunchActivity() 方法来创建并启动 Activity,同时创建 Activity 的 Context。

Android P(9.0) 开始 Activity 启动及生命周期有关的逻辑,被解耦成多个 Transaction 事务(如:LaunchActivityItem、ResumeActivityItem 等),通过 ClientLifecycleManager 来调度事务的执行。

2.3.2 时序图

2.3.3 源码解析

通过过程简述与时序图可知,Activity 的 Context 的创建是在 ActivityThread # handleLaunchActivity() 方法中创建的,跟踪查看源码进行详细解析。

2.3.3.1 ActivityThread # handleLaunchActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        ......
        final Activity a = performLaunchActivity(r, customIntent);
        ......
        return a;
    }
    ......
}

继续调用 ActivityThread # performLaunchActivity() 执行 Activity 的启动,继续跟踪启动流程。

2.3.3.2 ActivityThread # performLaunchActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
     /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
        	// 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
    	......
		// 创建 ContextImpl 实例,即 Activity 的上下文环境 Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
        	// 通过类加载器 ClassLoader 加载并新建 Activity 的实例
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ......
        } catch (Exception e) {
            ......
        }
        try {
        	// 创建 Application,注意 r.packageInfo 是前面获取的 LoadedApk 实例对象
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
			......
            if (activity != null) {
                ......
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
                // 将 Activity 实例赋值给 ContextImpl,以便 ContextImpl 可以访问 Activity
                appContext.setOuterContext(activity);
                // 执行 Activity 的 attach、初始化 Window 等并把 ContextImpl 实例设置给 Activity
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
                ......
                activity.mCalled = false;
                // 执行 Activity 的 onCreate
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ......
            }
            // 设置生命周期的状态为 ON_CREATE
            r.setState(ON_CREATE);
            synchronized (mResourcesManager) {
            	// 将包含 Activity 信息集的 r 对象,也就是 ActivityClientRecord
            	// 加入到 mActivities 中,r.token 为 key 值
                mActivities.put(r.token, r);
            }
        }
		......
        return activity;
    }
    ......
}

执行流程如下:

  • 获取 LoadedApk 实例对象,用于获取类加载器 ClassLoader 等,继续调用 ActivityThread # createBaseContextForActivity() 方法,该方法中调用 ContextImpl # createActivityContext() 方法创建 ContextImpl 实例对象,即 Activity 的上下文环境 Context。
  • 调用 Instrumentation # newActivity() 方法加载并新建 Activity 实例对象,该方法中调用 AppComponentFactory # instantiateActivity() 方法,然后通过在 ActivityThread # performLaunchActivity() 方法中获取的类加载器 ClassLoader 加载并新建 Activity 实例对象。
  • 通过 LoadApk # makeApplication() 方法创建一个 Application 对象,过程跟加载并新建 Activity 类似,用到类加载器 ClassLoader。
  • 执行 Activity # attach() 方法,通过该方法将 ContextImpl 实例设置给 Activity,除此之外,方法中还完成了 Window 实例的创建并建立自己和 Window 的关联,这样当 Window 接收到外部输入事件后就可以将事件传递给 Activity。
  • 执行 Instrumentation # callActivityOnCreate() 方法,该方法中调用 Activity # performCreate() 方法,Activity # performCreate() 方法中调用 Activity # onCreate() 方法。

2.3.3.3 ActivityThread # createBaseContextForActivity()

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
	private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ......
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
		......
        return appContext;
    }
    ......
}

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @UnsupportedAppUsage
    static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
		......
		// 通过 LoadedApk 获取类加载器 ClassLoader
        ClassLoader classLoader = packageInfo.getClassLoader();
        ......
        // 创建 Activity 的 ContextImpl 实例对象
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
                activityInfo.splitName, activityToken, null, 0, classLoader, null);
        ......
        return context;
    }
    ......
}

只看主流程有关源代码,调用 ContextImpl # createActivityContext() 创建 Activity 的 Context。

至于主流程中 Activity 和 Application 的加载并新建过程感兴趣的可以跟进源码查看,主要是由类加载器 ClassLoader 加载后新建实例对象,下面主要来查看 Activity 绑定 Context 的流程。

2.3.3.4 Activity # attach()

ContextImpl.java (api 30)
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2, ...... {
	......
	@UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,...) {
        // 将 ContextImpl 实例绑定到 Activity 实例对象的 mBase 成员变量
        attachBaseContext(context);
		......
		// 新建 PhoneWindow 实例
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ......
        mUiThread = Thread.currentThread();
        ......
        mWindowManager = mWindow.getWindowManager();
		......
    }
	......
    @Override
    protected void attachBaseContext(Context newBase) {
    	// 继续调用父类的 attachBaseContext() 方法
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setAutofillClient(this);
            newBase.setContentCaptureOptions(getContentCaptureOptions());
        }
    }
    ......
}

ContextThemeWrapper.java (api 30)
public class ContextThemeWrapper extends ContextWrapper {
	......
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }
    ......
}

ContextWrapper.java (api 30)
public class ContextWrapper extends Context {
	......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    ......
}

在 Activity 的 attach() 方法中继续调用 Activity # attachBaseContext() 方法,然后继续调用父类 ContextThemeWrapper # attachBaseContext() 方法,由于 ContextThemeWrapper 继承自 ContextWrapper,因此继续调用 ContextWrapper # attachBaseContext() 方法将 ContextImpl 实例对象赋值给成员变量 mBase。

2.4 总结

通过源码的深入分析可知,Application、Service 和 Activity 直接或间接的继承自 ContextWrapper 类,因此也都拥有了一个 Context 类型的成员变量 mBase 指向一个 ContextImpl 对象,ContextImpl 是 Context 类的具体实现类,所以它们也就都拥有了 Context 提供的获取应用环境全局信息的接口功能。

三. Context 补充知识

前面的章节分析了 Application、Service 和 Activity 等组件的创建以及绑定 ContextImpl 实例对象的流程,有同学会问了,四大组件中的 BroadcastReceiver 和 ContentProvider 创建的过程中没有绑定 ContextImpl 实例对象吗?

其实它们也有绑定的,只不过不是自身继承自 Context,其 Context 实例是需要通过前面所述三者来提供,大致来看一下源码。

3.1 BroadcastReceiver 获取 Context 实例

开发中,通常是通过调用 Context # registerReceiver() 方法来注册广播接收器,这里的 Context 根据注册广播接收器时的场景可以是前面所述三者的任意一个来提供,这里以 Activity 场景中注册为例,调用 Context # registerReceiver() 方法注册,由上面的分析可知,此时会调用到 Context 的实现类 ContextImpl # registerReceiver() 方法中,代码如下:

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return registerReceiver(receiver, filter, null, null);
    }
    
    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        return registerReceiverInternal(receiver, getUserId(),
                filter, broadcastPermission, scheduler, getOuterContext(), 0);
    }
    ......
    @UnsupportedAppUsage
    final Context getOuterContext() {
        return mOuterContext;
    }
    ......
}

注册广播接收器时,继续调用 ContextImpl # registerReceiverInternal() 方法并传入当前所处的上下文环境 - 即 Context,这里通过 ContextImpl # getOuterContext() 获取该 Context 实例,这个方法是不是看着很熟悉,在前面 2.1.5.2 、2.2.3.1 及 2.3.3.2 小节中,通过 ContextImpl # setOuterContext() 方法为其赋值的,这也验证了上面的解析,Context 实例的获取是根据注册广播接收器时所处的场景来决定到底获取的是前面所述三者中的哪一个。

3.2 ContentProvider 获取 Context 实例

ContentProvider 是四大组件中被使用频率最低的一个,通常用来做跨进程共享数据,它是伴随着应用程序的启动由系统创建的,但它本身不属于 Context 体系结构,因此创建 ContentProvider 实例时所用的 Context 实例需要由别处获得。既然这样那就先看看在应用程序启动过程中的哪里创建的 ContentProvider 实例?

3.2.1 时序图

应用程序启动的流程这里不做详细解读,可以参考这篇文章的详细分析-- 深度详解 Android R(11.0)Activity 启动过程。应用程序在创建并绑定 Application 后,通过 ActivityThread # installContentProviders() 方法来创建并绑定 Context 的实例,一起探索源码来验证一下。

3.2.2 源码分析

3.2.2.1 ActivityThread # handleBindApplication()

ActivityThread.class (api 30)
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
    ......
 	@UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
        ......
        Application app;
		......
        try {
        	// 创建 Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ......
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                	// 创建 ContentProvider 实例,注意入参是 Application 实例
                    installContentProviders(app, data.providers);
                }
            }
            ......
            try {
           		// 内部调用 Application # onCreate() 的方法
                mInstrumentation.callApplicationOnCreate(app);
            }
            ......
        }
    }
    
    @UnsupportedAppUsage
    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            ......
            // 继续调用 installProvider() 方法创建 ContentProvider 实例
            ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }
        try {
        	// 将 ContentProvider 列表发布到 AMS 中目的是进行缓存
        	// 其它应用进程想要获取它的 ContentProvider 的时候可以直接在缓存中遍历获取
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

	@UnsupportedAppUsage
    private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        if (holder == null || holder.provider == null) {
            ......
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
            	// 包名相同即同一应用内,则使用入参 Application 作为 Context
                c = context;
            }
			......// 根据不同使用场景,获取对应场景下的 Context 实例
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                ......
                // 内部通过 ClassLoader 加载并新建 ContentProvider 实例对象
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);
                provider = localProvider.getIContentProvider();
                ......
                // XXX Need to create the correct context for this provider.
                // 为 ContentProvider 设置合适的上下文环境 - Context
                localProvider.attachInfo(c, info);
            }
            ......
        } 
        ......
    }
    ......
}

在 ActivityThread # handleBindApplication() 方法中,调用 ActivityThread # installContentProviders() 方法并传入创建好的 Application 实例对象,继续调用 ActivityThread # installProvider() 方法来创建 ContentProvider 实例对象,创建过程跟上面分析的差不多,通过类加载器 ClassLoader 加载并新建 ContentProvider 实例对象,最后调用 ContentProvider # attachInfo() 方法为 ContentProvider 设置合适的上下文环境 - Context。

3.2.2.2 ContentProvider # attachInfo()

ContentProvider.class (api 30)
public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {
    ......
    public void attachInfo(Context context, ProviderInfo info) {
        attachInfo(context, info, false);
    }

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;
        mCallingPackage = new ThreadLocal<>();
        // 这里只允许设置一次,因此 ContentProvider 创建交付使用后,客户端不能再更改它
        if (mContext == null) {
            mContext = context;
            ......
            // 回调 ContentProvider # onCreate() 方法
            ContentProvider.this.onCreate();
        }
    }
    ......
}

ContentProvider # attachInfo() 方法中将 Context 实例对象赋值给 ContentProvider 的成员变量 mContext,这样 ContentProvider 就可以使用 Context 提供的获取应用环境全局信息的接口功能,而这个 Context 也正是一开始 ActivityThread # handleBindApplication() 方法中传进来的 Application 实例对象(注意:成员变量 mContext 只允许设置一次)。

注意:在 ContentProvider # installProvider() 方法中会根据不同使用场景,获取对应场景下的 Context 实例对象,我们这里是分析的是同一个应用程序内,所以 ContentProvider 的成员变量 mContext 被赋值为传进来的 Application 实例对象。如果跨进程或者通过 Intent # setPackage() 指定了其它应用的包名等,则需要获取对应场景下的 Context 实例对象。

3.3 总结

本节内容补充了四大组件中 BroadcastReceiver 和 ContentProvider 是如何获取到 Context 实例对象的,它们虽是系统组件,但不是 Context 体系结构中的一员,但身为系统组件,它们同样需要用到 Context 所提供的获取应用环境全局信息的接口功能,因此抱着深入学习的态度,还是细细的把源码流程研读了一遍,梳理其创建及获取 Context 实例对象的流程。

四. 总结

结合本文的讲解和源码解析,这里来看一下那些面试中问到过的问题,加深一下理解。

问题一:Android 系统中一个应用程序中 Context 的个数?

通过本文的详解可知,在 Context 体系结构中,Application 、Activity 和 Service 在创建实例对象的同时,都会创建一个 ContextImpl 实例对象,并赋值给它们父类 ContextWrapper 的成员变量 mBase,由于子类对象拥有父类对象中所有的属性和方法,因此在 Application 、Activity 和 Service 实例对象中可以通过成员变量 mBase 获取 ContextImpl 实例对象。也就是说每个 Activity 和 Service 都有一个 Context,而每个应用程序中 Application 由于是唯一的,所以 Android 系统中一个应用程序中 Context 的个数 = Activity 的个数 + Service 的个数 + 1。

问题二:Context 会导致内存泄露吗?

一般 Context 导致的内存泄漏,几乎都是当 Context 销毁的时候,却因为被引用导致 GC 销毁失败,而 Application 的 Context 对象可以理解为随着应用进程存在的,所以这里总结给出使用 Context 时的一些建议:

  • 当 Application 的 Context 能搞定的情况下,且生命周期较长的对象,优先使用 Application 的 Context。
  • 不要让生命周期长于 Activity 的对象持有 Activity 的引用。
  • 尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,推荐使用静态内部类,将外部实例引用作为弱引用持有。

问题三:getContext(),getBaseContxet(),getApplication() 及 getApplicationContext() 的区别?

在文章中已经解析过,getApplication() 和 getApplicationContext() 这俩个方法获取到的是同一个实例对象,只是使用场景范围的不同。getApplication() 方法更加直观,但只能在 Activity 和 Service 场景中调用。getApplicationContext() 方法适用范围更广,任意场景中通过 Context 对象皆可以调用此方法。那为何是同一个对象呢?简单看一下源码:

Activity.class (api 30)
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,... {
	......
	@UnsupportedAppUsage
    private Application mApplication;
    
    public final Application getApplication() {
        return mApplication;
    }
    @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,...
            Application application, ...) {
        ......
        mApplication = application;
		......
    }
}

Service.class (api 30)
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
	......
	@UnsupportedAppUsage
    private Application mApplication;
    
    public final Application getApplication() {
        return mApplication;
    }
    @UnsupportedAppUsage
    public final void attach(
            Context context,...
            Application application, Object activityManager) {
        ......
        mApplication = application;
        ......
    }
}

首先看到 getApplication() 方法返回的是 Activity 和 Service 调用 attach() 方法时传入的 Application 实例对象。还记得文章前面的分析不,Activity # attach() 方法的调用,参见 2.3.3.2 ActivityThread # performLaunchActivity(),而 Service # attach() 方法的调用,参见 2.2.3.1 ActivityThread # handleCreateService() ,在这两个方法中传给 attach() 方法的 Application 实例对象都是通过 LoadedApk # makeApplication() 方法来创建获取的。

再来看一下 getApplicationContext() 方法的返回值,虽然调用到 ContextWrapper,但最终还是委托给实现类 ContextImpl 中实现的,源码如下:

ContextImpl.java (api 30)
class ContextImpl extends Context {
	......
	@UnsupportedAppUsage
    final @NonNull ActivityThread mMainThread;
    @UnsupportedAppUsage
    final @NonNull LoadedApk mPackageInfo;
    
    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }
    ......
}

这里根据 mPackageInfo 是否为空,分别调用了mPackageInfo # getApplication() 方法和mMainThread # getApplication() 方法,那就来看看这两个方法,首先在 LoadedApk 中看一下 getApplication() 方法的返回值,代码如下:

LoadedApk.java (api 30)
public final class LoadedApk {
	......
	@UnsupportedAppUsage
    private Application mApplication;

    Application getApplication() {
        return mApplication;
    }

	@UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
		......
        Application app = null;
		......
        try {
        	......
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        }
        ......
        mApplication = app;
		......
        return app;
    }
    ......
}

首先判断 LoadedApk 中的 mApplication 是否为空 (保证对象的单例),不为空直接返回,如果为空的话新建了一个 Application 实例对象然后赋值给 mApplication。接着在 ActivityThread 中看一下 getApplication() 方法的返回值,代码如下:

ActivityThread.java (api 30)
public final class ActivityThread extends ClientTransactionHandler {
	......
    @UnsupportedAppUsage
    Application mInitialApplication;
    
    @UnsupportedAppUsage
    public Application getApplication() {
        return mInitialApplication;
    }
    ......
    @UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
		......
        Application app;
        try {
        	// data.info 是 LoadedApk 类
            app = data.info.makeApplication(data.restrictedBackupMode, null);
			......
            mInitialApplication = app;
			......
        }
	......
}

ActivityThread # getApplication() 方法返回的 mInitialApplication 也是 LoadedApk # makeApplication() 方法返回的,所以可以得出 getApplicationContext() 方法在上述两种情况下返回的是同一个 Application 实例对象。

由于 getApplication() 方法返回的 Application 实例对象也是通过 LoadedApk # makeApplication() 方法来创建获取的,所以说 getApplication() 和 getApplicationContext() 这俩个方法返回的是同一个 Application 实例对象。

getContext()、getBaseContxet() 和 getApplicationContext() 方法的区别?

首先 getBaseContxet() 方法获取的是前面分析的赋值给 mBase 的 Context 的实现类的实例对象。getApplicationContext() 方法返回的 LoadedApk # makeApplication() 方法创建的 Application 实例对象。并且 Application、Activity 和 Service 都有 getBaseContxet() 和 getApplicationContext() 这两个方法。而 getContext() 方法是在 Fragment 或 View 中用来获取其宿主对象的。

五,参考

  1. 全面解析之Context机制
相关推荐
Vincent(朱志强)4 分钟前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
职业考试资料墙8 分钟前
水利水电安全员考试题库及答案
学习·考试·题库
MUTA️9 分钟前
RT-DETR学习笔记(2)
人工智能·笔记·深度学习·学习·机器学习·计算机视觉
炭烤玛卡巴卡34 分钟前
初学elasticsearch
大数据·学习·elasticsearch·搜索引擎
mmsx36 分钟前
android 登录界面编写
android·登录界面
姜毛毛-JYM36 分钟前
【JetPack】Navigation知识点总结
android
oneouto1 小时前
selenium学习笔记(一)
笔记·学习·selenium
张铁铁是个小胖子1 小时前
MyBatis学习
java·学习·mybatis
我曾经是个程序员1 小时前
鸿蒙学习记录之http网络请求
服务器·学习·http
m0_748232391 小时前
WebRTC学习二:WebRTC音视频数据采集
学习·音视频·webrtc