Get to know the Context in Android

Get to know the Context in Android

Hou Liang
(This article is based on Android 7.0)

 

1 What is Context?

On the Android platform, Context is a basic concept that logically represents a runtime "context".

In fact, it is more than the Android platform. On other platforms, there is the concept of context. For example, a process is actually a context. When we write the simplest C language program, why can we write a simple malloc() to apply for memory, and write a simple open() to open a file? Did these all fall from the sky? of course not. To put it bluntly, before entering the main() function, the operating system has created a context for us, and we just enjoy the convenience provided by the context.

On the Android platform, the concept of context is refined again. The designer of Android is estimated to be thinking: Why on the previous operating system, A application can only use B application in a whole form? What if app A only wants to use part of app B, such as an interface? After several attempts, Android designers finally formed the following understanding: every important UI interface in the application is encapsulated with a small context, and every important external service is also encapsulated with a small context. These small contexts are all accommodated on a large Android platform, and are managed by Android unified scheduling to form a unified whole. From the underlying mechanism, it is impossible for application A to "directly" use the interface of application B, because their address spaces are different, but if the interface of application B is wrapped in a small context, it has to a certain extent With the ability to run independently, it can logically be used by application A.

Of course, I am not the designer of Android after all, all these are made up by myself, everyone can see it for easy understanding. Obviously, the small context that encapsulates the interface is the Activity in Android, and the small context that encapsulates the service is the Service. The Android context only needs to logically support access to application resources or system services, other than that, it is no different from ordinary objects.

 

2 Context behavior

Context is an abstract class reflected in the code, and its main behavior list is as follows:

Context behavior classification

Common functions

Use the services provided by the system getPackageManager()、getSystemService()......
basic skills

startActivity () 、 sendBroadcast () 、 registerReceiver () 、 startService () 、 bindService () 、 getContentResolver () ......

[The interior is basically dealing with AMS]

access resources getAssets()、getResources()、getString()、getColor()、getClassLoader()......
the connection to the currently hosted process  getMainLooper()、getApplicationContext()......
related to information storage getSharedPreferences()、openFileInput()、openFileOutput()、deleteFile()、openOrCreateDatabase()、deleteDatabase()......

Since it's an abstract class, eventually an actual derived class has to be needed. On Android, we can draw the following schematic diagram of Context inheritance:

We can think about this picture. Obviously, on the Android platform, Activity and Service are essentially a Context, and the Application object representing the application is also a Context. This object corresponds to the <Application in AndroidManifest.xml. > section. Because the actions of context accessing application resources or system services are the same, these actions are uniformly encapsulated into a ContextImpl auxiliary class. Activity, Service, and Application all contain their own ContextImpl. Whenever they need to access application resources or system services, they simply delegate the request to the internal ContextImpl.

3 ContextWrapper

There is also a ContextWrapper in the above figure, which is a wrapper class used to represent Context. When it performs context-related actions, it is basically delegated to the Context recorded in the internal mBase domain. If we want to subclass some behaviors of the context, we can override some member functions of ContextWrapper in a targeted manner.

The code snippet of ContextWrapper is as follows:
[frameworks/base/core/java/android/content/ContextWrapper.java]

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    public Context getBaseContext() {
        return mBase;
    }
    . . . . . .
    . . . . . .

The mBase in the code is its core.

ContextWrapper simply encapsulates Context, it rewrites all member functions of Context, such as:

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

In a project that the author participated in the development, the plug-in technology of Android was used. At that time, we constructed our own ContextWrapper and covered its function of obtaining resources.

Because ContextWrapper itself also inherits from Context, it can form a layer-by-layer packaging effect. For example, if we wrap two layers of ContextWrapper outside an Activity object, we can form the following diagram:

The decorative structure of this layer-by-layer package can theoretically form a complex and powerful combination. But its expected calling behavior must be called from the outermost ContextWrapper, and then delegated layer by layer. This kind of structure is most afraid that users will play cards out of common sense and start calling from a certain level in the middle, so the effect of execution may not be as good as you want.

In fact, this has actually happened. It is said that LayoutInflater, as a layout analysis tool, is an auxiliary class that most Android developers like to see. We often get a LayoutInflater object by calling LayoutInflater.from(), and then we can use this object to parse a layout resource and get a View object. But be careful, there is a hole here. The LayoutInflater object obtained by this from() function goes through multiple layers of winding inside it, and finally uses the direct external Context of the ContextImpl object at the end of the delegation chain. The relevant code is as follows:
[frameworks/base/core/java/android/ app/SystemServiceRegistry.java]

public LayoutInflater createService(ContextImpl ctx) {
    return new PhoneLayoutInflater(ctx.getOuterContext());
}});

We don't care about those twists and turns here, just pay attention to ctx.getOuterContext(). It is conceivable that even if we use a multi-layer ContextWrapper when calling the from() function, PhoneLayoutInflater (a derived class of LayoutInflater) will not start using the outermost ContextWrapper. If you rewrite the getResources() function in an outer ContextWrapper, you originally intended to use the new resources when parsing the layout, but because PhoneLayoutInflater cannot call your new getResources() function at all, a parsing exception will occur, prompting Says resource not found.

To solve this awkward problem, LayoutInflater provides the cloneInContext() function. We can refer to the getSystemService() function of ContextThemeWrapper, there is such a code:

mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);

Note that instead of using the return value of the from() function directly, clone it all at once. Cloning is to re-new a PhoneLayoutInflater object and adjust its mContext to the appropriate ContextWrapper. The schematic diagram is as follows:

 

4 ContextImpl

As we have said before, because the actions of context accessing application resources or system services are the same, this part of the action is uniformly encapsulated into a ContextImpl auxiliary class, and now let's take a look at this class.

The code snippet of ContextImpl is as follows:
[frameworks/base/core/java/android/app/ContextImpl.java]

class ContextImpl extends Context {
    private final static String TAG = "ContextImpl";
    private final static boolean DEBUG = false;

    @GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> 
sSharedPrefsCache;
    @GuardedBy("ContextImpl.class")
    private ArrayMap<String, File> mSharedPrefsPaths;

    final ActivityThread mMainThread;  // 依靠该成员和大系统联系
    final LoadedApk mPackageInfo;

    private final IBinder mActivityToken;
    private final UserHandle mUser;
    private final ApplicationContentResolver mContentResolver;

    private final String mBasePackageName;
    private final String mOpPackageName;

    private final @NonNull ResourcesManager mResourcesManager;
    private final @NonNull Resources mResources;  // 指明自己在用的资源
    private @Nullable Display mDisplay; 
    private final int mFlags;

    private Context mOuterContext;   // 指向其寄身并提供服务的上下文。

    private int mThemeResource = 0;
    private Resources.Theme mTheme = null;
    private PackageManager mPackageManager;
    private Context mReceiverRestrictedContext = null;

Obviously, as a core component of a context, ContextImpl has the responsibility to communicate with a larger system (we can understand the Android platform as a large system), so it will have a mMainThread member. Take the start of the activity as an example. Finally, it will go to startActivity() of ContextImpl, and this function generally calls mMainThread.getInstrumentation().execStartActivity() to send the semantics to the Android system.

Another important aspect in ContextImpl is access to resources. This involves where the resources come from. Simply put, when an APK is loaded, the system will create a corresponding LoadedApk object, and load the resource part of the APK into LoadedApk through the decoding module. Whenever we create a corresponding ContextImpl object for a context, the correct Resources object will be obtained from LoadedApk and recorded in the mResources member variable of ContextImpl for later use.

In addition, in order to facilitate access to the system services provided by Android, a small cache is also constructed in ContextImpl, which is recorded in the mServiceCache member variable:

final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

SystemServiceRegistry is an auxiliary class, and the code of its createServiceCache() function is very simple, just a new Object array and return it. That is to say, at the beginning, this mServiceCache array has no content in it. In the future, when we need to access system services, some sub-items of this array will be filled in at runtime. Then we can easily think that the content of the mServiceCache array contained in the internal ContextImpl of different activities started in an application is often different, and in general, this array is relatively sparse, that is to say, it contains many nulls.

When people write code, they often write sentences like the following:

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);

This function will eventually call the function of the same name of ContextImpl. The function code is as follows:
[frameworks/base/core/java/android/app/ContextImpl.java]

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

Continue to use the auxiliary class SystemServiceRegistry.

【frameworks/base/core/java/android/app/SystemServiceRegistry.java】

    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

The SYSTEM_SERVICE_FETCHERS used is a static hash table provided by the SystemServiceRegistry class:
[frameworks/base/core/java/android/app/SystemServiceRegistry.java]

final class SystemServiceRegistry {
    . . . . . .
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    private static int sServiceCacheSize;

It can be seen that the value part of this hash table must implement the ServiceFetcher<?> interface. For SystemServiceRegistry, it should be a CachedServiceFetcher<T> derived class object. The CachedServiceFetcher class has implemented the getService() function by default:
[frameworks/base/core/java/android/app/SystemServiceRegistry.java]

    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache; // 终于看到ContextImpl的mServiceCache了
        synchronized (cache) {
            Object service = cache[mCacheIndex];
            if (service == null) {
                service = createService(ctx); // 如果cache里没有,则调用createService()
                cache[mCacheIndex] = service;
            }
            return (T)service;
        }
    }

Simply put, whenever getService() is used, it will be obtained from the mServiceCache cache array of ContextImpl first. If there is no cache, it will be further createdService() and recorded in the cache. And each different CachedServiceFetcher<T> derived class will implement its own unique createService() function, so that colorful "system service access classes" can be created in the cache.

The SYSTEM_SERVICE_FETCHERS hash table will be initialized in the static block of the SystemServiceRegistry class. The code snippet is as follows:

    . . . . . .
    static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        . . . . . .
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), 
                       ctx.mMainThread.getHandler());
            }});

The so-called registerService() action here is actually mainly to insert the newly created CachedServiceFetcher object into the static hash table:

    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

Now, let's draw a diagram, taking an Activity's ContextImpl as an example:

 

5 Summary

The above is the main knowledge of Context. It's not that complicated, but it's something that every Android programmer should master. Here is a summary, I hope it will be helpful to all students.


 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325307376&siteId=291194637