关于 Context ,打从接触 Android 开始,就看到了相关文章,自己的笔记里也做了相关的记录,网上关于解析 Context 的优秀的博客已经很多了,这里纯粹当作记录啦。
Context,意为上下文。弄懂 Context 对于 Android 开发者还是比较重要的。首先,我们看下 Context 的主要继承结构。这里明确一点,是主要。
Context 主要应用了装饰模式。ContextWrapper 主要是 Context 的封装类,ContextImpl 则是 Context 的实现类。我们熟悉的 Activity、Service 和 Application 都是 Context 的子类。那么我们就很有必要看下 Context 类了。
/* *
* 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-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
可以看出 Context 是一个抽象类。我们通过它可以访问当前包的资源(getResources、getAssets)和启动其他组件(Activity、Service、Broadcast)。
接下来我们来看 ContextImpl 类,这里只截取其中关于 startActivity 的实现。总是 ContentImpl 是 Context 的实现类。
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
}
我们来看 ContextWrapper 类,里面维护了一个 Context 的引用,是Context 的封装类。
/**
* 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;
}
/**
* 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;
}
/**
* @return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources()
{
return mBase.getResources();
}
}
Activity、Service 和 Application 都是Context 的具体装饰类。那为什么 Activity 不直接继承 ContextWrapper 类,而是继承于 ContextThemeWrapper 这个好像多余的类呢?其实不然,我们来看下 ContentThemeWrapper 的源码注释:
/**
* A ContextWrapper that allows you to modify the theme from what is in the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
......
}
原来这个类的目的是让我们可以去修改或者说替换 Cotnext 的主题Theme。即android:theme 属性指定的。
Context 数量
关于一个应用中到底有多少个Context其实是显而易见的了。Context 一共有 Application、Activity 和 Service 三种类型,因此一个应用中 Context 数量为:
Context 数量 = Activity 数量 + Service 数量 + 1(Applcation)
Context 实例化过程的源码分析
Activity 对象中 Context 的实例化。
通过 startActivity 启动一个 Activity 进过一系列的调用方法之后会来到 ActivityThread(即主线程)的 performLaunchActivity()方法来创建一个 Activity 实例,然后回调 Activity 的 onCreate()等方法。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
if (activity != null) {
//创建一个Context对象
Context appContext = createBaseContextForActivity(r, activity);
......
//将上面创建的appContext传入到activity的attach方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
......
}
......
return activity;
}
可以看到,通过 createBaseContextForActivity(r,activity)
创建一个 Context 对象并在 attach 方法关联它。
我们再来看下 createBaseContextForActivity 方法。
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
//实质就是new一个ContextImpl对象,调运ContextImpl的有参构造初始化一些参数
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
//特别特别留意这里!!!
//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Activity对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Activity)。
appContext.setOuterContext(activity);
//创建返回值并且赋值
Context baseContext = appContext;
......
//返回ContextImpl对象
return baseContext;
}
总结:Activity 内部持有一个 ContextImpl 引用(ContextWrapper 的成员 mBase),进而 执行 Context(ContextImpl 继承于 Context) 中的方法。
Service 对象中 Context 的实例化。
通过 startService 或者 bindService 方法创建一个新 Service 时会回调 ActivityThread 类的 handleCreateService()方法完成相关数据操作。
private void handleCreateService(CreateServiceData data) {
......
//类似上面Activity的创建,这里创建service对象实例
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
......
}
try {
......
//不做过多解释,创建一个Context对象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//特别特别留意这里!!!
//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Service对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Service)。
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
//将上面创建的context传入到service的attach方法
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
......
} catch (Exception e) {
......
}
}
Application对象中 Context 的实例化。
其实,Application 对象中 Context 对象的实例化 和 Activity 、Service 中的基本一致。ContentImpl 的创建在 LoadedApk 类 的 makeApplication 方法实现。
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
//只有新创建的APP才会走if代码块之后的剩余逻辑
if (mApplication != null) {
return mApplication;
}
//即将创建的Application对象
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();
}
//不做过多解释,创建一个Context对象
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//将Context传入Instrumentation类的newApplication方法
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
//特别特别留意这里!!!
//ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Application对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Application)。
appContext.setOuterContext(app);
} catch (Exception e) {
......
}
......
return app;
}
应用程序 App 各种 Context 访问资源的唯一性
可以发现,Application、Activity 和 Service 都有自己 Context 的实例,那么平常我们通过 context.getResources() 得到的资源是不是同一份呢?
class ContextImpl extends Context {
......
private final ResourcesManager mResourcesManager;
private final Resources mResources;
......
@Override
public Resources getResources() {
return mResources;
}
......
}
context.getResources 方法获得的 Resources 对象就是上面 ContextImpl 的成员变量 mResources。mResource 的赋值操作如下:
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration) {
......
//单例模式获取ResourcesManager对象
mResourcesManager = ResourcesManager.getInstance();
......
//packageInfo对于一个APP来说只有一个,所以resources 是同一份
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (activityToken != null
|| displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
//mResourcesManager是单例,所以resources是同一份
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
overrideConfiguration, compatInfo, activityToken);
}
}
//把resources赋值给mResources
mResources = resources;
......
}
由此可以看出在设备等其他因素不变的情况下我们通过 Context 实例得到的 Resources 是同一套资源。这里要注意的是只有在设备等其他因素不变的情况下,这句话才是正确的。因为 res 下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,不同设备在访问同一个应用的时候可以不同,有可能是 hdpi 的 或者 xxhdpi 的。而且如果为横竖屏状态下提供了不同的资源,-land、-port,处在横屏状态下的 ContextImpl 和处在竖屏状态下的 ContextImpl 访问的资源也不是同一个资源对象。
getApplication 和 getApplicationContext 的区别
首先我们看 getApplication 方法,这个方法是 Activity 和 Service 中才有的。Application 和 Context 都没有此方法。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
public final Application getApplication() {
return mApplication;
}
......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
......
public final Application getApplication() {
return mApplication;
}
......
}
Activity 和 Service 提供了getApplication方法,而且返回类型都是 Application。这个 Application 是在 ActivityThread 中各自实例化时获取的 make Application 方法返回值。所以不同的 Activity 和 Service 返回的Application均为同一个全局对象。
接着我们来看 getApplicationContext 方法,
class ContextImpl extends Context {
......
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() :mMainThread.getApplication();
}
......
}
可以看到,getApplicationContext 方法 是Context 方法,而且返回值是 Context 类型,返回对象和上面通过 Service 或者 Activity 的 getApplication 返回的是一个对象。
因此 getApplication 方法 和 getApplicationContext 方法只是返回值类型不同,一个返回的是 Application ,另一个是 Context。还有就是依附的对象不同而已。
参考资料
[1] 工匠若水.Android应用Context详解及源码解析
[2] singwhatiwannaAndroid源码分析-全面理解Context