美文网首页
Android Context

Android Context

作者: tandeneck | 来源:发表于2017-12-10 18:40 被阅读0次

    关于 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

    相关文章

      网友评论

          本文标题:Android Context

          本文链接:https://www.haomeiwen.com/subject/rirvixtx.html