Android(安卓)Context
关于 Context ,打从接触 Android 开始,就看到了相关文章,自己的笔记里也做了相关的记录。觉得网上关于解析 Context 的优秀的博客已经很多了。自己再写好像也没有必要了。后来想开了,就当做对自己笔记的整理和再学习。其实主要是整合,囧。况且,看到的人也屈指可数。
Context,意为上下文。弄懂 Context 对于 Android 开发者还是比较重要的。首先,我们看下 Context 的主要继承结构。这里明确一点,是主要。
Context继承结构.jpgContext 主要应用了装饰模式。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 类,Content 维护了一个 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 开发零起步学习笔记(十一):界面切换+几种常用界面切换效果
- Android带进度条的文件上传,使用AsyncTask异步任务
- Android——组件之Service
- android 学习方法
- Android(安卓)Native Crash崩溃及错误原因分析二-实战解决
- Android开发中TextView文本过长滚动显示实现方法分析
- 浅谈:Android(安卓)TextView的append方法与滚动条同时使用
- Android(安卓)>> 14. LiveData
- 【Android应用实例之二】跟随手指的小球——自定义View应用