preface
Android Context: What is an Android Context?
But on a closer look, there’s more to talk about, like this equation:
Number of Contexts = Number of Services + Number of Activities + 1
To be honest, I don’t see the point of this equation, and it’s wrong. First, in the multi-process case, there is more than one Application object. Second, the Activity, Service, and Application inherit from ContextWrapper and are themselves a Context with a Base Context inside. And then finally, we have all kinds of outer contexts, display contexts, things like that, which I didn’t go into too much, but there are definitely more than twice as many contexts. Of course, if this is an interview question, then internal structure should not be considered, so the standard answer is:
Context = Number of Services + Number of Activities + Number of Applications
The above part is a discussion, and the following is a formal discussion. The contents of this paper are as follows:
- The Context family
- What’s the use of Context?
- What is the difference between ContextImpl, ContextWrapper, and ContextThemeWrapper?
- What is the difference between Activity Context, Service Context, Application Context, and Base Context?
- Why is Base Context not recommended?
- conclusion
The Context family
Context itself is an abstract class that implements ContextImpl and subclasses ContextWrapper and ContextThemeWrapper. Both subclasses are proxies for Context. The main difference is that ContextThemeWrapper has its own theme resource. Their inheritance relationship is as follows:
What’s the use of Context?
To figure out what a class does, it’s easy to look at what interfaces it provides. Here are some of the main ones:
/** * 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-callsforapplication-level operations such as launching activities, * broadcasting and receiving intents, Public abstract void startActivity(@requiresperMission Intent Intent); public abstract void startActivity(@requiresperMission Intent Intent); public abstract void sendBroadcast(@RequiresPermission Intent intent); public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter); public abstract void unregisterReceiver(BroadcastReceiver receiver); public abstract ComponentName startService(Intent service); public abstract boolean stopService(Intent service); public abstract booleanbindService(@RequiresPermission Intent service, @NonNull ServiceConnection conn, @BindServiceFlags int flags); public abstract void unbindService(@NonNull ServiceConnection conn); public abstract ContentResolver getContentResolver(); Public abstract AssetManager getAssets(); public abstract Resources getResources(); public abstract PackageManager getPackageManager(); public abstract Context getApplicationContext(); public abstract ClassLoader getClassLoader(); public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... } public final String getString(@StringRes int resId) { ... } public final int getColor(@ColorRes int id) { ... } public final Drawable getDrawable(@DrawableRes int id) { ... } public abstract Resources.Theme getTheme(); public abstract voidsetTheme(@StyleRes int resid); public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... } public abstract ApplicationInfo getApplicationInfo(); public abstract String getPackageName(); public abstract Looper getMainLooper(); public abstract int checkPermission(@NonNull String permission, int pid, int uid); Public abstract File getSharedPreferencesPath(String name); public abstract File getDataDir(); public abstract boolean deleteFile(String name); public abstract File getExternalFilesDir(@Nullable Stringtype); public abstract File getCacheDir(); . public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode); public abstract boolean deleteSharedPreferences(String name); Public abstract SQLiteDatabase openOrCreateDatabase(...) ; public abstract boolean deleteDatabase(String name); public abstract File getDatabasePath(String name); . / / other public void registerComponentCallbacks (ComponentCallbacks callback) {... } public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }... } public interface ComponentCallbacks { void onConfigurationChanged(Configuration newConfig); void onLowMemory(); }Copy the code
Context is the main steward of the Application, responsible for:
- Interaction of four components, including starting an Activity, Broadcast, Service, and obtaining a ContentResolver
- Obtain System or application Resources, including AssetManager, PackageManager, Resources, System Service, color, String, and Drawable
- Files, including obtaining cache folder, deleting files, SharedPreference related, etc
- Related to database (SQLite), including opening database, deleting database, obtaining database path, and so on
- Other ancillary features, such as setting up ComponentCallbacks, listen for configuration changes, running out of memory, and so on
What is the difference between ContextImpl, ContextWrapper, and ContextThemeWrapper?
ContextWrapper
Look at the ContextWrapper:
/** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed To modify behavior without changing * the original Context. */ 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; } // This is the Base Context public ContextgetBaseContext() {
returnmBase; } // The following methods are all done directly via mBase @override public AssetManagergetAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
returnmBase.getPackageManager(); }... }Copy the code
ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper: ContextWrapper
ContextThemeWrapper
ContextThemeWrapper (); ContextThemeWrapper ();
/**
* A context wrapper that allows you to modify or replace the theme of the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;
public ContextThemeWrapper() { super(null); } public ContextThemeWrapper(Context base, @StyleRes int themeResId) { super(base); mThemeResource = themeResId; } public ContextThemeWrapper(Context base, Resources.Theme theme) { super(base); mTheme = theme; } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); } / / in the Recource initialization before the incoming Configuration information public void applyOverrideConfiguration (Configuration overrideConfiguration) {if(mResources ! = null) { throw new IllegalStateException(...) ; }if(mOverrideConfiguration ! = null) { throw new IllegalStateException(...) ; } mOverrideConfiguration = new Configuration(overrideConfiguration); } public ConfigurationgetOverrideConfiguration() {
returnmOverrideConfiguration; } // No overridessetThe Resource, i.e.,setResource behaves the same as its parent @override public ResourcesgetResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else{// Initialize Resource based on configuration information // Note, Here to create another and Base the Context of different Resource final Context resContext = createConfigurationContext (mOverrideConfiguration); mResources = resContext.getResources(); }}return mResources;
}
@Override
public void setTheme(int resid) {
if(mThemeResource ! = resid) { mThemeResource = resid; initializeTheme(); } } private voidinitializeTheme() {
final boolean first = mTheme == null;
if(first) {// Get Theme from Resource mTheme = getResources().newtheme (); Final resources.theme = getBaseContext().getTheme();if(theme ! = null) { mTheme.setTo(theme); } } onApplyThemeResource(mTheme, mThemeResource, first); } protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) { theme.applyStyle(resId,true);
}
@Override
public Resources.Theme getTheme() {// will only be initialized onceif(mTheme ! = null) {return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
returnmTheme; }... }Copy the code
ContextThemeWrapper has its own additional Resource and Theme member, as opposed to ContextWrapper, and can pass in configuration information to initialize its own Resource and Theme. The Resource and theme-related behavior is no longer a direct call to mBase methods, i.e. ContextThemeWrapper and its mBase members are different in Resource and theme-related behavior.
ContextImpl
Let’s look at the ContextImpl’s Theme and Resource sections to see how it differs from ContextThemeWrapper:
/**
* Common implementation of Context API, which provides the base
* context object forActivity and other application components. */ class ContextImpl extends Context { private int mThemeResource = 0; private Resources.Theme mTheme = null; private @NonNull Resources mResources; // createActivityContext static ContextImpl createActivityContext(...) { ContextImpl context = new ContextImpl(...) ; context.setResources(resourcesManager.createBaseActivityResources(...) );returncontext; } // Create Application Context, Service Context static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { ContextImpl context = new ContextImpl(...) ; context.setResources(packageInfo.getResources());return context;
}
private static Resources createResources(...) {
returnResourcesManager.getInstance().getResources(...) ; } // ContextThemeWrapper did not override the parent classsetResources // hence the call to mBasesetResources, void as ContextImpl behavessetResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
@Override
public Resources getResources() {
returnmResources; } / * -- -- -- -- -- -- -- -- -- -- topic -- -- -- -- -- -- -- -- -- -- -- - * / @ Override public voidsetTheme(int resId) {
synchronized (mSync) {
if(mThemeResource ! = resId) { mThemeResource = resId; initializeTheme(); }}} // Create a Themem object directly, missing the ContextThemeWrapperinitializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
}
mTheme.applyStyle(mThemeResource, true);
}
@Override
public Resources.Theme getTheme() {synchronized (mSync) {// basically the same as ContextThemeWrapperif(mTheme ! = null) {return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getOuterContext().getApplicationInfo().targetSdkVersion);
initializeTheme();
returnmTheme; }}}Copy the code
The main difference between ContextImpl and ContextThemeWrapper is that there is no Configuration. The other behavior is roughly the same. In addition, ContextImpl can be used to create mBase members of activities, services, and applications that have different parameters and different resources. Note that setResource is called by mBase in methods such as createActivityContext. The Activity, Service, and Application themselves do not execute setResource.
summary
-
ContextWrapper and ContextThemeWrapper are proxy classes for Context. The difference is that ContextThemeWrapper has its own Theme and Resource. And the Resource can be passed its own configuration initialization
-
ContextImpl is the main implementation class for the Context, which creates the Base Context for activities, services, and applications. The ContextWrapper is the ContextImpl object itself
-
The main difference between ContextImpl and ContextThemeWrapper is that the ContextThemeWrapper has a Configuration object against which the Resource can be initialized
-
The Service and Application use the same Recource, and the Activity uses a different Resource
What is the difference between Activity Context, Service Context, Application Context, and Base Context?
Activity Context
When an Activity starts, it eventually executes ActivityThread’s FormLaunchActivitiy:
public final class ActivityThread { private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... / / the Context will be as the Base of the Activity Context ContextImpl appContext = createBaseContextForActivity (r); Activity activity = null; try { ClassLoader cl = appContext.getClassLoader(); / / create the Activity Activity = mInstrumentation. NewActivity (cl, component getClassName (), r.i ntent); StrictMode.incrementExpectedActivityCount(activity.getClass()); } catch (Exception e) { ... } the try {/ / create Application Application app = r.p ackageInfo. MakeApplication (false, mInstrumentation);
if(activity ! = null) {// Initialize the Activity with the appContext Activity. Attach (appContext,...) ; . } } catch (...) {... }returnactivity; } private ContextImpl createBaseContextForActivity(ActivityClientRecord r) { ContextImpl appContext = ContextImpl.createActivityContext(...) ; . }}Copy the code
As you can see, the Activity’s Base Context is created by the createActivityContext of the ContextImpl analyzed above.
The Base Context of a Service is created using ContextImpl createAppContext. That is, the Service Context and the Application Context have the same Resource. So skip the Service here, and let’s look at the Application Context.
Application Context
In the ActivityThread performLaunchActivity method above, you can see a call to makeApplication, which is the LoaedApk method:
public final class LoadedApk {
private Application mApplication;
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
if(mApplication ! = null) {returnmApplication; } Application app = null; Try {/ / create the Base Context ContextImpl appContext = ContextImpl. CreateAppContext (mActivityThread, this); / / create the Application and set the Base Context app. = mActivityThread mInstrumentation. The newApplication (cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { ... } // mApplication = app; .returnapp; } // get the mApplication ApplicationgetApplication() {
returnmApplication; }}Copy the code
The Application and its Base Context are basically created in the code above, but to fully understand the process, let’s take a look at the implementation of Instrumentation:
public class Instrumentation {
public Application newApplication(ClassLoader cl, String className, Context context) throws ... {
returnnewApplication(cl.loadClass(className), context); } static public Application newApplication(Class<? > clazz, Context context) throws ... {// reflection creates an Application object Application App = (Application) clazz.newinstance (); app.attach(context);returnapp; }}Copy the code
public class Application extends ContextWrapper implements ComponentCallbacks2 { /* package */ final void attach(Context Context) {// Call attachBaseContext of the parent class to set mBase attachBaseContext(context); }}Copy the code
As can be seen, Instrumentation is the use of reflection method to create an Application object, after creation, Application attach method will be executed to set the mBase member.
GetApplicationContext (); getApplicationContext ();
class ContextImpl extends Context {
ActivityThread mMainThread;
LoadedApk mPackageInfo;
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
}
Copy the code
As you can see from the code, getApplicationContext may return two values: the first is the getApplication method of LoadedApk, which returns the Application object just created; The second is the getApplication method for ActivityThread:
public final class ActivityThread {
Application mInitialApplication;
public Application getApplication() {
return mInitialApplication;
}
public static ActivityThread systemMain() {// Create ActivityThread ActivityThread thread = new ActivityThread(); thread.attach(true);
return thread;
}
private void attach(boolean system) {
mSystemThread = system;
if(! system) { ... }else{ try { mInstrumentation = new Instrumentation(); / / attention parameters getSystemContext (.) mPackageInfo ContextImpl context. = ContextImpl createAppContext (this, getSystemContext().mPackageInfo); / / create Application mInitialApplication = context. MPackageInfo. MakeApplication (true, null); mInitialApplication.onCreate(); } catch (Exception e) { ... }}... }}Copy the code
The mInitialApplication in ActivityThread is created when the systemMain method is executed, which is called when the SystemServer starts. Combined with the parameter getSystemContext().mpackageInfo, I guess that mInitialApplication corresponds to a certain APK of the system, that is, the Application at the system level. However, whether this is the case has not been further studied. You can do your own research if you’re interested.
summary
-
The Base Context for the Activity, Service, and Application are all created by ContextImpl and are ContextImpl objects, i.e. they are ContextImpl proxy classes
-
GetApplicationContext returns the Application object itself. In most cases, it corresponds to the Application object of the Application itself, but it can also be an Application of the system
Why is Base Context not recommended?
In general, using a proxy instead of using an object directly may serve two purposes:
- Customize your behavior
- The original object is not affected
The parent class of the Servcie and Application ContextWrapper has no custom behavior at all, while the parent class of the Activity ContextThemeWrapper defines custom behavior for Resource and Theme. Therefore, personal understanding:
- Base Context is not recommended for services and applications, because users may modify the Base Context and cause errors
- For an Activity, in addition to worrying about user changes, the Base Context and the Activity itself behave differently regarding the Reource and Theme (if Configuration is applied). Unexpected phenomena may occur when using the Base Context
I wrote a code to verify the Activity’s getResource problem:
public class MainActivity extends AppCompatActivity {
private Configuration mOverrideConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "getResources: " + getResources() + ", getBaseContext().getResources():"+ getBaseContext().getResources()); } / / because Android will automatically call before onCreate getResource / / so you need to perform here applyOverrideConfiguration @ Override public ResourcesgetResources() {
if (mOverrideConfiguration == null) {
mOverrideConfiguration = new Configuration();
applyOverrideConfiguration(mOverrideConfiguration);
}
returnsuper.getResources(); }}Copy the code
Output (I use xiaomi phone) :
getResources: android.content.res.MiuiResources@3c660a7,
getBaseContext().getResources():android.content.res.MiuiResources@5143954
Copy the code
As you can see from the source code, after Configuration is applied, the Activity’s getResource method returns a different object than the getBaseContext().getResources() method
conclusion
Context inheritance is as follows:
Context is the main steward of the Application, responsible for:
- Interaction of four components, including starting an Activity, Broadcast, Service, and obtaining a ContentResolver
- Obtain System or application Resources, including AssetManager, PackageManager, Resources, System Service, color, String, and Drawable
- Files, including obtaining cache folder, deleting files, SharedPreference related, etc
- Related to database (SQLite), including opening database, deleting database, obtaining database path, and so on
- Other ancillary features, such as setting up ComponentCallbacks, listen for configuration changes, running out of memory, and so on
ContextWrapper, ContextThemeWrapper, ContextImpl
- ContextWrapper and ContextThemeWrapper are proxy classes for Context. The difference is that ContextThemeWrapper has its own Theme and Resource. And the Resource can be passed its own configuration initialization
- ContextImpl is the main implementation class for the Context, which creates the Base Context for activities, services, and applications. The ContextWrapper is the ContextImpl object itself
- The main difference between ContextImpl and ContextThemeWrapper is that the ContextThemeWrapper has a Configuration object against which the Resource can be initialized
Activity Context, Service Context, Application Context, Base Context
- The Base Context for the Activity, Service, and Application are all created by ContextImpl and are ContextImpl objects, i.e. they are ContextImpl proxy classes
- The Service and Application use the same Recource, and the Activity uses a different Resource
- GetApplicationContext returns the Application object itself. In most cases, it corresponds to the Application object of the Application itself, but it can also be an Application of the system
Why not use Base Context:
- Base Context is not recommended for services and applications, because users may modify the Base Context and cause errors
- For an Activity, in addition to worrying about user changes, the Base Context and the Activity itself behave differently regarding the Reource and Theme (if Configuration is applied). Unexpected phenomena may occur when using the Base Context