Context在应用程序开发中经常被使用,Context被翻译为“上下文”,而在Android中的
Context应该被翻译为“场景”。
什么是Context
一个Context意味着一个场景,一个场景就是用户和操作系统交互的一种过程。比如打电话的
时候,场景包括电话程序对应的界面,以及隐藏在界面后的数据;当看到短信的时候,场景包括
短信界面,以及隐藏在后面的数据。
其实一个Activity就是一个Context,一个Service也是一个Context。
从语义角度上审视一下Context。Android的攻城狮把“场景”抽象为Context类,如上所述,用
户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是由界面的场景,还有一些
没有界面的场景,比如后台运行的Service。一个应用程序可以认为是一个工作环境,用户在这个工作
环境中会切换到不同的场景中。这就像一个前台秘书一样,她可能需要接待客人,可能要打印文件,还
可能要接听客户电话,而这些就称之为不同的场景,前台秘书可称之为一个应用程序。
Activity类基于Context,而Service类也是基于Context。Activity除了基于Context类之外,还实现了
一些其他重要接口,从设计的角度来看,interface仅仅是某些功能,而extends才是类的本质,即Activity
的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充之后的类称之为一个
Activity或者Service。
都知道Activity内部包含一些特别的方法,比如onCreate()、onPause()、onStart()等方法,这些是只有
Activity才有的。Activity尽管有一些特别的函数接口,但本质上就是一个Context。所以,Context被设计
为一个abstract类,Activity仅仅是基于Context而已。Google程序员还认为Service本质上是一个Context
所以Service也是仅仅基于Context而已。
一个应用程序中包含多少个Context对象
在以往的应用程序开发中,经常会调用Context的一些方法,这些方法看起来似乎会返回一些全局
的对象,而不仅仅是某个Activity,于是问题就来了,一个应用程序到底有多少个Context对象呢?比
如,Context.getResources()返回该应用程序所对应的Resources类对象,无论从哪个Activity中调用
都会返回同一个Resources对象。
1.:一个Activity就是一个场景(Context),一个Service也是一个场景,所以,应用程序中有多少
个Activity或者Service,就会有多少个Context对象。
2.:getResources()等方法的确返回的是同一个全局对象。
Context相关类的继承关系
Context相关的类的继承关系如图所示。
Context类本身是一个纯abstract类。
为了使用方便,又定义了ContextWrapper类,如其名而言,这只是一个包装而已。ContextWrapper
构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于
给ContextWrapper对象中指定真正的Context引用。调用ContextWrapper的方法都会转向其所包含的真
正的Context对象,如下列部分ContextWrapper源代码所示:
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
ContextThemeWrapper类,如其名称所示,其内部包含了与主题(Theme)相关的接口,这里所说的主题就
是指在AndroidMainfest.xml中通过android:theme为Application元素或者Activity元素指定的主题。
我们可以发现 Service 是直接继承ContextWrapper类的,而Activity需要继承ContextThemeWrapper。这
是因为只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直
接继承于ContextWrapper。
Contextlmpl类真正实现了 Context中所有的函数,应用程序中所调用的各种Context类的方法,其
实现均来自于该类。
创建Context
每一个Activity本质上就是一个Context,因为Activity就是基于ContextWrapper类,然而对于
ContextWrapper类,必须为其指定真正的Context对象,而真正实现了 Context类的是Contextlmpl
类。那么“真正的Context” (Contextlmpl)是如何创建的?
每一个应用程序在客户端都是从ActivityThread类开始的,创建Context对象也是在该类中完成,具
体创建Contextlmpl类的地方一共有7 处,分别如下:
1.在LoadedApk.makeApplication()中:
LoadedApk部分源码:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
.getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
}
return app;
}
一目了然,我们看到了
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this)
这是LoadedApk中的源码。而在ActivityThread中调用了LoadedApk的这个makeApplication
方法去创建了。
ActivityThread.handleBindApplication源码中:
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
mCompatConfiguration = new Configuration(data.config);
mProfiler = new Profiler();
if (data.initProfilerInfo != null) {
mProfiler.profileFile = data.initProfilerInfo.profileFile;
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
}
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName,
UserHandle.myUserId());
if (data.persistent) {
// Persistent processes on low-memory devices do not get to
// use hardware accelerated drawing, since this can add too much
// overhead to the process.
if (!ActivityManager.isHighEndGfx()) {
HardwareRenderer.disable(false);
}
}
if (mProfiler.profileFd != null) {
mProfiler.startProfiling();
}
// If the app is Honeycomb MR1 or earlier, switch its AsyncTask
// implementation to use the pool executor. Normally, we use the
// serialized executor as the default. This has to happen in the
// main thread so the main looper is set right.
if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
Message.updateCheckRecycle(data.appInfo.targetSdkVersion);
/*
* Before spawning a new process, reset the time zone to be the system time zone.
* This needs to be done because the system time zone could have changed after the
* the spawning of this process. Without doing this this process would have the incorrect
* system time zone.
*/
TimeZone.setDefault(null);
/*
* Initialize the default locale in this process for the reasons we set the time zone.
*/
Locale.setDefault(data.config.locale);
/*
* Update the system configuration since its preloaded and might not
* reflect configuration changes. The configuration object passed
* in AppBindData can be safely assumed to be up to date
*/
mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
mCurDefaultDisplayDpi = data.config.densityDpi;
applyCompatConfiguration(mCurDefaultDisplayDpi);
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
/**
* Switch this process to density compatibility mode if needed.
*/
if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
== 0) {
mDensityCompatMode = true;
Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
}
updateDefaultDensity();
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
if (!Process.isIsolated()) {
final File cacheDir = appContext.getCacheDir();
if (cacheDir != null) {
// Provide a usable directory for temporary files
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
setupGraphicsSupport(data.info, cacheDir);
} else {
Log.e(TAG, "Unable to setupGraphicsSupport due to missing cache directory");
}
}
final boolean is24Hr = "24".equals(mCoreSettings.getString(Settings.System.TIME_12_24));
DateFormat.set24HourTimePref(is24Hr);
View.mDebugViewAttributes =
mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
/**
* For system applications on userdebug/eng builds, log stack
* traces of disk and network access to dropbox for analysis.
*/
if ((data.appInfo.flags &
(ApplicationInfo.FLAG_SYSTEM |
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
StrictMode.conditionallyEnableDebugLogging();
}
/**
* For apps targetting SDK Honeycomb or later, we don't allow
* network usage on the main event loop / UI thread.
*
* Note to those grepping: this is what ultimately throws
* NetworkOnMainThreadException ...
*/
if (data.appInfo.targetSdkVersion > 9) {
StrictMode.enableDeathOnNetwork();
}
if (data.debugMode != IApplicationThread.DEBUG_OFF) {
// XXX should have option to change the port.
Debug.changeDebugPort(8100);
if (data.debugMode == IApplicationThread.DEBUG_WAIT) {
Slog.w(TAG, "Application " + data.info.getPackageName()
+ " is waiting for the debugger on port 8100...");
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.showWaitingForDebugger(mAppThread, true);
} catch (RemoteException ex) {
}
Debug.waitForDebugger();
try {
mgr.showWaitingForDebugger(mAppThread, false);
} catch (RemoteException ex) {
}
} else {
Slog.w(TAG, "Application " + data.info.getPackageName()
+ " can be debugged on port 8100...");
}
}
// Enable OpenGL tracing if required
if (data.enableOpenGlTrace) {
GLUtils.setTracingLevel(1);
}
// Allow application-generated systrace messages if we're debuggable.
boolean appTracingAllowed = (data.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Trace.setAppTracingAllowed(appTracingAllowed);
/**
* Initialize the default http proxy in this process for the reasons we set the time zone.
*/
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
if (b != null) {
// In pre-boot mode (doing initial launch to collect password), not
// all system is up. This includes the connectivity service, so don't
// crash if we can't get it.
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
ProxyInfo proxyInfo = service.getProxy();
Proxy.setHttpProxySystemProperty(proxyInfo);
} catch (RemoteException e) {}
}
if (data.instrumentationName != null) {
InstrumentationInfo ii = null;
try {
ii = appContext.getPackageManager().
getInstrumentationInfo(data.instrumentationName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
if (ii == null) {
throw new RuntimeException(
"Unable to find instrumentation info for: "
+ data.instrumentationName);
}
mInstrumentationPackageName = ii.packageName;
mInstrumentationAppDir = ii.sourceDir;
mInstrumentationSplitAppDirs = ii.splitSourceDirs;
mInstrumentationLibDir = ii.nativeLibraryDir;
mInstrumentedAppDir = data.info.getAppDir();
mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
mInstrumentedLibDir = data.info.getLibDir();
ApplicationInfo instrApp = new ApplicationInfo();
instrApp.packageName = ii.packageName;
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.splitSourceDirs = ii.splitSourceDirs;
instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
java.lang.ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
mInstrumentation.init(this, instrContext, appContext,
new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
mProfiler.handlingProfiling = true;
File file = new File(mProfiler.profileFile);
file.getParentFile().mkdirs();
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
} else {
mInstrumentation = new Instrumentation();
}
if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
}
// Allow disk access during application and provider setup. This could
// block processing ordered broadcasts, but later processing would
// probably end up doing the same disk access.
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, nu