安卓c语言获取context,认识一下Android里的Context

认识一下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继承示意图:

ae2d51deb1387ccf58bc8f56f2c6d1da.png

我们可以琢磨一下这张图,很明显,在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,就可以形成如下示意图:

2e781668e96600dbbbaaad04efa30ac6.png

这种层层包的装饰结构从理论上说,可以形成复杂强大的组合。但是其预想的调用行为一定是从最外层的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。示意图如下:

77692cfb5f10185b107f8bdd288536df.png

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为例:

70f4198c1d6bad70f7aa3ce89c2da62b.png

5 小结

以上所讲的,就是Context的主要知识。虽然算不上多复杂,不过却是Android程序员都应该掌握的东西。这里总结出来,希望对各位同学有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值