四大组件中的Context来源浅析

稍微有些源码经验的朋友应该都知道,Activity中的Context的实现类是ContextImpl 。但是这个Context到底是如何来的呢,今天就来分析一下,本文所引用源码版本为Android 8.0。

1.Activity

首先从Activity中开始。其实Activity就是一个Context,先看他的继承关系:



首先Context是一个抽象类,ContextWrapper 实现了其所有方法,不过这个实现有点特殊,简单看一下:

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

    ....

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return mBase.getSharedPreferences(name, mode);
    }

    @Override
    public File getFilesDir() {
        return mBase.getFilesDir();
    }
    ....
}

它的所有实现方法都是借助于mBase的,但是我们发现mBase的类型也是一个Context 。发现绕了一圈又回到原点了,不过如果熟悉设计模式的话,这个其实属于代理模式。ContextWrapper 就相当于代理类,真正重要的时被代理对象,一般在构造方法或者某些特殊方法中传进来。

我们可以看一下设置mBase的地方,只有两个构造方法和attachBaseContext。到这里如果想要继续往下分析,就需要对activity的启动有所了解了。一个Activity再怎么特殊也是一个java类,肯定有实例化的地方,有实例化的地方就会用到构造函数。关于Activity的实例化过程这里就不详细叙述了,直接告诉大家,在ActivityThread中有这么一个方法:

android\frameworks\base\core\java\android\app\ActivityThread.java

   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ....
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
        try {
            ....
                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;
    }
android\frameworks\base\core\java\android\app\Instrumentation.java

    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

是不是看到了想看的Activity activity = new Activity();谁说不能new一个Activity ,只不过要看在什么地方,这里用的反射,但是可以看到这里创建Activity时调用的是无参的构造。有人可能有疑问,ContextWrapper 只有一个单参数构造啊,向他的子类看:

    public ContextThemeWrapper() {
        super(null);
    }

这里有一个无参构造,mBase赋值为null,为空当然是不行的。所以我们可以肯定,是调用了attachBaseContext给他赋的值。但是是哪里调用的呢?我们回头再看ActivityThread的performLaunchActivity。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);
        ...
}

这个拥有一大堆参数的方法第一步就是调用attachBaseContext。其实我们在回顾performLaunchActivity方法,一个activity被创建出来后,除了调用了一次getClass(),基本上第一时间调用了attach(),所以相当于第一时间调用attachBaseContext(),给mBase赋值,可见Context的重要性。

回到正题,attachBaseContext的context参数来自attach,那么attach的呢?在ActivityThread中他的名称叫appContext ,看我截取的performLaunchActivity方法的第一行,的确是ContextImpl 类型的变量。但会不会是多态什么的,实际类型有是别的呢?我们可以继续看看

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

        ....
        return appContext;
    }

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

        ....
        return context;
    }

看到new应该放心了吧,的确童叟无欺,Activity中的Context实际上就是一个ContextImpl 实例化对象。以后在想看一些Context的方法具体实现的时候,就不用Ctrl+左键跳转到ContextWrapper 后一脸懵逼去百度了。

2.Service

看完Activity后我们我们在看一下和Activity很类似的Service的Context。

Service通过继承关系看,也是继承于ContextWrapper 的,和Activity类似


我们简单看一下,首先从创建对象开始:

    private void handleCreateService(CreateServiceData data) {
        ....
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = (Service) cl.loadClass(data.info.name).newInstance();
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }

        try {

            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            service.onCreate();
            ....
        }
    }

可见也是反射无参构造创建对象,然后调用attach:

    public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
        attachBaseContext(context);
        ....
    }

在attach中第一时间调用attachBaseContext。其中context来自于handleCreateService方法中调用的ContextImpl.createAppContext:

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                null);
        context.setResources(packageInfo.getResources());
        return context;
    }

也是new一个ContextImpl,可见Service的Context也是ContextImpl的实例化对象。

从上面分析我们还可以看到一个问题,每个Activity或Service的创建都会重新实例化一个Context。所以我们经常说这两个组件里的Contex不是全局的,如果引用不当会造成内存泄漏。而Application是每个应用只有一个,所以是全局的。

3.Application

既然看到这了我们再来看一下Application的Context来源,先看继承关系



发现也是一个Context,再看创建对象的地方:

android\frameworks\base\core\java\android\app\LoadedApk.java

在makeApplication方法中:

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

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

        Application app = null;

        
        try {
           ....
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } 
        ....
        mApplication = app;
        ....
        return app;
    }

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

    final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                null);
        context.setResources(packageInfo.getResources());
        return context;
    }

发现没有?一样的逻辑,都是先反射创建对象,而且都是无参数的,然后调用attach方法,attach里面调用attachBaseContext来设置Context。其中Context都是ContextImpl类型的。我们常用的getApplicationContext实现如下:

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

实际上就是获取应用的Application,上面方法中无论走哪个分支得到的Application都是一样的,具体见源码。

4.BroadcastReceiver

最后我们看看广播接受者的Context来历。首先在BroadcastReceiver是一个抽象类,什么也没继承,Context直接来源是onReceive中的Context参数。我们主要探讨这个参数来历。如果你比较清楚广播的源码,这个问题是很好解决的。如果不清楚的话,可以先去了解一下,或者直接往下看,这里虽然不会详细讲解广播的流程,但重要的地方还是会提到的

首先看广播注册,首先调用registerReceiver方法,自然是ContextWrapper 中的,不过重重调用到registerReceiverInternal方法:

    private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context, int flags) {
        IIntentReceiver rd = null;
        if (receiver != null) {
           if (mPackageInfo != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    resultReceiver, getOuterContext(), scheduler,
                    mMainThread.getInstrumentation(), false);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
            }
        }
        ....
    }

这里面创建了一个重要对象,穿进去的第二个参数就是Context,不过调用的时getOuterContext,如果你认真看上文Application创建时我截取的代码时,会注意到Application创建后,调用了appContext.setOuterContext(app);getOuterContext只是取出而已。

这里创建完成后,会被存储起来,在收到广播时拿出来调用。这个我们要探讨的Context有什么关系呢?看一下收到广播回调onReceive的地方,在LoadedApk中:

       final class Args extends BroadcastReceiver.PendingResult {
            ....
                    try {
                        ClassLoader cl = mReceiver.getClass().getClassLoader();
                        intent.setExtrasClassLoader(cl);
                        intent.prepareToEnterProcess();
                        setExtrasClassLoader(cl);
                        receiver.setPendingResult(this);
                        receiver.onReceive(mContext, intent);
                    }
            ....
        }

可见这里回调并传入了mContext,这个mContext哪里来呢?Args 是ReceiverDispatcher的内部类,看他的构造

        ReceiverDispatcher(BroadcastReceiver receiver, Context context,
                Handler activityThread, Instrumentation instrumentation,
                boolean registered) {
            if (activityThread == null) {
                throw new NullPointerException("Handler must not be null");
            }

            mIIntentReceiver = new InnerReceiver(this, !registered);
            mReceiver = receiver;
            mContext = context;
            mActivityThread = activityThread;
            mInstrumentation = instrumentation;
            mRegistered = registered;
            mLocation = new IntentReceiverLeaked(null);
            mLocation.fillInStackTrace();
        }

就是在创建时传进来的context。而创建时传的是ContextWrapper 的getOuterContext方法,也就是Application。所以广播接受者中的Context就是Application,也是全局的,即节省了创建时的性能耗费页避免了内存泄漏。

5.ContentProvider

四大组件已经说了3个了,索性就把最后一个也看了吧。ContentProvider比较特殊,我们先看他的继承关系:



它谁也没继承,所以他本身并不是一个Context,所以在这里面我们如果想要调用Context的方法,需要先调用getContext()方法,这个方法实现如下:

    public final @Nullable Context getContext() {
        return mContext;
    }

返回了一个类成员,看这个类成员初始化的地方,只有两个地方,构造方法和attachInfo方法。至于具体走的那个,要从源码看起。实例化的地方在ActivityThread的installProvider方法:

   private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        ....
        Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }

            if (info.splitName != null) {
                try {
                    c = c.createContextForSplit(info.splitName);
                } catch (NameNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                ...
                localProvider.attachInfo(c, info);
            } 
        ...
        return retHolder;
    }

可见也是先根据无参构造创建对象,紧接着就调用了attachInfo方法赋予context。向上看这个c变量的来历。首先在installProvider有两个
Context,第一个是调用者传进来的,第二个是要传给ContentProvider。可见先进行了判断,若调用者和被调用者是一个应用,则直接使用现成的,否则根据被调用者包名调用createPackageContext创建一个。大家可以去看createPackageContext实现,虽然是创建一个Context,但是和ContentProvider所在的那个应用中Context是一样的。

最后我们总结一下,一个应用中只有每个Activity和Service及那个唯一的Application中的Context是独立的,其余的Context都是源于其他地方。并且所有的Context的实现都是ContextImpl !~

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

推荐阅读更多精彩内容