文档章节

认识一下Android里的Context

悠然红茶
 悠然红茶
发布于 2018/05/05 10:32
字数 2986
阅读 1254
收藏 1

认识一下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里的<Application>部分。因为上下文访问应用资源或系统服务的动作是一样的,所以这部分动作被统一封装进一个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<String, ArrayMap<File, SharedPreferencesImpl>> 
sSharedPrefsCache;
    @GuardedBy("ContextImpl.class")
    private ArrayMap<String, File> 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<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    private static int sServiceCacheSize;

可以看到,这张哈希表的value部分必须实现ServiceFetcher<?>接口,对SystemServiceRegistry而言,应该是个CachedServiceFetcher<T>派生类对象。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<T>派生类都会实现自己独有的createService()函数,这样就能在缓存里创建多姿多彩的“系统服务访问类”了。

SYSTEM_SERVICE_FETCHERS哈希表会在SystemServiceRegistry类的静态块中初始化,代码截选如下:

    . . . . . .
    static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        . . . . . .
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), 
                       ctx.mMainThread.getHandler());
            }});

这里所谓的registerService()动作,其实主要就是向静态哈希表里插入新创建的CachedServiceFetcher对象:

    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

现在,我们画一张示意图,以一个Activity的ContextImpl为例:

 

5 小结

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


 

© 著作权归作者所有

悠然红茶
粉丝 343
博文 20
码字总数 106144
作品 0
西安
高级程序员
私信 提问
加载中

评论(1)

欧文kriy
很赞~~~感谢分享
Android 内存溢出(Out Of Memory)的总结

Android 内存溢出(Out Of Memory)的总结 发表于 2011/11/03 由 liuchengbao http://labs.ywlx.net/?p=1689 随着所做的游戏越来越复杂,图片越来越多,内存溢出已经成了不得不注意的问题了。 ...

迷途d书童
2012/03/26
330
0
~关于内存溢出的一些想法(Android)

最近看了一些资料,对android的程序的内存有一些想法,我提出来,请大家指正,目的是抛砖引玉。 首先提几个有意思的问题: 1,如果activty destroy掉的话,那么这个activty内的view所引用的b...

天空风
2012/04/24
484
0
android自学笔记《四》——应用程序结构分析

昨天在帖子里看到的一个分析方法,很简单,给大家分享下! 呵呵,由于我也是刚开始学习Android,还没有学到下面这些知识,这里就直接截图了。 看完他这个分析之后,我对Android程序有了一个简...

郭子
2012/02/03
2.5K
1
彻底认识 PendingIntent

最近在写一个闹钟程序的时候使用到了 PendingIntent, 而且是两个地方用到,一个是 AlarmManager 定时的时候, 另一个是在点击通知进入应用的时候。其实我早就想深入研究一下 PendingIntent...

Jinux111
2018/05/25
0
0
Android MVP架构实现

最近在学习Android的MVP架构,在网上找了许多资料都没有一个清晰的认识,偶然看到简书上骆驼骑士前辈的文章,对MVP的实现过程有了一个较为清晰的认识。后又研究了一下Google官方Demo,分别对...

君琬
2018/11/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

家庭作业——苗钰婷

2 编写一个程序,发出一声警报,然后打印下面的文本: Startled by the sudden sound, Sally shouted, "By the Great Pumpkin, what was that! #include<stdio.h>int main(){......

OSC_Okruuv
9分钟前
1
0
经典系统设计面试题解析:如何设计TinyURL(一)

原文链接: https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR 编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助...

APEMESH
10分钟前
1
0
2.面向对象设计原则(7条)

开闭原则 开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。 实现方法 可以通过“抽象约束、封装变化”来实...

Eappo_Geng
12分钟前
1
0
8086汇编基础 debug P命令 一步完成loop循环

    IDE : Masm for Windows 集成实验环境 2015     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   ......

志成就
16分钟前
1
0
使用nodeJS实现前端项目自动化之项目构建和文件合并

本文转载于:专业的前端网站➜使用nodeJS实现前端项目自动化之项目构建和文件合并 前面的话   一般地,我们使用构建工具来完成项目的自动化操作。本文主要介绍如何使用nodeJS来实现简单的项...

前端老手
30分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部