Android基础之Context
Context
对于Android开发者来说Context应该是非常熟悉的。我们在使用Toast,启动Activity,启动Service,加载资源,操作数据库,获取App相关的文件路径,创建View等操作时,都会涉及到一个Context引用。那么Context到底是什么,Activity和Application作为Context到底有什么区别,哪个更好呢?以及我们在项目建立最初自定义的Application提供全局Context该怎么实现呢?
什么是Context
写过一个小Android demo的应该会有一个疑问为什么一个Android应用不需要一个main()方法来启动整个程序,如果是通过一个main()方法来启动程序,那么我们就可以在里面执行例如new一个Activity等操作。Android的应用模型是基于组件的应用设计模式,组件能够运行需要一个完整的工程环境。同时,各个组件拥有自己独立的场景(Context),并不能采用new的方式创建一个组件对象。Context是维持Android程序中各组件能够正常工作的一个核心功能类。
总结:一个应用是一个完整的工程环境,在这个应用中我们有许多功能,例如打电话,发短信,发邮件。这些功能都算是这个环境中的一个场景。比如发短信场景包含界面的显示以及数据的传递等等。所以我们可以把场景(Context)理解为用户与操作系统交互的一个过程。
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 abstractclassContext{......}
他的注释:Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。也就是说Context描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。
引用自博客 Android应用Context详解及源码解析
那么在平常开发中我们用到哪些这个Context抽象类的实现呢?
盗用大牛郭霖的图片.png
可以发现Application,Service,Activity都是者间接继承自Context。在上面的继承关系中我们看到ContextImpl和ContextWrapper是直接继承自Context。ContextWrapper是Context类的一个封装类,而ContextImpl是Context抽象类的实现类。ContextWrapper的构造函数中有一个真正的Context的引用(ContextImpl对象,mBase)。ContextWrapper的子类有一个ContextThemeWrapper,是带主题封装的类,即通过android:theme属性指定的(例如Activity带UI显示)。
那么我们一个应用中有多少个Context呢?
Android应用程序只有四大组件,而其中两大组件都继承自
Context,另外每个应用程序还有一个全局的Application对象。所以在我们了解了上面继承关系之后我们就可以计算出来Context总数,如下:
appContextCount = Application + ActivityCount + ServiceCount;
Context的实例化
Activity,Application,Service实例都是在ActivityThread中创建的。我参考了博客Android应用Context详解及源码解析(因为自己还没有深入了解到App的启动,只是想了解Context基础知识)
Activity中ContextImpl实例化源码分析
通过startActivity启动一个新的Activity时系统会回调ActivityThread的handleLaunchActivity()方法,该方法内部会调用performLaunchActivity()方法去创建一个Activity实例,然后回调Activity的onCreate()等方法。所以Activity的ContextImpl实例化是在ActivityThread类的performLaunchActivity方法中,如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){......//已经创建好新的activity实例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);创建appContext,然后通过activity.attach设置值。
createBaseContextForActivity(r, activity)部分源代码:
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.attach()部分源代码:
finalvoidattach(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){//特别特别留意这里!!!//与上面createBaseContextForActivity方法中setOuterContext语句类似,不同的在于://通过ContextThemeWrapper类的attachBaseContext方法,将createBaseContextForActivity中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImplattachBaseContext(context);......}
总结:Activity通过ContextWrapper的成员mBase来引用了一个ContextImpl对象,这样,Activity组件以后就可以调用这个ContextImpl对象的一系列方法(启动Service等);同时ContextImpl类又通过自己的成员mOuterContext引用了与它关联的Activity,这样ContextImpl类也可以操作Activity。
由此说明一个Activity就有一个Context,而且生命周期和Activity类相同。在使用Activity作为Context引用时要考虑到内存泄漏。
Service中ContextImpl实例化源码分析
通过startService或者bindService方法创建一个新Service时就会回调ActivityThread类的handleCreateService()方法完成相关数据操作。具体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){......}}
Service中的attach方法:
public finalvoidattach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager){//特别特别留意这里!!!//与上面handleCreateService方法中setOuterContext语句类似,不同的在于://通过ContextWrapper类的attachBaseContext方法,将handleCreateService中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImplattachBaseContext(context);......}
说明一个Service就有一个Context,而且生命周期和Service类相同,在使用Service作为Context引用时要考虑到内存泄漏
Application中ContextImpl实例化源码分析
一个APP以后每次重新启动时都会首先创建Application对象(每个APP都有一个唯一的全局Application对象,与整个APP的生命周期相同)。创建Application的过程也在ActivityThread类的handleBindApplication()方法完成相关数据操作。而ContextImpl的创建是在该方法中调运LoadedApk类的makeApplication方法中实现,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;}
Instrumentation.newApplication方法部分源码:
publicApplicationnewApplication(ClassLoader cl,String className,Context context)throwsInstantiationException,IllegalAccessException,ClassNotFoundException{returnnewApplication(cl.loadClass(className), context);}
newApplication方法部分源码:
staticpublicApplicationnewApplication(Class<?> clazz,Context context)throwsInstantiationException,IllegalAccessException,ClassNotFoundException{......//继续传递context
app.attach(context);return app;}
Application类的attach方法部分源码:
finalvoidattach(Context context){//特别特别留意这里!!!//与上面makeApplication方法中setOuterContext语句类似,不同的在于://通过ContextWrapper类的attachBaseContext方法,将makeApplication中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Application的实现类ContextImplattachBaseContext(context);......}
说明一个Application就有一个Context,而且生命周期和Application类相同(然而一个App只有一个Application,而且与应用生命周期相同)。
通过Context引用获取资源
看到这里我有个疑问不同的Context引用,获取到的资源是同一份吗?
classContextImplextendsContext{......privatefinalResourcesManager mResourcesManager;privatefinalResources mResources;......@OverridepublicResourcesgetResources(){return mResources;}......}
有了上面分析我们可以很确定平时写的App中context.getResources方法获得的Resources对象就是上面ContextImpl的成员变量mResources。那我们追踪可以发现mResources的赋值操作如下:
privateContextImpl(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是同一套资源。
以上源码分析完全是引用自别人的博客,自己对上面的分析也只是停留在一个字面意思上,只是通过上面的分析理解了不同Context的实例相对于整个App的生命周期,可以在实际开发中作为判断到底使用哪个Context引用的依据。以及解决了心中对于不同Context引用获取资源的疑问
getApplication和getApplicationContext的区别
getApplication()方法
Activity和Service提供了getApplication,而且返回类型都是Application。
publicfinalApplicationgetApplication(){return mApplication;}
这个mApplication都是在各自类的attach方法参数出入的,也就是说这个mApplication都是在ActivityThread中各自实例化时获取的makeApplication方法返回值。而且不同的Activity和Service返回的Application均为同一个全局对象。
getApplicationContext()方法
通过ContextImpl对象调用getApplicationContext()方法
classContextImplextendsContext{......@OverridepublicContextgetApplicationContext(){return(mPackageInfo !=null)?
mPackageInfo.getApplication(): mMainThread.getApplication();}......}
可以看到getApplicationContext方法是Context的方法,而且返回值是Context类型,返回对象和上面通过Service或者Activity的getApplication返回的是一个对象。
区别
虽然这两个方法最终的得到的值都是同一个对象的引用,但是它们的类型不同(一个是Application,一个是Context)且作用域有很大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法。
还有一个得到Context的方法,getBaseContext()。getBaseContext()方法得到的是一个ContextImpl对象。也就是调用ContextWrapper的getBaseContext()方法返回存储ContextImpl对象的mBase。
Activity和Application作为Context引用
Context引起的内存泄漏
在实际开发中我们需要用到工具类,一般都会采用单例模式:
publicclassSingleton{privatestaticSingleton instance;privateContext mContext;privateSingleton(Context context){this.mContext = context;
}
publicstaticsynchronizedSingletongetInstance(Context context){if(instance ==null){
instance =newSingleton(context);}return instance;}}
你在某个Activity调用了getInstance()方法,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。
Java强引用会导致GC内存无法回收
解决办法:
publicclassSingleton{privatestaticSingleton instance;privateContext mContext;privateSingleton(Context context){this.mContext = context;
}
publicstaticsynchronizedSingletongetInstance(Context context){if(instance ==null){
instance =newSingleton(context.getApplicationContext());}return instance;}}
由于Application对象的生命周期如上面分析的那样,和整个项目一样长。也就是它的生命周期和我们的单例对象一致。
不同的Context适用场景
正如上面所说的Context引用不当会造成内存泄漏,那么我们是否可以都引用Application作为Context引用呢?答案是否定的,不同Context(指的是Activity,Application,Service)的应用场景是不同的,并非所有Activity为Context的场景,Application Context都能搞定。
Application | Activity | Service | |
show dialog | N | Y | N |
start Activity | N | Y | N |
Layout Inflation | N | Y | N |
start service | Y | Y | Y |
send a BroadCast | Y | Y | Y |
register a BroadCast Receiver | Y | Y | Y |
load Resource | Y | Y | Y |
上述表格中可以看到,在启动一个Activity和Dialog时,不推荐使用Application和Service。Android系统出于安全原因的考虑,是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。调用除了Activity的,其他Context实例的LayoutInflation,会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
总结:和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
参考
Android Context完全解析,你所不知道的Context的各种细节
目标
Activity启动
项目初始化,自定义Application细节
单例模式,参考博客如何正确地写出单例模式
java引用类型:强,弱,虚,软。参考博客Java 7之基础 - 强引用、弱引用、软引用、虚引用
本文仅用于笔记的收藏;
原作者:狮_子歌歌

2322

被折叠的 条评论
为什么被折叠?



