什么是Context?

想必大家都不陌生,在 Android 开发中离不开 Context 调用各种跟系统有关的 API 都必须用到 Context 。我们可以将她理解为上下文环境,大概就是里面存储一堆全局变量,这些变量在调用系统 API 时需要用到。文字始终难以表达我想说的,咱们来分析原理吧!

Context 哪里来的?

开发 Android 应用必须得有一个 Activity ,然后在你的 Activity 里面调用各种系统 API 比如,

String string = getString(R.string.app_name);

这行代码看似简单,但是如果不让你在 Activity 中调用你该怎么获取这个 string ?

Context context;
context.getString(R.string.app_name);

你需要一个 Context 对象,那在 Activity 中为何不需要呢?
答案是,Activity 本来就是一个 Context 他继承自 Context,你打开 Activity 可以知道他的继承顺序如下:
Activity -> ContextThemeWrapper -> ContextWrapper -> Context

Context 是一个抽象类,所有方法都没实现,ContextWrapper 实现了所有方法,但是他只是一个代理,打开源码你可以看到他是这样的:

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

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

它仅仅调用 mBase 的方法,(这种在设计模式里叫 代理模式 Proxy ),看来我们得弄清楚这个 mBase 哪来的了!
查看源码你会发现只有2个地方设置了 mBase,

public ContextWrapper(Context base) {
        mBase = base;
    }

    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

一是构造函数,一是 attachBaseContext 。
我们先来分析构造函数!Activity 继承自它,所以分析 Activity是 怎么创建的, Activity 的创建过程比较复杂,下面的源码可以看出,Activity 创建的时候调用的是无参数构造函数。

ActivityThread中的performLaunchActivity(ActivityClientRecord r, Intent customIntent)方法片段

Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.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);
            }
        }

源码中的activity实例化

activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

上面就验证了Activity的实例化是通过无参的构造函数初始化的。
所以 mBase 不是来自构造函数,那就是来自 attachBaseContext 了,下面我们来分析 Activity 在哪里调用了 attachBaseContext() 方法:
查阅 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) {
        attachBaseContext(context);

就是 Activity.attach 方法,此方法在 framework 创建 Activity 的时候调用的,要知道这个 context 参数是什么,我们得从 framework 代码去看,
代码:android.app.ActivityThread

android context 的各种获取方法 android context详解_android


android context 的各种获取方法 android context详解_ide_02

看到了,context 就是此处的 appContext 他是 ContextImpl 对象,好了,现在我们知道 ContextImpl 才是真正的实现者了。
所以 getString() 的真正调用是 ContextImpl.getResource.getString() ,getString() 的真正实现在 Resource (android.content.res.Resources) 。

有哪几种 Context?

Activity :刚刚我们已经分析
Service :也是继承自 ContextWrapper 跟 Activity 一样
BroadcastReceiver : onReceiver(Context context )
Application:
记住所有 Context 真正实现都是 ContextImpl
Activty、Service 的 Context 都是每次创建的,而不是全局唯一,所以不要将 Activity 、Service 当做全局 Context 引用,这样会导致 Activity 无法销毁,一直被引用者。

应用程序创建Context实例的
情况有如下几种情况:
1、创建Application 对象时, 而且整个App共一个Application对象
2、创建Service对象时
3、创建Activity对象时

因此应用程序App共有的Context数目公式为:

                 总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例)

那么什么样的 Context 才能做全局引用呢?
答案是:Application ,如果你没有自定义的 Application 怎么获取这个 Context 呢,你会发现 Context 有一个 getApplicationContext() 方法,他在 ContextImpl 的实现如下:

android.app.ContextImpl

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

android.app.LoadedApk

Application getApplication() {
        return mApplication;
    }

这个mApplication 对象就是程序的 Application 。所以你可以把他强转成你自定义的 Application

BroadcastReceiver 的 Context

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

            if (localLOGV) Slog.v(
                TAG, "Performing receive of " + data.intent
                + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + packageInfo.getPackageName()
                + ", comp=" + data.intent.getComponent().toShortString()
                + ", dir=" + packageInfo.getAppDir());

            ContextImpl context = (ContextImpl)app.getBaseContext();
            sCurrentBroadcastIntent.set(data.intent);
            receiver.setPendingResult(data);
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);

从代码得知,他是 Application 的 ContextImpl 对象,所以他是全局的。