【Android 源码解析】Context 及其子类

一、Context 类继承关系

Context 类继承关系

上图描述了 Context 类的主要继承关系,Context 是抽象类,提供了应用环境全局信息的接口;Context 有两个直接子类 ContextWrapper 和 ContextImpl,顾名思义,ContextWrapper 是上下文功能的封装类,ContextImpl 是上下文功能的实现类;Applicaiton 和 Service 直接继承自 ContextWrapper 类,Activity 继承自 ContextThemeWrapper 类,因为 Activity 提供 UI 显示,需要有主题。

  • Context 类提供了一组通用的 API;
  • ContextImpl 实现了 Context 所有的功能,为 Activity 等应用组件提供 Context 对象;
  • ContextWrapper 包含一个真正的 ContextImpl 的引用 mBase,然后就是 ContextImpl 的装饰者模式;
  • ContextThemeWrapper 内部包含了 Theme 相关的接口,即 android:theme 指定的属性;

二、Context 的数量及作用域

Context 数量 = Activity 数量 + Service 数量 + 1(Application 数量)


Context 作用域

由于 Context 的具体实例是由 ContextImpl 类去实现的,因此在绝大多数场景下,Activity、Service 和 Application 这三种类型的 Context 都是可以通用的。
不过有几种场景比较特殊:

  1. Dialog 则必须在一个 Activity 上面弹出(除非是 System Alert类型的 Dialog),因此在这种场景下,我们只能使用 Activity 类型的 Context,否则将会出错。
  2. 非 Activity 类型的 Context 并没有所谓的任务栈,所以待启动的 Activity 就找不到栈了。解决这个问题的方法就是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就为它创建一个新的任务栈,而此时 Activity 是以 singleTask 模式启动的。所有这种用 Application 启动 Activity 的方式不推荐使用,Service 同 Application。
  3. 在 Application 和 Service 中去 layout inflate 也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。

三、获取 Context 对象

  1. View 的 getContext 方法,返回 View 的 Context,通常是当前正在展示的 Activity 的对象,通常在自定义 View 时会用到;
  2. Activity.this 返回当前 Activity 实例,如果是 UI 控件需要使用 Activity 作为 Context 对象,但是默认的 Toast 实际上使用 ApplicationContext 也可以;
  3. Activity 和 Service 的 getApplication 方法,返回 Application 的实例;
  4. getApplicationContext 方法,获取当前应用的 Context 对象,也就是 Application 的实例,与上一个方法不同的是依附的对象不同;

四、Context 引起的内存泄漏

一般 Context 造成的内存泄漏都是 Activity 被销毁后还被其它对象强引用也就是长生命周期对象持有短生命周期对象的引用而造成 Activity 不能被 GC 回收而导致的。
造成内存泄漏的原因主要有两个:

  1. 静态的 Activity Context 或任何包含 Activity Context 的对象(如 View)没有在该 Activity 销毁的时候置空引用;
  2. 非静态内部类或匿名内部类持有外部类的引用,在 Activity 销毁后依然执行任务;

常见的场景如下:

static 直接或间接修饰 Activity Context

解决方法:

  1. 在任何时候都不建议用 static 修饰 Activity,因为 static 变量和应用的存活时间是一样的,这样 Activity 生命周期结束时,引用仍然被持有;
  2. 在适当的地方比如 onDestroy 方法中置空 Activity Context 的引用;
  3. 使用软引用解决,确保在 Activity 销毁时,垃圾回收机制可以将其回收。
单例引用 Activity Context

解决方法:

  1. 使用 Applicaion Context 代替 Activity Context ;
  2. 在调用的地方使用弱引用
非静态或匿名内部类执行耗时任务

非静态内部类或匿名内部类都会拥有所在外部类的引用
解决方法:
用 static 修饰内部类或者匿名内部类所在的方法;

静态内部类持有外部类静态成员变量

虽然静态内部类的生命周期和外部类无关,但是如果在内部类中想要引入外部成员变量的话,这个成员变量必须是静态的了,这也可能导致内存泄露。
解决方法:
使用弱引用

Handler 引起的内存泄漏

这种情况和非静态或匿名内部类执行耗时任务的原因一样。
总结一下:

  1. 当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的 Context
    2:不要让生命周期长于 Activity 的对象持有到 Activity 的引用。引用了要及时置空
    3:尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

五、各种 Context 在 ActivityThread 中实例化过程源码分析

Context 的实现是 ContextImpl,Activity、Application 和 Service 的创建都是在 ActivityThread 中完成的。

Activity 中 ContextImpl 实例化源码分析

startActivity 启动一个新的 Activity 时系统会回调 ActivityThread 的 handleLaunchActivity() 方法,该方法内部会调用 performLaunchActivity() 方法去创建一个 Activity 实例,然后回调 Activity 的 onCreate() 等方法。所以 Activity 的 ContextImpl 实例化是在 ActivityThread 类的 performLaunchActivity 方法中,如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
            //已经创建好新的activity实例
            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;
    }

上面是 performLaunchActivity 的核心代码,在 performLaunchActivity 方法里面创建了 Activity 的实例,然后然后调用 createBaseContextForActivity 方法创建一个 Context 对象,这个对象是 ContextImpl,将前面创建的 Context 对象传入到 Activity 的 attach 方法中去。

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;
    }

再看 createBaseContextForActivity 方法,实质就是通过 ContextImpl 的有参构造初始化一些参数创建一个 ContextImpl 对象,创建一个 Context 对象将创建的 ContextImpl 赋值给 Context 变量并返回。
ContextImpl 有一个成员变量 mOuterContext,通过 ContextImpl 的 setOuterContext 方法将传进来的 Activity 对象赋值给 mOuterContext,这样 ContextImpl 就持有了 Activity 的引用。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        //特别特别留意这里!!!
        //与上面createBaseContextForActivity方法中setOuterContext语句类似,不同的在于:
        //通过ContextThemeWrapper类的attachBaseContext方法,将createBaseContextForActivity中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImpl
        attachBaseContext(context);
        ......
    }

attach 里面关键的代码是调用 ContextThemeWrapper 类的 attachBaseContext 方法将 createBaseContextForActivity 创建的 ContextImpl 实例赋值给 ContextWrapper 的 mBase 变量,这样 ContextWrapper 的成员 mBase 就被实例化为 Context 的实现类 ContextImpl

Service中ContextImpl实例化源码分析

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) {
            ......
        }
    }

与实例化 Activity 的过程相似,创建一个 ContextImpl 对象赋值给 mBase 变量,同时 ContextImpl 的 mOuterContext 变量也持有 Service 的引用,这里创建 ContextImpl 对象是用的 ContextImpl.createAppContext 方法

public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
            //特别特别留意这里!!!
            //与上面handleCreateService方法中setOuterContext语句类似,不同的在于:
            //通过ContextWrapper类的attachBaseContext方法,将handleCreateService中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImpl
        attachBaseContext(context);
        ......
    }

Application中ContextImpl实例化源码分析

创建 Application 的过程也在 ActivityThread 类的 handleBindApplication() 方法完成相关数据操作,而 ContextImpl 的创建是在该方法中调运 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;
    }

参考:
Android应用Context详解及源码解析

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

推荐阅读更多精彩内容