The Context class structure

Let’s take a look at the Context class structure diagram

Context itself is a pure AbSTact class with two concrete implementation subclasses: ContextImpl and ContextWrapper

ContextWrapper is a wrapper class for a Context and must contain a real Context reference in its constructor, ContextWrapper also provides attachBaseContext() to specify the actual Context object in the ContextWrapper object. Calls to the ContextWrapper method are directed to the actual Context contained in the ContextWrapper object.

So ContextImpl is the implementation of Context, Activity, Application, Services inherit from ContextWrapper (Activities inherit from ContextThemeWrapper, a subclass of ContextWrapper), but they are initialized to create a ContextImpl object. Methods in the Context are implemented by ContextImpl. ContextThemeWrapper, which contains an interface associated with the Theme.

Source code Application, Activity, Service Context creation process

Application, Activity, and Service all create ContextImpl during initialization.

Application Context

When the application is first started, it sends the H.bin_application message through the bindApplication, and then handleBindBapplication creates the ContextImpl object in two places. But the two execution after creation condition in the if (data instrumentationName! = null) {if the Android Unit Test project is created, the corresponding Test program will meet this condition.

If it is not a test project, the makeApplication method is called

Application app = data.info.makeApplication(data.restrictedBackupMode, null);
Copy the code

In the makeApplication method

//LoadApk.java
//makeApplication
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
Copy the code

The ContextImpl createAppContext

//ContextImpl.java
//createAppContext
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        return new ContextImpl(null, mainThread,
                packageInfo, null.null.false.null.null, Display.INVALID_DISPLAY);
}
Copy the code

So the packageInfo parameter in createAppContext is actually data.info, which comes from the handleBindBapplication method call, The call to handleBindBapplication is sent from bindApplication. Here is the code for bindApplication:

767        public final void bindApplication(String processName, ApplicationInfo appInfo,
768                List<ProviderInfo> providers, ComponentName instrumentationName,
769                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
770                IInstrumentationWatcher instrumentationWatcher,
771                IUiAutomationConnection instrumentationUiConnection, int debugMode,
772                boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
773                Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
774                Bundle coreSettings) {
775...819
820            AppBindData data = new AppBindData();
821            data.processName = processName;
822            data.appInfo = appInfo;
823            data.providers = providers;
824            data.instrumentationName = instrumentationName;
825            data.instrumentationArgs = instrumentationArgs;
826            data.instrumentationWatcher = instrumentationWatcher;
827            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
828            data.debugMode = debugMode;
829            data.enableOpenGlTrace = enableOpenGlTrace;
830            data.restrictedBackupMode = isRestrictedBackupMode;
831            data.persistent = persistent;
832            data.config = config;
833            data.compatInfo = compatInfo;
834            data.initProfilerInfo = profilerInfo;
835            sendMessage(H.BIND_APPLICATION, data); // Send data, received in handleBindBapplication
836        }
Copy the code

As you can see in the bindApplication method, an AppBindData class is constructed with ApplicationInfo and other parameters. ** Note that the AppBindData info is still empty. ** The getPackageInfoNoCheck in the handleBindBapplication will be assigned.

data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
Copy the code

As you can see, this method is created according to the ApplicationInfo and CompatibilityInfo parameters of AppBindData

//getPackageInfoNoCheck
1787    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
1788            CompatibilityInfo compatInfo) {
1789        return getPackageInfo(ai, compatInfo, null.false.true.false);
1790    }
//getPackageInfo
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
1805            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
1806            boolean registerPackage) {
1807        final booleandifferentUser = (UserHandle.myUserId() ! = UserHandle.getUserId(aInfo.uid));1808        synchronized (mResourcesManager) {
1809            WeakReference<LoadedApk> ref;
1810            if (differentUser) {
1811                // Caching not supported across users
1812                ref = null;
1813            } else if (includeCode) {
1814                ref = mPackages.get(aInfo.packageName);
1815            } else {
1816                ref = mResourcePackages.get(aInfo.packageName);
1817            }
1818
1819LoadedApk packageInfo = ref ! =null ? ref.get() : null;
1820            if (packageInfo == null|| (packageInfo.mResources ! =null
1821                    && !packageInfo.mResources.getAssets().isUpToDate())) {
1822                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
1823                        : "Loading resource-only package ") + aInfo.packageName
1824                        + " (in "+ (mBoundApplication ! =null
1825                                ? mBoundApplication.processName : null)
1826                        + ")");
1827                packageInfo =
1828                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
1829                            securityViolation, includeCode &&
1830(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) ! =0, registerPackage);
1831
1832                if (mSystemThread && "android".equals(aInfo.packageName)) {
1833                    packageInfo.installSystemApplicationInfo(aInfo,
1834                            getSystemContext().mPackageInfo.getClassLoader());
1835                }
1836
1837                if (differentUser) {
1838                    // Caching not supported across users
1839                } else if (includeCode) {
1840                    mPackages.put(aInfo.packageName,
1841                            new WeakReference<LoadedApk>(packageInfo));
1842                } else {
1843                    mResourcePackages.put(aInfo.packageName,
1844                            new WeakReference<LoadedApk>(packageInfo));
1845                }
1846            }
1847            return packageInfo;
1848        }
1849    }
Copy the code

This method creates a global packageInfo(LoadApk) object for the ActivityThread class. Then call to the data. The info. MakeApplication (data restrictedBackupMode, null) this is LoadApk (packageInfo) source

The process of creating a Context in Application

The Context for the Activity

When an Activity is started, AMS(ActivityManagerService) calls the scheduleLaunchActivity of ActivityThread through Binder. Create an ActivityClientRecord object in this memory and send sendMessage(h.launch_activity, r)

630        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
631                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
632                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
633                int procState, Bundle state, PersistableBundle persistentState,
634                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
635                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
636
637            updateProcessState(procState, false);
638							
639            ActivityClientRecord r = new ActivityClientRecord();
640
641            r.token = token;
642            r.ident = ident;
643            r.intent = intent;
644            r.referrer = referrer;
645            r.voiceInteractor = voiceInteractor;
646            r.activityInfo = info;
647r.compatInfo = compatInfo; ...659            r.overrideConfig = overrideConfig;
660            updatePendingConfiguration(curConfig);
661						 // Send the message, and finally call handleLaunchActivity()
662            sendMessage(H.LAUNCH_ACTIVITY, r);
663        }
Copy the code

PerformLaunchActivity is then called from the handleLaunchActivity method, which creates ContextImpl in the following code:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  					ActivityInfo aInfo = r.activityInfo;
2297        if (r.packageInfo == null) {
2298            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
2299                    Context.CONTEXT_INCLUDE_CODE);
2300}...2344            if(activity ! =null) {
  									/ / create the context
2345Context appContext = createBaseContextForActivity(r, activity); ...2350                activity.attach(appContext, this, getInstrumentation(), r.token,
2351                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
2352                        r.embeddedID, r.lastNonConfigurationInstances, config,
2353                        r.referrer, r.voiceInteractor);
Copy the code

The getPackageInfo method executes the same logic as getPackageInfoNoCheck. Then create the Context. Specific create Context in createBaseContextForActivity approach

2425    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
2426        int displayId = Display.DEFAULT_DISPLAY;
2427        try {
2428            displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
2429        } catch (RemoteException e) {
2430        }
2431
2432        ContextImpl appContext = ContextImpl.createActivityContext(
2433                this, r.packageInfo, displayId, r.overrideConfig);
2434appContext.setOuterContext(activity); ...2453        return baseContext;
2454    }
Copy the code
Activity The process of creating a Context

Context for Service

When a Service is started, AMS, through Binder, calls ActivityThread’s scheduleCreateService method, where the CreateServiceData object is constructed

718        public final void scheduleCreateService(IBinder token,
719                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
720            updateProcessState(processState, false);
721            CreateServiceData s = new CreateServiceData();
722            s.token = token;
723            s.info = info;
724            s.compatInfo = compatInfo;
725
726            sendMessage(H.CREATE_SERVICE, s);
727        }
Copy the code

Next, send h.create_service and execute the handleCreateService() method to create the Context object

2854        LoadedApk packageInfo = getPackageInfoNoCheck(
2855                data.info.applicationInfo, data.compatInfo);
2856        Service service = null;
2867...2871        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
2872            context.setOuterContext(service);
2873
2874            Application app = packageInfo.makeApplication(false, mInstrumentation);
2875            service.attach(context, this, data.info.name, data.token, app,
2876                    ActivityManagerNative.getDefault());
2877            service.onCreate();
Copy the code

This is basically the same as the previous Activity Context. The assignment code also uses the getPackageInfoNoCheck method, which means that the Service corresponds to the mPackageInfo and Activity inside the Context, It’s exactly the same in Application.

The process of creating a Context in a Service

The relationship between contexts

Can be seen from the above analysis, Application/Activity/Service create the Context object in the process of basic is the same, the code structure is very similar to, the following is the source of different PackageInfo object Context subclasses

The name of the class Remote data class Local data type Assignment way
Application ApplicationInfo AppBindData getPackageInfoNoCheck()
Activity ActivityInfo ActivityClientRecord getPackageInfo()
Service ServiceInfo CreateServiceData getPackageInfoNoCheck()
summary

The number of contexts in an Application = Number of Services + Number of Activities +1(Application)+ Number of other ContextImpl

The number of contexts = 2 x (Number of Services + Number of activities + Number of applications) + number of other ContextImpl. Create Applciation ContextImpl/Service/Activity process will create the base object, Application and so on is the proxy object, but the previous Context class structure can be seen, they eventually inherit ContextWrapper object, And ContextWrapper just a wrapper class, the real implementation is ContextImpl, so Applciation/Service/Activity creation process inevitably contains create ContextImpl, Application proxy object, of course, It doesn’t make a lot of sense if you’re just talking about pure numbers. I tend to the Context for the Context (Application, the Activity, Service) the number of, but ContextImpl may also ContextImpl. CreatePackageContext () to read other apk resources, So it makes sense to + other ContextImpl.

The application contains multiple ContextImpl objects, and its internal variable mPackageInfo points to the same LoadApk object. This design structure generally means that ContextImpl is a lightweight class and LoadApk is a heavyweight class. ContextImpl; ContextImpl; ContextImpl; ContextImpl; ContextImpl; mPackageInfo(LoadApk); This is also reasonable from the point of view of system efficiency.

The scope of Context

The Context scope Application Activity Service
Show a Dialog NO YES NO
Start an Activity Is not recommended YES Is not recommended
Layout Inflation Is not recommended YES Is not recommended
Start a Service YES YES YES
Send a Broadcast YES YES YES
Register Broadcast Receiver YES YES YES
Load Resource Value YES YES YES
  1. An error is reported if we use the ApplicationContext to start an Activity with the LaunchMode standardandroid.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?This is because a non-activity Context does not have a task stack, so the Activity to be launched cannot find the stack. The solution to this problem is to specify the FLAG_ACTIVITY_NEW_TASK bit for the Activity to be started, so that a new stack of tasks is created for it when the Activity is started in singleTask mode. This method of starting an Activity with Application is not recommended. Service is the same as Application.
  2. Layout constructs are also legal in applications and services, but use the system default theme style. Some styles may not be used if you customize them. Therefore, this method is not recommended. To sum up: All uI-related activities should be handled using the Activity Context. Some other operation, Service, Activity, Application instance can, of course, pay attention to the Context reference to hold, to prevent a memory leak.

GetApplicaiton vs. getApplicationContext

We print both out through program calls

/ / call
Log.d("jackie"."getApplication"+application)
Log.d("jackie"."getApplicationContext"+applicationContext)
// Print the result
2020-10-18 22:05:55.323 18590-18590/com.jackie.testapk D/jackie: getApplicationandroid.app.Application@eee7231
2020-10-18 22:05:55.323 18590-18590/com.jackie.testapk D/jackie: getApplicationContextandroid.app.Application@eee7231
Copy the code

You can see that the printed memory address is the same, it looks like they are the same object; Take a look at the source code for these two methods

//===============getApplicationContext==========
//ContextWrapper.java
@Override
public Context getApplicationContext(a) {
    return mBase.getApplicationContext();
}

//================getApplication=================
//Activity
/** Return the application that owns this activity. */
	public final Application getApplication(a) {
  	return mApplication;
}
//Service
/** Return the application that owns this service. */
public final Application getApplication(a) {
  	return mApplication;
}
Copy the code

From the previous analysis, we can see that the packageInfo of Application, Activity, and Service are from the same source, and all three are ultimately derived from ContextWrapper. Since these two methods give the same result, why does Android provide two methods that duplicate functions? There is actually a big difference in scope between the two methods. The getApplication() method is semantic enough to get an Application instance, but it can only be called in activities and services. If you want to get an instance of an Application in BroadcastReceiver, you can use the getApplicationContext() method.

Is it true that Dialog cannot pop up in Application?

First let’s do a test, create a Dialog with getApplciationContext, and pop up the Dialog.

//vm api 18
public void test1(a){
    Dialog alert = new Dialog(getApplicationContext());
    //alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
    alert.setContentView(view);
    alert.show();
}
Copy the code

You can then see the console error log

10-19 10:19:17.646 6451-6451/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
    android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:563)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:269)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
        at android.app.Dialog.show(Dialog.java:281)
        at com.jackie.testdialog.MainActivity.test1(MainActivity.java:52)
        at com.jackie.testdialog.MainActivity$1.onClick(MainActivity.java:27)
Copy the code

Token is empty and cannot be added to window.

Here we need to briefly mention that all views in Android are presented through the Window. Whether it is an Activity, Dialog, or Toast, their views are actually attached to the Window, so the Window is actually the direct manager of the View.

There are three types of Windows:

  1. Application level Windows ranging from 1 to 99, such as activities.
  2. Child Windows, which cannot exist alone, must be attached to a particular parent window, hierarchy 1000 to 1999, such as Dialog.
  3. System-level Windows ranging from 2000 to 2999, such as Toast. System type Window is required to check permissions, need to be declared in the AndroidManifest.

Error: We have no token because we passed getApplcationContext. ** In addition, Windows is a special system that does not require tokens. ** Therefore, in the above example, you only need to specify the Window type of the dialog box as system type to pop up the dialog box normally.

// VM API 18 successfully tested on Android 18
public void test1(a){
    Dialog alert = new Dialog(getApplicationContext());
    alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
    alert.setContentView(view);
    alert.show();
}
    // VM API 29 successfully tested on Android29
public void test(a){
    Dialog alert = new Dialog(getApplicationContext());
    alert.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
    View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
    alert.setContentView(view);
    alert.show();
}
Copy the code

You also need to declare permissions

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Copy the code

conclusion

Sometimes to fully understand a function, you need to go deep into the source code, manually do experimental verification, and find relevant articles to fully grasp.

Refer to the article

www.jianshu.com/p/51d63a1ff…

Android development art exploration