认识一下Android里的Context
侯亮
(本文以Android 7.0为准)
1 什么是Context?
在Android平台上,Context是一个基本的概念,它在逻辑上表示一个运行期的“上下文”。
其实何止是Android平台,在其他平台上,一样有上下文的概念,比如一个进程,其实也是个上下文。我们在编写最简单的C语言程序时,凭什么写一句简单的malloc()就可以申请到内存,写一句简单的open()就可以打开一个文件?这些难道都是从天上掉下来的吗?当然不是。说穿了在进入main()函数之前,操作系统就已经为我们创建好了一个上下文,我们只是享受了上下文给我们提供的便利而已。
在Android平台上,上下文的概念被再一次细化了。Android的设计者估计在构思:为什么在以前的操作系统上,A应用只能以一个整体的形式来使用B应用呢?如果A应用只希望使用B应用的一部分,比如一个界面,那又该怎么办呢?在经过若干尝试之后,Android设计者终于形成了以下认识:应用里的每个重要UI界面都用一个小型上下文来封装,而每个重要的对外服务也都用一个小型上下文封装。这些小型上下文都容身到一个Android大平台上,并由Android统一调度管理,形成一个统一的整体,这样不就行了。从底层机制说,A应用是不可能“直接”使用B应用的界面的,因为它们的地址空间都不一样,但如果B应用的界面被包在一个小型上下文里,它就在一定程度上拥有了独立运行的能力,那么在逻辑上也就可以被A应用使用了。
当然,我毕竟不是Android的设计者,所有这些都是我自己杜撰的,大家看着便于理解即可。很明显,封装界面的小型上下文就是Android里的Activity啦,而封装服务的小型上下文就是Service。Android的上下文只需在逻辑上支持访问应用资源或系统服务即可,除此之外,它和普通对象也没什么不同啦。
2 Context的行为
Context体现到代码上来说,是个抽象类,其主要表达的行为列表如下:
Context行为分类
常用函数
使用系统提供的服务
getPackageManager()、getSystemService()......
基本功能
startActivity()、sendBroadcast()、registerReceiver()、startService()、bindService()、getContentResolver()......
【内部基本上是和AMS打交道】
访问资源
getAssets()、getResources()、getString()、getColor()、getClassLoader()......
和当前所寄身的进程之间的联系
getMainLooper()、getApplicationContext()......
和信息存储相关
getSharedPreferences()、openFileInput()、openFileOutput()、deleteFile()、openOrCreateDatabase()、deleteDatabase()......
既然是抽象类,最终就总得需要实际的派生类才行。在Android上,我们可以画出如下的Context继承示意图:
我们可以琢磨一下这张图,很明显,在Android平台上,Activity和Service在本质上都是个Context,而代表应用程序的Application对象,也是个Context,这个对象对应的就是AndroidManifest.xml里的部分。因为上下文访问应用资源或系统服务的动作是一样的,所以这部分动作被统一封装进一个ContextImpl辅助类里。Activity、Service、Application内部都含有自己的ContextImpl,每当自己需要访问应用资源或系统服务时,无非是把请求委托给内部的ContextImpl而已。
3 ContextWrapper
上图中还有个ContextWrapper,该类是用于表示Context的包装类,它在做和上下文相关的动作时,基本上都是委托给内部mBase域记录的Context去做的。如果我们希望子类化上下文的某些行为,可以有针对性地重写ContextWrapper的一些成员函数。
ContextWrapper的代码截选如下:
【frameworks/base/core/java/android/content/ContextWrapper.java】
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;
}
public Context getBaseContext() {
return mBase;
}
. . . . . .
. . . . . .
代码中的mBase是它的核心。
ContextWrapper只是简单封装了Context,它重写了Context所有成员函数,比如:
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
在笔者参与开发的一个项目中,用到过Android的插件技术,那时我们就构造过自己的ContextWrapper,并覆盖了其获取资源的函数。
因为ContextWrapper本身也继承于Context,所以就可以形成一种层层包装的效果。比如我们在一个Activity对象外,再包两层ContextWrapper,就可以形成如下示意图:
这种层层包的装饰结构从理论上说,可以形成复杂强大的组合。但是其预想的调用行为一定是从最外层的ContextWrapper开始调用,然后逐层委托。这种结构最怕使用者不按常理出牌,从中间某个层次开始调用,那么执行起来的效果也许就不如你所愿了。
事实上,这种情况还真就出现过。话说LayoutInflater作为布局解析工具,是广大Android开发者喜闻乐见的一个辅助类。我们常常通过调用LayoutInflater.from()来获取一个LayoutInflater对象,然后就可以用这个对象解析某个布局资源,并得到一个View对象。但是请小心,这里有一个坑。这个from()函数得到的LayoutInflater对象,在其内部经过多层弯弯绕,最终使用的是委托链最后的ContextImpl对象的直接外部Context,相关代码如下:
【frameworks/base/core/java/android/app/SystemServiceRegistry.java】
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
我们在这里先不关心那些弯弯绕,只需注意ctx.getOuterContext()即可。可以想见,即便我们调用from()函数时使用的是个多层ContextWrapper,PhoneLayoutInflater(LayoutInflater的一个派生类)内部也不会从最外层的ContextWrapper开始使用。如果你在外层某个ContextWrapper里重写了getResources()函数,原本打算解析布局时使用新的资源,但因为PhoneLayoutInflater压根就调用不到你的新getResources()函数,所以就会出现解析异常,提示说找不到资源。
为了解决这个尴尬的问题,LayoutInflater提供了cloneInContext()函数。我们可以参考ContextThemeWrapper的getSystemService()函数,有这么一句代码:
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
注意,并不是直接使用from()函数的返回值,还要克隆那么一下子。克隆就是重新new一个PhoneLayoutInflater对象,并且将其mContext调整为合适的ContextWrapper。示意图如下:
4 ContextImpl
前文我们已经说过,因为上下文访问应用资源或系统服务的动作是一样的,所以这部分动作被统一封装进一个ContextImpl辅助类里,现在我们就来看看这个类。
ContextImpl的代码截选如下:
【frameworks/base/core/java/android/app/ContextImpl.java】
class ContextImpl extends Context {
private final static String TAG = "ContextImpl";
private final static boolean DEBUG = false;
@GuardedBy("ContextImpl.class")
private static ArrayMap>
sSharedPrefsCache;
@GuardedBy("ContextImpl.class")
private ArrayMap mSharedPrefsPaths;
final ActivityThread mMainThread; // 依靠该成员和大系统联系
final LoadedApk mPackageInfo;
private final IBinder mActivityToken;
private final UserHandle mUser;
private final ApplicationContentResolver mContentResolver;
private final String mBasePackageName;
private final String mOpPackageName;
private final @NonNull ResourcesManager mResourcesManager;
private final @NonNull Resources mResources; // 指明自己在用的资源
private @Nullable Display mDisplay;
private final int mFlags;
private Context mOuterContext; // 指向其寄身并提供服务的上下文。
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
很明显,作为一个上下文的核心部件,ContextImpl有责任和更大的系统进行通信(我们可以把Android平台理解为一个大系统),所以它会有一个mMainThread成员。就以启动activity动作来说吧,最后会走到ContextImpl的startActivity(),而这个函数内部大体上是进一步调用mMainThread.getInstrumentation().execStartActivity(),从而将语义发送给Android系统。
ContextImpl里的另一个重要方面是关于资源的访问。这就涉及到资源从哪里来。简单地说,当一个APK被加载起来时,系统会创建一个对应的LoadedApk对象,并通过解码模块将APK里的资源部分加载进LoadedApk。每当我们为一个上下文创建对应的ContextImpl对象时,就会从LoadedApk里获取正确的Resources对象,并记入ContextImpl的mResources成员变量,以便以后使用。
另外,为了便于访问Android提供的系统服务,ContextImpl里还构造了一个小cache,记在mServiceCache成员变量里:
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
SystemServiceRegistry是个辅助类,其createServiceCache()函数的代码很简单,只是new了一个Object数组并返回之。也就是说,一开始这个mServiceCache数组,里面是没有内容的。日后,当我们需要访问系统服务时,会在运行期填写这个数组的某些子项。那么我们很容易想到,一个应用里启动的不同Activity,其内部的ContextImpl所含有的mServiceCache数组内容,常常也是不同的,而且一般情况下这个数组还是比较稀疏的,也就是说含有许多null。
大家在写代码时,常常会写下类似下面的句子:
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
该函数最终会调用到ContextImpl的同名函数,函数代码如下:
【frameworks/base/core/java/android/app/ContextImpl.java】
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
继续使用着辅助类SystemServiceRegistry。
【frameworks/base/core/java/android/app/SystemServiceRegistry.java】
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
其中用到的SYSTEM_SERVICE_FETCHERS是SystemServiceRegistry类提供的一张静态哈希表:
【frameworks/base/core/java/android/app/SystemServiceRegistry.java】
final class SystemServiceRegistry {
. . . . . .
private static final HashMap> SYSTEM_SERVICE_FETCHERS =
new HashMap>();
private static int sServiceCacheSize;
可以看到,这张哈希表的value部分必须实现ServiceFetcher>接口,对SystemServiceRegistry而言,应该是个CachedServiceFetcher派生类对象。CachedServiceFetcher类已经默认实现了getService()函数:
【frameworks/base/core/java/android/app/SystemServiceRegistry.java】
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache; // 终于看到ContextImpl的mServiceCache了
synchronized (cache) {
Object service = cache[mCacheIndex];
if (service == null) {
service = createService(ctx); // 如果cache里没有,则调用createService()
cache[mCacheIndex] = service;
}
return (T)service;
}
}
简单地说,就是每当getService()时,会优先从ContextImpl的mServiceCache缓存数组中获取,如果缓存里没有,才会进一步createService(),并记入缓存。而每个不同的CachedServiceFetcher派生类都会实现自己独有的createService()函数,这样就能在缓存里创建多姿多彩的“系统服务访问类”了。
SYSTEM_SERVICE_FETCHERS哈希表会在SystemServiceRegistry类的静态块中初始化,代码截选如下:
. . . . . .
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
. . . . . .
registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
new CachedServiceFetcher() {
@Override
public ActivityManager createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(),
ctx.mMainThread.getHandler());
}});
这里所谓的registerService()动作,其实主要就是向静态哈希表里插入新创建的CachedServiceFetcher对象:
private static void registerService(String serviceName, Class serviceClass,
ServiceFetcher serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
现在,我们画一张示意图,以一个Activity的ContextImpl为例:
5 小结
以上所讲的,就是Context的主要知识。虽然算不上多复杂,不过却是Android程序员都应该掌握的东西。这里总结出来,希望对各位同学有帮助。