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 |
- An error is reported if we use the ApplicationContext to start an Activity with the LaunchMode standard
android.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. - 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:
- Application level Windows ranging from 1 to 99, such as activities.
- Child Windows, which cannot exist alone, must be attached to a particular parent window, hierarchy 1000 to 1999, such as Dialog.
- 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