preface

Context is something you can’t get around in Android development, so this article will take a look. Through this article, you will learn:

Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources: Resources

The Context family

Context is an abstract class, so let’s look at common subclasses

The figure above shows the inheritance of common Context subclasses. Activity, Application and Service are the things we usually contact with.

The Context effect

Based on the figure above, we will focus on the Resource related to the four components.

Context in the four components

The Context subclass

There are no special member variables in Context, and its member methods are mostly subclassed.

ContextWrapper

Walk through ContextWrapper with only one member variable:

Context mBase;

The member method ContextWrapper overrides the Context’s methods, internally relying on mBase calls.

MBase is actually ContextImpl, so let’s see what’s in it.

ContextImpl

Member variables

//ContentResolver private final ApplicationContentResolver mContentResolver; // Manage Resource private Final @nonnull ResourcesManager mResourcesManager; //Resource reference private @nonnull Resources mResources; Private int mThemeResource = 0; Private Resources.Theme mTheme = null; / / cache context. GetSystemService to obtain an instance of the final Object [] mServiceCache = SystemServiceRegistry. CreateServiceCache (); / / to omit...Copy the code

ContextImpl Member methods are where the Context methods are implemented.

The Context/ContextWrapper ContextImpl three relations

ContextWrapper is associated with ContextImpl

How and when are ContextWrapper and ContextImpl related? Application Application is ContextWrapper subclass, let’s see how it is related. The following section covers the Application/Activity startup process, if you are interested, please go to: The Android Activity creation to View display process

#LoadedApk.java #makeApplication(...) / / create instances ContextImpl ContextImpl appContext = ContextImpl. CreateAppContext (mActivityThread, this); // Create Application instance and point mBase to appContext //Application.attach(...) ->ContextWrapper.attachBaseContext(...) ->mBase=appContext app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); . / / ContextImpl mOuterContext to app appContext setOuterContext (app);Copy the code

When creating an Application, the ContextImpl object is constructed, then the Application instance is constructed, and the mBase in the Application points to the ContextImpl object. Finally, I’m going to point the ContextImpl mOuterContext to my app. The Application is associated with the ContextImpl, ContextWrapper and ContextImpl. Service ContextWrapper also has another common subclass: Service. Let’s see how the Service is associated with ContextImpl.

#ActivityThread. Java private void handleCreateService(CreateServiceData data) {// Get LoadedApk LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); . / / to create service service = packageInfo getAppFactory () instantiateService (cl, data. Info. Name, data. The intent); } the catch (Exception e) {} try {/ / create ContextImpl ContextImpl context. = ContextImpl createAppContext (this, packageInfo); //contextImpl holds the Service Context.setouterContext (Service); Application app = packageInfo.makeApplication(false, mInstrumentation); // Initialize some member variables of Service, Associated ContextImpl service. Attach (context, this, the data. The info. Name, data. The token, app, ActivityManager. GetService ()); // the service onCreate method is overridden to listen for the creation of a service. } catch (Exception e) { } }Copy the code

Next look at service.attach(…)

public final void attach( Context context, ActivityThread thread, String className, IBinder token, Application Application, Object activityManager) {// Attach to ContextImpl attachBaseContext(context); mThread = thread; // NOTE: unused - remove? mClassName = className; mToken = token; mApplication = application; mActivityManager = (IActivityManager)activityManager; mStartCompatibility = getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.ECLAIR; }Copy the code

So, ContextImpl is associated with the Service. Activity ContextWrapper also has a subclass ContextThemeWrapper. ContextThemeWrapper, as the name implies, is theme-specific.

{// Theme resource id private int mThemeResource; //theme private Resources.Theme mTheme; private LayoutInflater mInflater; private Configuration mOverrideConfiguration; private Resources mResources; }Copy the code

The Activity inherits from ContextThemeWrapper. See how the Activity and ContextImpl are related.

Private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {// Omitted... / / create ContextImp ContextImpl appContext = createBaseContextForActivity (r); / / create the Activity Activity Activity = mInstrumentation. NewActivity (cl, component getClassName (), r.i ntent); / / associated ContextImpl and activity appContext. SetOuterContext (activity); Attach (appContext, this, getInstrumentation(), r.toy, R.I.dent, app, R.I.ntent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback, r.assistToken); / / omit}Copy the code

Similarly, continue looking at activity.attach(…)

ContextWrapper attachBaseContext(…) , associated with mBase. BroadcastReceiver The BroadcastReceiver does not inherit from Context, but can be used in onReceive(…). I get the Context, so how did this Context come from?

#ActivityThread. Java private void handleReceiver(ReceiverData data) {// omitted... Application app; BroadcastReceiver receiver; ContextImpl context; try { app = packageInfo.makeApplication(false, mInstrumentation); ContextImpl Context = (ContextImpl) app.getBaseconText (); . / / construct receiver receiver = packageInfo getAppFactory () instantiateReceiver (cl, data. Info. Name, data. The intent); } the catch (Exception e) {} try {/ / call onReceive receiver. OnReceive (context. GetReceiverRestrictedContext (), the data. The intent); } catch (Exception e) { } finally { } }Copy the code

We noticed that the context. GetReceiverRestrictedContext () :

# ContextImpl. Java final Context getReceiverRestrictedContext () {/ / ReceiverRestrictedContext ContextWrapper subclasses of the if (mReceiverRestrictedContext ! = null) { return mReceiverRestrictedContext; } // This Context is ContextImpl, so getOuterContext() returns //Application instance. Finally ReceiverRestrictedContext mBase to return mReceiverRestrictedContext = new Application instance ReceiverRestrictedContext(getOuterContext()); }Copy the code

So we come to the conclusion that:

The Context is in the onReceive ReceiverRestrictedContext type, inherited from ContextWrapper, its mBase pointing in the direction of the Application. As you can see, mBase is not necessarily ContextImpl, but will eventually call ContextImpl.

ContentProvider ContentProvider does not inherit from Context, but its member variable mContext is of type Context. How does this variable get assigned? When ContextImpl is constructed, the ContentResolver is initialized

mContentResolver = new ApplicationContentResolver(this, mainThread);

This, which is ContextImpl itself, is passed in and assigned to the ContentResolver variable:

private final Context mContext;

When the ContentProvider is queried using the ContentResolver and created, the mContext is assigned to the mContext of the ContentProvider. The relationship between Application and four components and Context is analyzed above, as shown in the figure:

The Context and the Resources

The most common use of a Context is to retrieve Resources from a Context. Context does not have a member variable of type Resources, nor does ContextWrapper. ContextImpl has a member variable:

private @NonNull Resources mResources;

And the Context has a member method that gets Resources:

public abstract Resources getResources();

Eventually, ContextImpl is called, returning mResources. So the focus is on how ContextImpl’s mResources are assigned. Such as mentioned above, the Application/Activity/Service associated ContextImpl, will construct a new ContextImpl instance, at the time of initialization, mResources assignment. Resources are managed through ResourcesManager, and finally how ResourcesManager manages Resources.

ResourcesManager

Resources and ResourcesImpl Resources have a member variable:

private ResourcesImpl mResourcesImpl;

As the name implies, Resources is loaded by ResourcesImpl. ResourcesImpl member variables:

final AssetManager mAssets;

The AssetManager is responsible for communicating with the underlying layer (manipulating files). ResourcesManager gets the Resources core code:

Resources getOrCreateResources(@android.annotation.Nullable IBinder activityToken, @NonNull ResourcesKey key, @nonnull ClassLoader ClassLoader) {//ResourcesKey records information such as the path and Configuration of resource files. Synchronized (this) {// Synchronized (this) {// Synchronized (this) {// synchronized (activityToken! = null) {/ / from ResourcesImpl Map to find the corresponding cache ResourcesImpl ResourcesImpl = findResourcesImplForKeyLocked (key); if (resourcesImpl ! = null) { return getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); }} else {/ / the Activity of the Resource ResourcesImpl ResourcesImpl = findResourcesImplForKeyLocked (key); if (resourcesImpl ! = null) { return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } // create AssetManager ResourcesImpl ResourcesImpl = createResourcesImpl(key); if (resourcesImpl == null) { return null; } mResourceImpls. Put (key, new WeakReference<>(resourcesImpl)); final Resources resources; if (activityToken ! = null) { //Activity Resource resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } else {/ / the Activity Resource resources = getOrCreateResourcesLocked (this, resourcesImpl, key. MCompatInfo); } return resources; }}Copy the code

To sum up:

1. Use ResourcesKey to check whether ResourcesImpl has been created before. If not, create a new object and record it in Map. 2. Iterate through the ArrayList to find Resources objects that can be reused, based on the condition that the passed ResourcesImpl is equal to the existing one. If none can be reused, create the Resources object, set ResourcesImpl, and place the Resources object in the List for reuse. 3. You can see that ResourcesManager manages resources by setting the cache. Whereas ResourcesManager is a singleton, Context is getResources(…) The Resource is essentially obtained through ResourcesManager.

Then look at the Application/Service/Activity to obtain the Resource is the same Resource? ActivityThread creates ContextImpl for Application/Service using the following methods:

#ContextImpl.java static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo, String opPackageName) { ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null, opPackageName); // getResources context.setresources (packageinfo.getresources ()) from packageInfo; return context; }Copy the code

packageInfo.getResources():

#LoadedApk public Resources getResources() {//LoadedApk is global, So it mResources only an if (mResources = = null) {mResources = ResourcesManager. GetInstance (). GetResources (null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } return mResources; }Copy the code

As you can see, ContextImpl created with createAppContext(xx) shares the same Resources object.

The ActivityThread creates ContextImpl for the Activity using:

#ContextImpl.java static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader, null); final ResourcesManager resourcesManager = ResourcesManager.getInstance(); / / get the context through resourcesManager setResources (resourcesManager. CreateBaseActivityResources (activityToken, packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, classLoader)); return context; }Copy the code

As you can see, the Resource manager is used directly to obtain the Resource. Finally, different activities obtain different Resource objects, but share the same ResourcesImpl object.

Different contexts are associated and used

So with all these contexts, when do you use which Context? Here are a few of them.

Start the Activity

public void startActivity(Intent intent, Bundle options) { final int targetSdkVersion = getApplicationInfo().targetSdkVersion; // If targetSdkVersion is less than 7.0 or greater than 9.0 // start the Activity with ContextImpl, if FLAG_ACTIVITY_NEW_TASK is not added, Throw an exception if ((intent. GetFlags () & intent. FLAG_ACTIVITY_NEW_TASK) = = 0 && (targetSdkVersion < Build. VERSION_CODES. N | | targetSdkVersion >= Build.VERSION_CODES.P) && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires  the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options); }Copy the code

TargetSdkVersion For details, see TargetSdkVersion, compileSdkVersion, minSdkVersion

To start an Activity in a non-activity Context, simply add the FLAG_ACTIVITY_NEW_TASK flag. It is recommended to hold a reference to the Activity object at the top of the stack and start another Activity from the Activity.

Start the Dialog

Dialog will fail to start from a non-activity Context. The reason is:

The Activity has an IBinder appToken. When a Window is associated with a WindowManager, the appToken is assigned to the Window’s IBinder mAppToken. When adding a Window to a WindowManagerService, MAppToken will be assigned to the WindowManager. LayoutParams IBinder token. WindowManagerService checks the validity of the added window and finds that if the window type to be added is Dialog but has no Token, it throws an exception. When an Activity starts a Dialog, the WindowManager obtained by the Dialog is the WindowManager of the Activity. Its parentWindow is the Window of the Activity, and the Window of the Activity has a token. Finally, the token is assigned to LayoutParams.

So to start a Dialog, you need to pass in the Activity as the Context.

The Context of the View

The View Context mContext variable is assigned when the View object is created. We know there are two ways to create a View object:

Create a new View(Context) dynamically, depending on the Context passed in. XML layouts, which are ultimately loaded by LayoutInflater. LayoutInflater from(Context Context), which is finally passed to the View.

The Context of a View does not explicitly restrict what type to use. However, you can’t use the Activity Theme feature without using the Activity Context. Topic please click: cut the most in-depth Android/Attr/Styleable/Style/Theme TypedArray clearly