源码分析 getApplication和getApplicationContext的区别

本文打算从原理出发,通过分析源码,找到Activity的成员变量Application的出处,以此分析俩个方法的区别。

背景知识

Context关系图.png

首先上一张老生常谈的图。

由图的继承关系可知,Activity、Application、Service都继承自ContextWrapper;
ContextWrapper是个代理类,它的部分源码截图如下:

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

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }
    
    ...
}

从源码可以看出,ContextWrapper的方法都是通过代理"另一个"Context实现的。这里暂时给出结论:这个代理类就是ContextImpl,它是Context的真正实现类。稍后我们会用源码验证结论。

也就是说,Activity内部有成员变量ContextImpl的实例,以此实现我们常用的Context的方法。

源码分析

了解了上述背景知识之后,下面来正式开始分析Activity的方法:getApplication()getApplicationContext()的区别。

getApplication

我们先来看getApplication()的源码:

public final Application getApplication() {
    return mApplication;
}

Activity内部有成员变量mApplication,getApplication()直接返回的就是它,那么它的来源是哪呢?

这里为了使流程更加清晰,我将从源码源头分析,正推出结果,实际学习则是通过结果逆向反推出源头的。

ActivityThread是Activity的启动类,通过调用ActivityThread.performLaunchActivity(),即可生成要启动的Activity实例,本文不会专程去分析应用程序或Activity的启动原理,只需要知道它是Activity实例的创建方法就可以了。下面我们来看下这个方法的部分源码:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ActivityInfo aInfo = r.activityInfo;
        ...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation); #1

            ...

            if (activity != null) {
                ...
                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);#2
                        
                        ...

注释1处,调用r.packageInfo.makeApplication生成了Application实例;注释2处,调用activity.attch,将Application传给了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,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context); 

        ...
        mApplication = application; #1

注释1处,直接将application赋值给了mApplication,看来这就是我们要找的源头。

回到上一步,r.packageInfo.makeApplication生成了Application实例,这里,r.packageInfo实际是LoadedApk对象,故名思义,它通过ClassLoader去加载APK文件,以此来获取我们需要的类。不明白ClassLoader可以稍微去了解下,不了解也没关系,只需要知道通过ClassLoader可以将类文件转化为类就可以了。

下面来看LoadedApk.makeApplication()源码:

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        ...

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }#1

        try {
            java.lang.ClassLoader cl = getClassLoader(); #2
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); #3
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);#4
            ...
        } catch (Exception e) {
            ...
        }
        ...
        mApplication = app;

        ...
        return app;
    }

这里为了理清流程,省略了部分无关主题的代码。

注释1处,获取要创建的Application的ClassName,如果没有自定义的Application,则使用系统原生"android.app.Application"
注释2处,获取ClassLoader,它可以通过ClassName获取需要的Class。
注释3处,获取ContextImpl,上文说过Application继承自ContextWrapper,它仅仅是Context的代理,真正的实现类是ContextImpl。这一结论很快将得到证实。
在得到必要的三个参数之后,就可以创建Application了,注释4处执行了创建Application的方法:

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

通过ClassLoader和ClassName,获取到Class<Application>实例,然后执行下一步:

    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance(); #1
        app.attach(context); #2
        return app;
    }

注释1处,通过class反射创建了Application实例。这就是getApplication()的最终源头。

但是还没有完,注释2处调用了app.attach(context);,联系上下文可以知道这个context就是之前创建的ContextImpl,而attach最终调用的是ContextWrapper.attachBaseContext,还记得文章开头的ContextWrapper源码吗?找一下这个方法,你就会发现,Application作为ContextWrapper,其真实调用的Context就是这里传过来的ContextImpl

讲到这里,我们已经找到了getApplication()的源头。同时也会发现:ContextImpl从始至终都没有与getApplication()有任何关联,那为什么还要大费周章的讲它呢?别急,还有一个方法getApplicationContext()没说呢。

getApplicationContext

先来看一下Activity.getApplicationContext()源码,该方法是Activity的父类:ContextWrapper实现的。

    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }

通过上文可知,mBase即是ContextImpl(但是注意不是上文Application的ContextImpl,下文会有分析),来看下ContextImpl.getApplicationContext()的源码:

    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }

mPackageInfo就是LoadedApk实例,ContextImpl.LoadedApk.getApplication()直接返回了LoadedApk的成员变量mApplication。从这里还无从得知mApplication的来源,所以我们可以先分析ContextImpl实例的来源。

回到上文中Activity实例的创建方法performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ActivityInfo aInfo = r.activityInfo;
        ...

        ContextImpl appContext = createBaseContextForActivity(r); #1
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation); 

            ...

            if (activity != null) {
                ...
                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);#2
                        
                        ...

注释1处,创建了ContextImpl实例。
注释2处,调用了activity.attach,还记得它的源码吗?

    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,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);  #1

        ...
        mApplication = application; 

注释1处,ContextImpl被赋值给了Activity,看来这就是ContextImpl的最初源头。

回到上一步注释1处,来看下ContextImpl实例的创建过程createBaseContextForActivity(r)

    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ...

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

        ...
        return appContext;
    }

第二个参数r.packageInfo就是LoadedApk对象,它在这里赋值给了ContextImpl。联系上下文,也就是说,getApplicationContext()的application,就来自r.packageInfo的成员变量mApplication。

那么,r.packageInfo的成员变量mApplication,又是什么时候赋值的呢?

还记得上文Application是怎么创建的吗?

r.packageInfo.makeApplication(false, mInstrumentation);

再来回顾一遍LoadedApk.makeApplication源码,这次只需要关注注释1处:

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        ...

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader(); 
            ...
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); 
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            ...
        } catch (Exception e) {
            ...
        }
        ...
        mApplication = app; #1

        ...
        return app;
    }

在上文中创建的Application,在赋值给Activity的成员变量mApplication的同时,还赋值给了LoadedApk的mApplication,之后LoadedApk又作为了ContextImpl的成员变量之一传给了Activity。

至此得知,对于Activity而言,getApplication()getApplicationContext()返回的是同一个Application,它们都是LoadedApk通过ClassLoader创建的,只不过赋值给了不同的成员变量而已。

流程图

下面梳理下它们的创建与赋值流程。

getApplication

getApplication流程图.png

getApplicationContext

getApplicationContext流程图.png

由流程图可以看出,Application的来源均是LoadedApk.makeApplication(),故俩个方法返回的是同一个Application。

写在最后

分析俩个方法的区别,旨在理清Android的启动流程,加深对Android系统的理解,重在过程,而非结论。

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

推荐阅读更多精彩内容