Android Context的那一套

前言

本文主要讲解Context这一套的东西,包括Context相关的一套所涉及的设计模式,只有当理解设计模式之后我们才能更好的理解Context。然后希望大家看完本篇文章之后,对Context能够了解得更多,平时不仅仅限于只会使用这一套东西。

内容

Activity、Service和Application的流程图(下面暂时只提及Activity、可类推...):


Context到Activity
Context到服务
Context到Application

这一套流程图很多人都已经知道了,Context包含着很多的抽象方法,包括最常见的startActivity()、getApplicationContext()、getColor()......。因为活动继承自Context,而Context里面的抽象方法已经被实现过了,那么这些方法是我们在活动中都可以调用到的方法,是不是已经有些感觉了?就是对“Contex->环境”的这个概念的理解,身处Context这个“大环境中”,我就可以实现一些功能:进行资源访问、访问其它组件和调用底层API等等。

活动继承自Context,但是活动一个Contxet的抽象方法都不用实现,因为Context的抽象方法全被ContextWrapper覆写了,但是Context真正的实现类又是ContextImpl。那么这之中的奥妙之处在哪里呢?我们先看了一个设计模式——装饰者模式。

设计模式

这里参考《Android源码设计模式》上的一个例子,很通俗易懂。
一个人需要穿衣服,而衣服就是对他人的修饰。

/**
 * 一个人的抽象
 */
public abstract class Person {

    /**
     * 穿衣服的抽象
     */
    public abstract void dressed();
}

/**
 * 一个人的具体实现
 */
public class PersonImpl extends Person {

    /**
     * 穿衣服的具体实现
     */
    @Override
    public void dressed() {
        System.out.println("穿上内衣内裤");
    }
}

/**
 * 人的穿着的抽象
 */
public abstract class PersonCloth extends Person {

    private Person mPerson;

    public PersonCloth(Person person) {
        mPerson = person;
    }

    @Override
    public void dressed() {
        mPerson.dressed();
        System.out.println("穿一件上衣、穿一条裤子");
    }
}

/**
 * 很贵的穿着装饰
 */
public class ExpensiveCloth extends PersonCloth {
    public ExpensiveCloth(Person person) {
        super(person);
    }

    @Override
    public void dressed() {
        super.dressed();
        System.out.println("穿一件2万的外套、一双5千的鞋子");
    }
}

    /**
     * 实现
     */
    public static void main(String[] args) {
        Person person = new PersonImpl();
        PersonCloth cloth = new ExpensiveCloth(person);
        cloth.dressed();
    }

分析: 人和它的子类很容易理解,关键在于它的装饰者的理解。装饰者需要拿到一个“人”,需要它装饰的对象,然后需要该对象进行装饰,也就是扩展其它的功能。为了代码的可扩展性,这里的装饰者也是具有继承的层级分布。

显然,继承自Context之后的类都可以理解成它的装饰者,但是也不尽是装饰者,比如Activity之类的,显然Activity是利用Context这一套环境来实现自己的功能。

装饰模式的好处:

  • 很符合开闭原则,通过扩展代码来修改、增加功能, 利于后期项目迭代。
  • 灵活性高,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。

Context的实现过程

这里就不再讲解Context的实现类了,ContextImpl就是实现了Context的所有抽象方法,就是一个资源的获取、管理和组件之间的管理等等。我们找Context是何时被初始化和赋值在ContextWrapper中的。

Activity的启动是在ActivityThread中,找到关键方法performLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       //配置信息
       ......

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);    
      
        //这里拿到的是成员对象Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        //连接Activity
        ......    
       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, window, r.configCallback);     
          ......

        return activity;
    }

咱们先看ContextImpl对象是如何创建的:

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ......

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

        //在调试状态下,如果有辅助显示器,则在辅助显示器上显示
        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
        String pkgName = SystemProperties.get("debug.second-display.pkg");
        if (pkgName != null && !pkgName.isEmpty()
                && r.packageInfo.mPackageName.contains(pkgName)) {
            for (int id : dm.getDisplayIds()) {
                if (id != Display.DEFAULT_DISPLAY) {
                    Display display =
                            dm.getCompatibleDisplay(id, appContext.getResources());
                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                    break;
                }
            }
        }
        return appContext;
    }

createActivityContext()方法中还是采用直接new的方法来获取实例。这也是为什么Context能够作为Activity、Service、Application的父类了,ContextImpl没有区分当前是哪一个Context子类,因此是一个通用的“环境”。

ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);

我们回到performLaunchActivity()方法中,该方法中还有一句makeApplication(),就是把成员对象Application拿到,因此它只有一个,那么一个应用中的Context实例对象应该有 “活动数+服务数+1”个。

接下来再去activity.attach()中看appContext是如何被设置的,进去之后就有一句attachBaseContext(context);,再次点击跳转,调用super.attachBaseContext(newBase);,回到ContextWrapper类中,执行

protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

到这儿就设置完成了。

此处有一个注意点getBaseContext()getApplicationContext()的区别,想必现在就很清楚了,为什么推荐使用getApplicationContext()呢?因为这个方法使用的是ActivityThread中的成员对象mApplication的Context引用,整个应用只会有一个Application对象,所以怎么用都不会存在内存泄漏,而getBaseContext()则是拿到当前活动或者服务的Context应用,因此使用不当,导致当前活动或服务应用被持有而无法释放,内存泄漏。

总结

我是直接在ActivityThread中找到Activity的启动方法的,但是在Activity实例之前是有一大波逻辑分析的,感兴趣的可以看这篇文章
Context还是很容易理解的,光听“环境”这个词语很很懵,但是看一看源码就一目了然了嘛~

笔者水平有限,有写得不好的地方,请大胆指出~

请支持原创哦~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容