Android Context

关于 Context ,打从接触 Android 开始,就看到了相关文章,自己的笔记里也做了相关的记录,网上关于解析 Context 的优秀的博客已经很多了,这里纯粹当作记录啦。

Context,意为上下文。弄懂 Context 对于 Android 开发者还是比较重要的。首先,我们看下 Context 的主要继承结构。这里明确一点,是主要。

Context继承结构.jpg

Context 主要应用了装饰模式。ContextWrapper 主要是 Context 的封装类,ContextImpl 则是 Context 的实现类。我们熟悉的 Activity、Service 和 Application 都是 Context 的子类。那么我们就很有必要看下 Context 类了。

/* *
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 */
public abstract class Context {

可以看出 Context 是一个抽象类。我们通过它可以访问当前包的资源(getResources、getAssets)和启动其他组件(Activity、Service、Broadcast)。

接下来我们来看 ContextImpl 类,这里只截取其中关于 startActivity 的实现。总是 ContentImpl 是 Context 的实现类。

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
      @Override
      public void startActivity(Intent intent, Bundle options) {
          warnIfCallingFromSystemProcess();
          if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
              throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
       }
      mMainThread.getInstrumentation().execStartActivity(
        getOuterContext(), mMainThread.getApplicationThread(), null,
        (Activity)null, intent, -1, options);
      }
}

我们来看 ContextWrapper 类,里面维护了一个 Context 的引用,是Context 的封装类。

/**
 * Proxying implementation of Context that simply delegates all of its calls to
 * another Context.  Can be subclassed to modify behavior without changing
 * the original Context.
 */
public class ContextWrapper extends Context {
    Context 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;
    }

    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() {
        return mBase;
    }

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

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

Activity、Service 和 Application 都是Context 的具体装饰类。那为什么 Activity 不直接继承 ContextWrapper 类,而是继承于 ContextThemeWrapper 这个好像多余的类呢?其实不然,我们来看下 ContentThemeWrapper 的源码注释:

/**
 * A ContextWrapper that allows you to modify the theme from what is in the 
 * wrapped context. 
*/
public class ContextThemeWrapper extends ContextWrapper {
    ......
}

原来这个类的目的是让我们可以去修改或者说替换 Cotnext 的主题Theme。即android:theme 属性指定的。

Context 数量

关于一个应用中到底有多少个Context其实是显而易见的了。Context 一共有 Application、Activity 和 Service 三种类型,因此一个应用中 Context 数量为:

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

Context 实例化过程的源码分析

Activity 对象中 Context 的实例化。

通过 startActivity 启动一个 Activity 进过一系列的调用方法之后会来到 ActivityThread(即主线程)的 performLaunchActivity()方法来创建一个 Activity 实例,然后回调 Activity 的 onCreate()等方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
        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;
}

可以看到,通过 createBaseContextForActivity(r,activity)
创建一个 Context 对象并在 attach 方法关联它。

我们再来看下 createBaseContextForActivity 方法。

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

总结:Activity 内部持有一个 ContextImpl 引用(ContextWrapper 的成员 mBase),进而 执行 Context(ContextImpl 继承于 Context) 中的方法。

Service 对象中 Context 的实例化。

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

Application对象中 Context 的实例化。

其实,Application 对象中 Context 对象的实例化 和 Activity 、Service 中的基本一致。ContentImpl 的创建在 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;
}

应用程序 App 各种 Context 访问资源的唯一性

可以发现,Application、Activity 和 Service 都有自己 Context 的实例,那么平常我们通过 context.getResources() 得到的资源是不是同一份呢?

class ContextImpl extends Context {
    ......
    private final ResourcesManager mResourcesManager;
    private final Resources mResources;
    ......
    @Override
    public Resources getResources() {
        return mResources;
    }
    ......
}

context.getResources 方法获得的 Resources 对象就是上面 ContextImpl 的成员变量 mResources。mResource 的赋值操作如下:

private ContextImpl(ContextImpl container, ActivityThread mainThread,
        LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
        Display display, Configuration overrideConfiguration) {
    ......
    //单例模式获取ResourcesManager对象
    mResourcesManager = ResourcesManager.getInstance();
    ......
    //packageInfo对于一个APP来说只有一个,所以resources 是同一份
    Resources resources = packageInfo.getResources(mainThread);
    if (resources != null) {
        if (activityToken != null
                || displayId != Display.DEFAULT_DISPLAY
                || overrideConfiguration != null
                || (compatInfo != null && compatInfo.applicationScale
                        != resources.getCompatibilityInfo().applicationScale)) {
            //mResourcesManager是单例,所以resources是同一份
            resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                    packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                    packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                    overrideConfiguration, compatInfo, activityToken);
        }
    }
    //把resources赋值给mResources
    mResources = resources;
    ......
}

由此可以看出在设备等其他因素不变的情况下我们通过 Context 实例得到的 Resources 是同一套资源。这里要注意的是只有在设备等其他因素不变的情况下,这句话才是正确的。因为 res 下可能存在多个适配不同设备、不同分辨率、不同系统版本的目录,不同设备在访问同一个应用的时候可以不同,有可能是 hdpi 的 或者 xxhdpi 的。而且如果为横竖屏状态下提供了不同的资源,-land、-port,处在横屏状态下的 ContextImpl 和处在竖屏状态下的 ContextImpl 访问的资源也不是同一个资源对象。

getApplication 和 getApplicationContext 的区别

首先我们看 getApplication 方法,这个方法是 Activity 和 Service 中才有的。Application 和 Context 都没有此方法。

public class Activity extends ContextThemeWrapper
    implements LayoutInflater.Factory2,
    Window.Callback, KeyEvent.Callback,
    OnCreateContextMenuListener, ComponentCallbacks2,
    Window.OnWindowDismissedCallback {
    ......
    public final Application getApplication() {
        return mApplication;
    }
    ......
}


public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    ......
    public final Application getApplication() {
        return mApplication;
   }
    ......
}

Activity 和 Service 提供了getApplication方法,而且返回类型都是 Application。这个 Application 是在 ActivityThread 中各自实例化时获取的 make Application 方法返回值。所以不同的 Activity 和 Service 返回的Application均为同一个全局对象。

接着我们来看 getApplicationContext 方法,

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

可以看到,getApplicationContext 方法 是Context 方法,而且返回值是 Context 类型,返回对象和上面通过 Service 或者 Activity 的 getApplication 返回的是一个对象。

因此 getApplication 方法 和 getApplicationContext 方法只是返回值类型不同,一个返回的是 Application ,另一个是 Context。还有就是依附的对象不同而已。

参考资料

[1] 工匠若水.Android应用Context详解及源码解析
[2] singwhatiwannaAndroid源码分析-全面理解Context

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

推荐阅读更多精彩内容