Android Learning Road (14) Context Detailed Explanation

1. Introduction

In Android development or interviews, the four major components are inseparable. When creating or starting these components, you cannot directly create instance objects through the new keyword followed by the class name. Instead, you need to have their own The context environment is the Context to be discussed in this article.

1.1 Context Overview

Context, literally means: context, environment, context. In the Android system, it can be understood as the working environment of the current object in the application. It internally defines many interfaces for accessing global information in the application environment. Through it, you can access classes related to the application's resources, such as Resources, AssetManager, Package and permission-related information. It can also be used to call application-level operations, such as starting activities and services, sending broadcasts, etc.

Let’s take a look at the official comments on the Context class:

/**
 * 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 {...}

Translation: Context provides an interface for global information about the application environment. It is an abstract class and its implementation is provided by the Android system. It allows access to resources and types that characterize the application and is a context that governs some resources (application environment variables, etc.).

1.2 Context architecture

  • Context: is an abstract class that defines a series of interfaces for interacting with the system.
  • ContextWrapper: Inherited from the Context abstract class, it is a wrapper class (decorator mode) of the Context class. It internally maintains a Context type member variable mBase pointing to a ContextImpl object. The method calls in ContextWrapper are to call the methods in ContextImpl through mBase. Here Proxy mode is used.
  • ContextImpl: Inherits from the Context abstract class, implements the abstract methods in the Context class, and is the specific implementation class of the Context class. It provides a context environment for Activity and other application components, and the Context methods used in the application are implemented by it.
  • ContextThemeWrapper: Inherited from the ContextWrapper class, adding logic related to the theme on the basis of ContextWrapper, that is, you can specify the Context wrapper class of Theme, which is used to provide the Theme attribute set for the View when it is constructed.

The main core classes involved in the ContextImpl implementation class are: ActivityThread, LoadedApk, PackageManager and ResourcesManager. These classes are all singletons and share the same object in an application process.
Contextlmpl is a lightweight class, while LoadedApk is a heavyweight class. Most of the heavyweight functions for package operations in Contextlmpl actually turn to the corresponding methods of the LoadedApk object.

Activity inherits from ContextThemeWrapper, and Application and Service inherit from ContextWrapper. They inherit directly or indirectly from the ContextWrapper class, so they also have a member variable mBase of the Context type that points to a ContextImpl object. ContextImpl is the specific implementation class of the Context class, so they all Has all the functions provided by Context.

Proxy mode: It belongs to the structural mode, which refers to providing a proxy for other objects to control access to this object. The proxy mode is divided into static proxy and dynamic proxy.
Decorator pattern: Also called packaging pattern, it is also a structural pattern. It refers to a pattern that dynamically adds some responsibilities to the object (that is, adds its additional functions) without changing the structure of the existing object.

1.3 Context scope

Note: If you need to load the layout display interface, use Activity as the Context field as much as possible. Although you can use Application and Service as the Context field to load the layout and start the Activity, it is not recommended to avoid errors or the UI inexplicably uses the system default theme. exhibit.

1.4 Summary

Through the analysis in this section, we have a simple understanding of the concept, architecture and scope of Context, so let’s explore it in depth!

2. Context detailed explanation

When we talked about the architecture of Context earlier, we learned that its final implementation classes are: Application, Service and Activity. They all hold the real implementation of the Context abstract class ContextImpl. Next, we will discuss and analyze these three implementation classes respectively.

2.1 Application Context

Application is a system component in the Android system framework. When an Android application is started, the system will create one and only one Application class object to store some system information, that is, Application is a singleton.

Usually there is no need to specify an Application during the development process. The system automatically creates it for developers. If you want to create an application-customized Application, you only need to create a class that inherits Application and register it in the application tag in the AndroidManifest.xml file ( Just add the name attribute to the application tag and add the custom Application name).

Usually the purpose of customizing the Application is to do some global initialization work when the application starts. When the application starts, the Application is created and started synchronously. The system will create a PID, which is the process ID, and all activities will be on this process. When running, the values ​​of these initialized global variables can be obtained, and since the Application object will always exist during the entire application running, some developers will write some tool methods in the Application to obtain and use them globally, but remember not to In this way, Application is used as a tool class. Note: This seriously violates Google's principles for designing Applications, and also violates the single responsibility principle in design patterns.

2.1.1 Custom Application Instance

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

    private fun initSDKs() {...}
}

Inherit Application and override the onCreate() method, which is called when the Application is created. It is generally used for global initialization, such as the initialization of third-party SDKs, environment configuration, etc. At the same time, the global Context object of the Application type can be obtained through TestApplication # context. .

2.1.2 Get Application instance

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 = [email protected]()
        val application = [email protected]()

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

There are generally two methods to obtain Application:

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

Application can be obtained through both getApplication() and getApplicationContext(), so what is the difference between them?

From the log output, we can see that they obtain the same object, but some students want to ask, why do we need to provide two methods with the same function? Because the getApplication() method is more intuitive, but it can only be called in Activity and Service scenarios. The getApplicationContext() method has a wider scope of application. This method can be called through the Context object in any scenario.

2.1.3 Application Context creation process

The Context of Application is created when the application is created. To track its creation, you need to explore it from the startup process of the application, that is, from clicking on the desktop application icon to a certain step in the process when the first interface of the application is displayed. For the specific step of creation, you can refer to the detailed analysis of this article - an in-depth explanation of the Android R (11.0) Activity startup process .

Let’s briefly describe the process:

  • The ActivityThread class serves as the application initialization class. It calls ActivityThread in its entry method main() method # attach() method, and then calls the attachApplication() method of AMS in the system_server process across processes through Binder communication, and passes ApplicationThread as a parameter. .
  • Through the passed-in ApplicationThread, cross-process communication calls the ApplicationThread # bindApplication() method in the application process to bind the Application.
  • ApplicationThread # bindApplication() method, build the AppBindData object, and then send the BIND_APPLICATION type Handler message through the inner class H, and then call the ActivityThread # handleBindApplication() method to create and bind the Application.

2.1.4 Timing diagram

Interview question: Is ActivityThread a Thread?
The ActivityThread class is an application initialization class. Its main() method is the entry method of the application. It is also what we call the "main thread". However, ActivityThread itself is not a thread. The reason why it is called the "main thread" is because it runs in the main thread. So ActivityThread is part of the main thread, but it does not represent the main thread. Its function is as follows:

  • ActivityThread is responsible for creating the Application object and managing its life cycle method calls.
  • ActivityThread manages the life cycle method calls of the four major components.

2.1.5 Source code analysis

From the process description and sequence diagram, we can see that the Application's Context is created in the ActivityThread # handleBindApplication() method. Track and view the source code for detailed analysis.

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 # The parameter AppBindData of the handleBindApplication() method is the startup information passed by AMS to the application, which includes LoadedApk, ApplicationInfo, etc., and then creates ContextImpl and Application instance objects through the LoadedApk instance object.

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;
    }
    ......
}

The execution process is as follows:

  • First determine whether mApplication in the LoadedApk object exists. If it already exists, return it directly. If it does not exist, first obtain the Application class name specified by the name attribute in the application tag in AndroidMenifest. Then get the ClassLoader and create a ContextImpl instance.
  • Create an Application class instance through the class loader ClassLoader, and assign the Application instance to the member variable mOuterContext of ContextImpl so that ContextImpl can access the Application instance through mOuterContext. At the same time, assign the Application instance to mApplication, and call the Context # getApplicationContext() method to obtain the instance object.

2.1.5.3 ContextImpl creation

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;
    }
    ......
}

Create a ContextImpl instance object, and assign to ContextImpl the "permission" objects related to accessing system resources – ActivityThread, LoadedApk, etc.

2.1.5.4 Application creation

Let’s go back and look at the creation of the Application class instance of LoadedApk # makeApplication(). The code is as follows:

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();
    }
    ......
}

Get the AppComponentFactory of LoadedApk, then load the Application class through ClassLoader, and create an instance object of the class. Next, assign the ContextImpl instance to the mBase member variable of the created Application instance object.

2.1.5.5 Application binding 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;
    }
    ......
}

In the attach() method of Application, call the attachBaseContext() method of ContextWrapper and assign the ContextImpl instance object to its member variable mBase.

2.2 Service Context

Service is one of the four major components in the Android system framework. It is an application component that can perform long-running operations in the background without a user interface. It can be started by other application components (such as Activity). Once the service is started, it will always run in the background. Even if the component (Activity) that starts the service is destroyed, it will not be affected. Next, analyze the origin of its Context instance.

2.2.1 Service Context creation process

The Context of the Service is created when the Service is started by other application components (such as starting in Activity). The startup process of the Service will not be analyzed in detail here, nor is it the focus of this article. For details, please refer to the detailed analysis of this article - an in-depth explanation of the Android R (11.0) Service startup process .

Let’s briefly describe the process:

  • The Context # startService() method is usually called to start the Service. As can be seen from the above analysis, the ContextImpl # startService() method of its implementation class will be called here, and then the startService() method of AMS in the system_server process is called across processes through Binder communication. After a series of calls in the system process, the process goes to the scheduleCreateService() method of ApplicationThread, in which the ServiceInfo created in the AMS process is encapsulated into a CreateServiceData object.
  • ApplicationThread # In the scheduleCreateService() method, the CreateServiceData instance object sends a Handler message of the CREATE_SERVICE type through the internal class H, and then calls the ActivityThread # handleCreateService() method to create the Service and create the Context of the Service.

2.2.2 Timing diagram

2.2.3 Source code analysis

From the process description and sequence diagram, we can see that the Service's Context is created in the ActivityThread # handleCreateService() method. Track and view the source code for detailed analysis.

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();
            }
        } 
        ......
    }
    ......
}

The execution process is as follows:

  • Obtain the LoadedApk instance object, which is used to obtain the class loader ClassLoader, etc., then create a ContextImpl instance object, which is the context environment of the Service, and assign to the ContextImpl the "permission" objects related to accessing system resources - ActivityThread, LoadedApk, etc.
  • Obtain the ClassLoader instance object and use it to load the Service class and create an instance object. Assign the created Service instance to the member variable mOuterContext of ContextImpl so that ContextImpl can access the Service instance through mOuterContext.
  • Call the Service # attach() method to bind the ContextImpl instance to the mBase member variable of the Service instance object, then call back the Service # onCreate() method, and finally notify AMS that the Service has been started by calling the AMS # serviceDoneExecuting() method across processes.

2.2.3.2 Service binding 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;
    }
    ......
}

Continue to call the Service # attachBaseContext() method in the Service's attach() method, and then continue to call the parent class ContextWrapper # attachBaseContext() method to assign the ContextImpl instance object to the member variable mBase.

2.3 Activity Context

Activity is the most frequently used component among the four major components in the Android system framework. It is an interface used to display content to users and interact directly with users. The Context of Activity is often used in daily development, such as starting a new Activity, starting a Service and registering a broadcast receiver through Context. Let's take a look at the origin of the Context instance of Activity.

2.3.1 Activity Context creation process

The Context of Activity is created when the Activity component is started. Here we do not analyze the startup process of Activity in detail, nor is it the focus of this article. Interested students can refer to the detailed analysis of this article - In-depth explanation of Android R ( 11.0 )Activity startup process .

Let’s briefly describe the process:

  • Usually the Activity # startActivity() method is called to start the Activity, and then the startActivity() method of ATMS in the system_server process is called across processes through Binder communication. After a series of calls in the system process, the process goes to the scheduleTransaction() method of ApplicationThread. .
  • ApplicationThread # scheduleTransaction() method schedules the transaction LaunchActivityItem that starts the Activity according to the life cycle status. In the LaunchActivityItem # execute() method, the ActivityThread # handleLaunchActivity() method is called to create and start the Activity, and at the same time create the Activity's Context.

Android P (9.0) starts Activity startup and life cycle-related logic, which is decoupled into multiple Transaction transactions (such as: LaunchActivityItem, ResumeActivityItem, etc.), and the execution of transactions is scheduled through ClientLifecycleManager.

2.3.2 Timing diagram

2.3.3 Source code analysis

From the process summary and sequence diagram, we can see that the Activity's Context is created in the ActivityThread # handleLaunchActivity() method. Track and view the source code for detailed analysis.

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;
    }
    ......
}

Continue to call ActivityThread # performLaunchActivity() to execute the startup of the Activity and continue to track the startup process.

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;
    }
    ......
}

The execution process is as follows:

  • Obtain the LoadedApk instance object, which is used to obtain the class loader ClassLoader, etc., and continue to call the ActivityThread # createBaseContextForActivity() method, which calls the ContextImpl # createActivityContext() method to create the ContextImpl instance object, which is the context environment Context of the Activity.
  • Call the Instrumentation # newActivity() method to load and create a new Activity instance object, which calls the AppComponentFactory # instantiateActivity() method, and then load and create a new Activity instance object through the class loader ClassLoader obtained in the ActivityThread # performLaunchActivity() method.
  • Create an Application object through the LoadApk # makeApplication() method. The process is similar to loading and creating a new Activity, using the class loader ClassLoader.
  • Execute the Activity # attach() method, through which the ContextImpl instance is set to the Activity. In addition, the method also completes the creation of the Window instance and establishes its association with the Window, so that when the Window receives an external input event, it Events can be passed to Activity.
  • Execute the Instrumentation # callActivityOnCreate() method, which calls the Activity # performCreate() method, and the Activity # performCreate() method calls the Activity # onCreate() method.

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;
    }
    ......
}

Just look at the source code of the main process and call ContextImpl # createActivityContext() to create the Activity's Context.

As for the loading and creation process of Activity and Application in the main process, those who are interested can follow the source code to view it. The main purpose is to create new instance objects after being loaded by the class loader ClassLoader. The following is mainly to view the process of Activity binding 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;
    }
    ......
}

Continue to call the Activity # attachBaseContext() method in the Activity's attach() method, and then continue to call the parent class ContextThemeWrapper # attachBaseContext() method. Since ContextThemeWrapper inherits from ContextWrapper, continue to call the ContextWrapper # attachBaseContext() method to assign the ContextImpl instance object to Member variable mBase.

2.4 Summary

Through in-depth analysis of the source code, it can be seen that Application, Service and Activity directly or indirectly inherit from the ContextWrapper class, so they all have a Context type member variable mBase pointing to a ContextImpl object. ContextImpl is the specific implementation class of the Context class, so they also They all have the interface function provided by Context to obtain global information about the application environment.

3. Context supplementary knowledge

The previous chapters analyzed the creation of components such as Application, Service and Activity and the process of binding ContextImpl instance objects. Some students may ask, are the ContextImpl instance objects not bound during the creation process of BroadcastReceiver and ContentProvider among the four major components?

In fact, they are also bound, but they do not inherit from Context. The Context instance needs to be provided through the three mentioned above. Let's take a rough look at the source code.

3.1 BroadcastReceiver obtains Context instance

During development, broadcast receivers are usually registered by calling the Context # registerReceiver() method. The Context here can be provided by any of the three mentioned above according to the scenario when registering the broadcast receiver. Here, the registration in the Activity scenario is For example, call the Context # registerReceiver() method to register. As can be seen from the above analysis, the ContextImpl # registerReceiver() method of the Context implementation class will be called. The code is as follows:

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;
    }
    ......
}

When registering a broadcast receiver, continue to call the ContextImpl # registerReceiverInternal() method and pass in the current context - that is, Context. Here, the Context instance is obtained through ContextImpl # getOuterContext(). Does this method look familiar? In sections 2.1.5.2, 2.2.3.1 and 2.3.3.2, the value is assigned through the ContextImpl # setOuterContext() method, which also verifies the above analysis. The Context instance is obtained based on the scenario when registering the broadcast receiver. Decide which of the three previously described is to be obtained.

3.2 ContentProvider obtains Context instance

ContentProvider is the least frequently used of the four components. It is usually used to share data across processes. It is created by the system when the application is started, but it does not belong to the Context architecture itself, so it is used when creating a ContentProvider instance. The Context instance needs to be obtained elsewhere. In this case, let's first take a look at where the ContentProvider instance is created during the application startup process?

3.2.1 Timing diagram

The process of application startup will not be explained in detail here. You can refer to the detailed analysis of this article - an in-depth explanation of the Android R (11.0) Activity startup process. After the application creates and binds the Application, it creates and binds an instance of Context through the ActivityThread # installContentProviders() method. Let's explore the source code to verify it.

3.2.2 Source code analysis

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);
            }
            ......
        } 
        ......
    }
    ......
}

In the ActivityThread # handleBindApplication() method, call the ActivityThread # installContentProviders() method and pass in the created Application instance object. Continue to call the ActivityThread # installProvider() method to create the ContentProvider instance object. The creation process is similar to the one analyzed above. Through the class The loader ClassLoader loads and creates a new ContentProvider instance object, and finally calls the ContentProvider # attachInfo() method to set the appropriate context environment - Context for the ContentProvider.

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() method assigns the Context instance object to the member variable mContext of ContentProvider, so that ContentProvider can use the interface function provided by Context to obtain global information about the application environment, and this Context is also the initial ActivityThread # handleBindApplication() method The Application instance object passed in (note: the member variable mContext is only allowed to be set once).

Note: In the ContentProvider # installProvider() method, the Context instance object in the corresponding scenario will be obtained according to different usage scenarios. What we are analyzing here is within the same application, so the member variable mContext of ContentProvider is assigned the value of the passed in Application instance object. If the package name of other applications is specified across processes or through Intent # setPackage(), you need to obtain the Context instance object in the corresponding scenario.

3.3 Summary

This section supplements how BroadcastReceiver and ContentProvider among the four major components obtain Context instance objects. Although they are system components, they are not members of the Context architecture. However, as system components, they also need to use Context. It provides an interface function for obtaining global information of the application environment. Therefore, with an attitude of in-depth learning, I carefully read the source code process and sorted out the process of creating and obtaining Context instance objects.

4. Summary

Combined with the explanation and source code analysis of this article, let's take a look at the questions asked in the interview to deepen our understanding.

Question 1: How many Contexts are there in an application in the Android system?

From the detailed explanation of this article, we can see that in the Context architecture, when Application, Activity and Service create instance objects, they will create a ContextImpl instance object and assign it to the member variable mBase of their parent class ContextWrapper. Since the subclass object has the parent class All properties and methods in the object, so the ContextImpl instance object can be obtained through the member variable mBase in the Application, Activity and Service instance objects. That is to say, each Activity and Service has a Context, and since the Application in each application is unique, the number of Contexts in an application in the Android system = the number of Activities + the number of Services + 1.

Question 2: Will Context cause memory leaks?

Generally, memory leaks caused by Context are almost always when the Context is destroyed, but the GC destruction fails because it is referenced. The Context object of the Application can be understood as existing with the application process, so here is a summary of some issues when using the Context. suggestion:

  • When the Application's Context can be handled, and for objects with a long life cycle, the Application's Context will be used first.
  • Do not let objects with a life cycle longer than the Activity hold a reference to the Activity.
  • Try not to use non-static inner classes in Activity, because non-static inner classes will implicitly hold references to external class instances. It is recommended to use static inner classes and hold external instance references as weak references.

Question 3: What are the differences between getContext(), getBaseContxet(), getApplication() and getApplicationContext()?

As analyzed in the article, the two methods getApplication() and getApplicationContext() obtain the same instance object, but the scope of the usage scenario is different. The getApplication() method is more intuitive, but can only be called in Activity and Service scenarios. The getApplicationContext() method has a wider scope of application. This method can be called through the Context object in any scenario. So why is it the same object? Take a quick look at the source code:

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;
        ......
    }
}

First of all, we see that the getApplication() method returns the Application instance object passed in when the Activity and Service call the attach() method. Still remember the analysis earlier in the article, for the invocation of the Activity # attach() method, see 2.3.3.2 ActivityThread # performLaunchActivity(), and for the invocation of the Service # attach() method, see 2.2.3.1 ActivityThread # handleCreateService(). Between these two The Application instance objects passed to the attach() method in each method are created and obtained through the LoadedApk # makeApplication() method.

Let’s take a look at the return value of the getApplicationContext() method. Although the ContextWrapper is called, it is ultimately delegated to the implementation class ContextImpl. The source code is as follows:

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();
    }
    ......
}

Here, depending on whether mPackageInfo is empty, the mPackageInfo # getApplication() method and the mMainThread # getApplication() method are called respectively. Let's take a look at these two methods. First, look at the return value of the getApplication() method in LoadedApk. The code is as follows :


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;
    }
    ......
}

First, determine whether mApplication in LoadedApk is empty (guaranteed to be a singleton of the object). If not, return directly. If it is empty, create a new Application instance object and assign it to mApplication. Then take a look at the return value of the getApplication() method in ActivityThread. The code is as follows:

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;
			......
        }
	......
}

The mInitialApplication returned by the ActivityThread # getApplication() method is also returned by the LoadedApk # makeApplication() method, so it can be concluded that the getApplicationContext() method returns the same Application instance object in the above two cases.

Since the Application instance object returned by the getApplication() method is also created and obtained through the LoadedApk # makeApplication() method, the two methods getApplication() and getApplicationContext() return the same Application instance object.

What is the difference between getContext(), getBaseContxet() and getApplicationContext() methods?

First, the getBaseContxet() method obtains the instance object of the implementation class assigned to mBase's Context analyzed earlier. LoadedApk returned by getApplicationContext() method #Application instance object created by makeApplication() method. And Application, Activity and Service all have two methods: getBaseContxet() and getApplicationContext(). The getContext() method is used in Fragment or View to obtain its host object.

Five, reference

  1. Comprehensive analysis of the Context mechanism

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/132789346