你足够了解Context吗?
这里有关于Context的一切
-
写在前面:
当我还是一个24K纯Android新手的时候(现在是也是个小Android萌新),拿着工具书对着电脑敲敲打打,那个时候我就有一个非常大的疑问:Context到底为何这么牛?show一个Dialog,启动一个Activity,Service,发送一个Broadcast,还有各种方法需要传入的参数。几乎在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.
- 一个应用环境的全局信息,字面意思是上下文的意思;
- Context是一个抽象类;
- 允许我们通过Context获取各种资源,服务,或者去启动一个Activity,发送一个广播,等等;
怎么去理解Context呢?其实Context就是给Android应用程序提供了一个可以实现各种操作的土壤环境,Context为Android提供了各种资源、功能、服务。如果说编写一个Android程序像搭建一座房子,那Context就为Android提供了土地,木材,和染料(启动一个Activity,弹出一个Dialog),并且能提供呼叫各种将房屋建得更完善的其他帮助(发送一个广播,启动一个服务等)。
一:继承关系
通过继承关系可以看到,Context直接子类为ContextIml(实现类)和ContextWrapper(包装类)
再看看ContextWrapper的子类有什么,看到熟悉的Service和Application了吧,不过看到这里你一定有个疑问,为什么Activity和他们哥俩不在一个继承层级呢?而是Activity又继承了ContextThemeWrapper,那么ContextWrapper和ContextThemeWrapper的区别在哪里呢?
看到这两个类的名字,相信你心里已经有了答案,对,区别在Theme。
该类内部包含了主题(Theme)相关的接口,即android:theme属性指定的。
只有Activity需要主题,Service不需要主题,
所以Service直接继承于ContextWrapper类。而Activity因为含有Theme属性的缘故,所以继承自ContextThemeWrapper。
所以说,Context所调用的资源是不同的了?保留这个疑问,继续向下看。
二:源码阅读
看到了继承结构,我们分别来看看Context及其子类的一些源码
- Context
|
|
Context的源码算上注释有3000行之多,这里贴出一些重要代码,可以看到,Context几乎包含了所有你能想到的,一个Android程序需要的资源和操作,Context自己就像一个App一样,启动Activity、Service,发送Broadcast,拿到assets下的资源,获取SharedPreferences等等。
但Context只是一个顶层接口啊,又是谁帮他实现了操作呢?是ContextWrapper吗?
- ContextWrapper
|
|
好吧,ContextWrapper好像很懒的样子,它把所有操作都丢给了mBase,mBase又是谁呢?在构造方法和attachBaseContext方法中,指向了一个Context实例,ContextIml,我们赶紧来看看ContextIml的源码!
- ContextImpl
|
|
其实ContextImpl才是在Context的所有继承结构中唯一一个真正实现了Context中方法的类。其它Context的子类,Application,Activity,Service,都是与ContextImpl相关联,去获取资源和服务,并没有真正自己去实现,这里就不贴上ContextThemeWrapper的源码了,它是为Activity添加了一些Theme的属性,不再赘述。
思路越来越清晰,我们现在就是要去寻找,Activity,Service,Application是何时与ContextImpl完成绑定关联的。
三:关联时机
我们都知道ActivityThread的main方法,是整个Android程序的入口,所以去探究ActivityThread类,也是一件非常重要的事。
推荐一篇文章,去了解下ActivityThread吧
贴出ActivityThread的main方法部分重要的代码
|
|
也许你不能完全看懂、理解这些代码,不过没关系,直接告诉你结论吧,ActivityThread的一个内部类H,里面定义了activity、service等启动、销毁等事件的响应,也就是说activity、service的启动、销毁都是在ActivityThread中进行的。
当然了,一个Activity或者Service的从创建到启动是相当复杂的,其中还涉及的Binder机制等等原理,推荐给大家两篇博文,去慢慢研读消化吧。
准备工作不知不觉就做了这么多,差点忘了正事,我们还是要继续寻找Application、Activity、Service是何时与ContextImpl进行关联的。
- Application
|
|
每个应用程序在第一次启动时,都会首先创建一个Application对象。从startActivity流程可知,创建Application的时机在handleBindApplication()方法中,该函数位于 ActivityThread.java类
- Activity
|
|
|
|
通过startActivity()或startActivityForResult()请求启动一个Activity时,如果系统检测需要新建一个Activity对象时,就会回调handleLaunchActivity()方法,该方法继而调用performLaunchActivity()方法,去创建一个Activity实例,并且回调onCreate(),onStart()方法等,函数位于 ActivityThread.java类。
- Service
|
|
通过startService或者bindService时,如果系统检测到需要新创建一个Service实例,就会回调handleCreateService()方法,完成相关数据操作。handleCreateService()函数位于 ActivityThread.java类
看到这里,相信你对Context的理解更进一步了,现在我们知道了Context是什么,它为Android提供了怎样的资源、功能、和服务,又在什么时候将Application、Activity、Service与ContextImpl相关联,但是所请求的资源是不是同一套资源呢?
在这里你一定说:“当然不是,不同的Context对象明显是有区别的,用法也不同”
但是其实他们访问的,确确实实,是同一套资源。
三:Context资源详解
来吧,看看不同Context对象的区别和用法的不同,参见以下表格。
这张表格是不是又支持了你的观点(也就是一直认为的,Context资源对象是不同的),但是还是要再次强调一次,它们所请求的,确确实实是同一块资源,看看上面进行关联的源码,都走进了Context实现类的init方法,拨云见日,我们去看看init方法吧。
查看ContextImpl类源码可以看到,getResources方法直接返回内部的mResources变量
|
|
mResources又是调用LoadedApk的getResources方法进行赋值。代码如下。
|
|
从代码中可以看到,最终mResources的赋值是由AcitivtyThread的getTopLevelResources方法返回。代码如下。
|
|
以上代码中,mActiveResources对象内部保存了该应用程序所使用到的所有Resources对象,其类型为WeakReference,所以当内存紧张时,可以释放Resources占用的资源,自然这不是我们探究的重点,ResourcesKey的构造需要resDir和compInfo.applicationScale。resdDir变量的含义是资源文件所在路径,实际指的是APK程序所在路径,比如可以是:/data/app/com.haii.android.xxx-1.apk,该apk会对应/data/dalvik-cache目录下的:data@app@com.haii.android.xxx-1.apk@classes.dex文件。
所以结论来了:
如果一个应用程序没有访问该程序以外的资源,那么mActiveResources变量中就仅有一个Resources对象。
总结:
当ActivityThread类中创建Application、Service、Activity的同时,完成了与ContextImpl的关联绑定,通过ContextImpl类中init方法,获得了一个唯一的Resources对象,根据上述代码中资源的请求机制,再加上ResourcesManager采用单例模式,这样就保证了不同的ContextImpl访问的是同一套资源。
如果这篇博客现在就结束了,你一定会杀了我 - -,现在我们就来分析下,是什么造成了唯一的这个Resources,却展现出了“不同”。
举个通俗易懂的例子,我和我老妈都拿到同一块土豆,但是因为我们处理这个土豆的方法有区别,导致这个土豆最后表现出来的也不一样,我想把它做成薯片,我妈妈把它炒成了土豆丝,:-D。
再具体一点,比如除了Activity可以创建一个Dialog,其它Context都不可以创建Dialog。比如在Application中创建Dialog会报错,还有Application和Service可以启动一个Activity,但是需要创建一个新的task。比如你在Application中调用startActivity(intent)时系统也会崩溃报错。
报错的原因并不是因为他们拿到的Context资源不同,拿到的都是一个Resoucres对象,但是在创建Dialog的时候会使用到Context对象去获取当前主题信息,但是我们知道Application和Service是继承自ContextWrapper,没有实现关于主题的功能,然而Activity是继承自ContextThemeWrapper,该类是实现了关于主题功能的,因此创建Dialog的时候必须依附于Activity的Context引用。
结论:
Application、Service、Activity,它们本身对Resources资源处理方法的不同,造成了这个Resoucres最后表现出来的不一样,这么说大家就都懂了吧!
四:Context内存泄漏
关于Context的内存泄漏,找到一篇比较不错的文章分享给大家。
写在最后:
Context可能还有更多深层次的知识需要我们去了解,比如Context这些封装类,是具体如何通过Binder跟ContextImpl进行关联的;资源对象都被存储在ArrayMap,为什么ArrayMap中会有可能存在多个资源对象,如何访问其他应用程序的Context资源等等,剩下的这些就靠大家慢慢发掘了~
转载请注明出处。
如果大家觉得喜欢有价值,就关注我,点下赞哈,你们的支持是我持续原创的动力。