Android context源码详解及深入分析
android context详解
前言:
context都没弄明白,还怎么做android开发?
activity mactivity =new activity()
作为android开发者,不知道你有没有思考过这个问题,activity可以new吗?android的应用程序开发采用java语言,activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。android程序不像java程序一样,随便创建一个类,写个main()方法就能运行,android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的android工程环境,在这个环境下,activity、service等系统组件才能够正常工作,而这些组件并不能采用普通的java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是我们这里讨论的context。可以这样讲,context是维持android程序中各组件能够正常工作的一个核心功能类。
context到底是什么
context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,context在加载资源、启动activity、获取系统服务、创建view等操作都要参与。
那context到底是什么呢?一个activity就是一个context,一个service也是一个context。android程序员把“场景”抽象为context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。
如何生动形象的理解context
上面的概念中采用了通俗的理解方式,将context理解为“上下文”或者“场景”,如果你仍然觉得很抽象,不好理解。在这里我给出一个可能不是很恰当的比喻,希望有助于大家的理解:一个android应用程序,可以理解为一部电影或者一部电视剧,activity,service,broadcast receiver,content provider这四大组件就好比是这部戏里的四个主角:胡歌,霍建华,诗诗,baby。他们是由剧组(系统)一开始就定好了的,整部戏就是由这四位主演领衔担纲的,所以这四位主角并不是大街上随随便便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,他们必须通过镜头(context)才能将戏传递给观众,这也就正对应说四大组件(四位主角)必须工作在context环境下(摄像机镜头)。那button,textview,linearlayout这些控件呢,就好比是这部戏里的配角或者说群众演员,他们显然没有这么重用,随便一个路人甲路人乙都能演(可以new一个对象),但是他们也必须要面对镜头(工作在context环境下),所以button mbutton=new button(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 { /** * file creation mode: the default mode, where the created file can only * be accessed by the calling application (or all applications sharing the * same user id). * @see #mode_world_readable * @see #mode_world_writeable */ public static final int mode_private = 0x0000; public static final int mode_world_writeable = 0x0002; public static final int mode_append = 0x8000; public static final int mode_multi_process = 0x0004; }
源码中的注释是这么来解释context的:context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动activity,发广播,接受intent等)。既然上面context是一个抽象类,那么肯定有他的实现类咯,我们在context的源码中通过ide可以查看到他的子类
context类本身是一个纯abstract类,它有两个具体的实现子类:contextimpl和contextwrapper。其中contextwrapper类,如其名所言,这只是一个包装而已,contextwrapper构造函数中必须包含一个真正的context引用,同时contextwrapper中提供了attachbasecontext()用于给contextwrapper对象中指定真正的context对象,调用contextwrapper的方法都会被转向其所包含的真正的context对象。contextthemewrapper类,如其名所言,其内部包含了与主题(theme)相关的接口,这里所说的主题就是指在androidmanifest.xml中通过android:theme为application元素或者activity元素指定的主题。当然,只有activity才需要主题,service是不需要主题的,因为service是没有界面的后台场景,所以service直接继承于contextwrapper,application同理。而contextimpl类则真正实现了context中的所以函数,应用程序中所调用的各种context类的方法,其实现均来自于该类。一句话总结:context的两个子类分工明确,其中contextimpl是context的具体实现类,contextwrapper是context的包装类。activity,application,service虽都继承自contextwrapper(activity继承自contextwrapper的子类contextthemewrapper),但它们初始化的过程中都会创建contextimpl对象,由contextimpl实现context中的方法。
一个应用程序有几个context
其实这个问题本身并没有什么意义,关键还是在于对context的理解,从上面的关系图我们已经可以得出答案了,在应用程序中context的具体实现子类就是:activity,service,application。那么context数量=activity数量+service数量+1。当然如果你足够细心,可能会有疑问:我们常说四大组件,这里怎么只有activity,service持有context,那broadcast receiver,content provider呢?broadcast receiver,content provider并不是context的子类,他们所持有的context都是其他地方传过去的,所以并不计入context总数。上面的关系图也从另外一个侧面告诉我们context类在整个android系统中的地位是多么的崇高,因为很显然activity,service,application都是其子类,其地位和作用不言而喻。
context能干什么
context到底可以实现哪些功能呢?这个就实在是太多了,弹出toast、启动activity、启动service、发送广播、操作数据库等等都需要用到context。
textview tv = new textview(getcontext()); listadapter adapter = new simplecursoradapter(getapplicationcontext(), ...); audiomanager am = (audiomanager) getcontext().getsystemservice(context.audio_service);getapplicationcontext().getsharedpreferences(name, mode); getapplicationcontext().getcontentresolver().query(uri, ...); getcontext().getresources().getdisplaymetrics().widthpixels * 5 / 8; getcontext().startactivity(intent); getcontext().startservice(intent); getcontext().sendbroadcast(intent);
context作用域
虽然context神通广大,但并不是随便拿到一个context实例就可以为所欲为,它的使用还是有一些规则限制的。由于context的具体实例是由contextimpl类去实现的,因此在绝大多数场景下,activity、service和application这三种类型的context都是可以通用的。不过有几种场景比较特殊,比如启动activity,还有弹出dialog。出于安全原因的考虑,android是不允许activity或dialog凭空出现的,一个activity的启动必须要建立在另一个activity的基础之上,也就是以此形成的返回栈。而dialog则必须在一个activity上面弹出(除非是system alert类型的dialog),因此在这种场景下,我们只能使用activity类型的context,否则将会出错。
发现activity所持有的context的作用域最广,无所不能。因为activity继承自contextthemewrapper,而application和service继承自contextwrapper,很显然contextthemewrapper在contextwrapper的基础上又做了一些操作使得activity变得更强大,这里我就不再贴源码给大家分析了,有兴趣的童鞋可以自己查查源码。上图中的yes和no我也不再做过多的解释了,这里我说一下上图中application和service所不推荐的两种使用情况。
1:如果我们用applicationcontext去启动一个launchmode为standard的activity的时候会报错android.util.androidruntimeexception: calling startactivity from outside of an activity context requires the flag_activity_new_task flag. is this really what you want?这是因为非activity类型的context并没有所谓的任务栈,所以待启动的activity就找不到栈了。解决这个问题的方法就是为待启动的activity指定flag_activity_new_task标记位,这样启动的时候就为它创建一个新的任务栈,而此时activity是以singletask模式启动的。所有这种用application启动activity的方式不推荐使用,service同application。
2:在application和service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
一句话总结:凡是跟ui相关的,都应该使用activity做为context来处理;其他的一些操作,service,activity,application等实例都可以,当然了,注意context引用的持有,防止内存泄漏。
如何获取context
通常我们想要获取context对象,主要有以下四种方法
1:view.getcontext,返回当前view对象的context对象,通常是当前正在展示的activity对象。
2:activity.getapplicationcontext,获取当前activity所在的(应用)进程的context对象,通常我们使用context对象时,要优先考虑这个全局的进程context。
3:contextwrapper.getbasecontext():用来获取一个contextwrapper进行装饰之前的context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4:activity.this 返回当前的activity实例,如果是ui控件需要使用activity作为context对象,但是默认的toast实际上使用applicationcontext也可以。
getapplication()和getapplicationcontext()
上面说到获取当前application对象用getapplicationcontext,不知道你有没有联想到getapplication(),这两个方法有什么区别?相信这个问题会难倒不少开发者。
程序是不会骗人的,我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,application本身就是一个context,所以这里获取getapplicationcontext()得到的结果就是application本身的实例。那么问题来了,既然这两个方法得到的结果都是相同的,那么android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getapplication()方法的语义性非常强,一看就知道是用来获取application实例的,但是这个方法只有在activity和service中才能调用的到。那么也许在绝大多数情况下我们都是在activity或者service中使用application的,但是如果在一些其它的场景,比如broadcastreceiver中也想获得application的实例,这时就可以借助getapplicationcontext()方法了。
publicclassmyreceiverextendsbroadcastreceiver{ @override publicvoidonreceive(contextcontext,intentintent){ applicationmyapp=(application)context.getapplicationcontext(); } }
context引起的内存泄露
但context并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。
错误的单例模式
public class singleton { private static singleton instance; private context mcontext; private singleton(context context) { this.mcontext = context; } public static singleton getinstance(context context) { if (instance == null) { instance = new singleton(context); } return instance; } }
这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含activity,假如activity a去getinstance获得instance对象,传入this,常驻内存的singleton保存了你传入的activity a对象,并一直持有,即使activity被销毁掉,但因为它的引用还存在于一个singleton中,就不可能被gc掉,这样就导致了内存泄漏。
view持有activity引用
public class mainactivity extends activity { private static drawable mdrawable; @override protected void oncreate(bundle saveinstancestate) { super.oncreate(saveinstancestate); setcontentview(r.layout.activity_main); imageview iv = new imageview(this); mdrawable = getresources().getdrawable(r.drawable.ic_launcher); iv.setimagedrawable(mdrawable); } }
有一个静态的drawable对象当imageview设置这个drawable时,imageview保存了mdrawable的引用,而imageview传入的this是mainactivity的mcontext,因为被static修饰的mdrawable是常驻内存的,mainactivity是它的间接引用,mainactivity被销毁时,也不能被gc掉,所以造成内存泄漏。
正确使用context
一般context造成的内存泄漏,几乎都是当context销毁的时候,却因为被引用导致销毁失败,而application的context对象可以理解为随着进程存在的,所以我们总结出使用context的正确姿势:
1:当application的context能搞定的情况下,并且生命周期长的对象,优先使用application的context。
2:不要让生命周期长于activity的对象持有到activity的引用。
3:尽量不要在activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
总结
总之context在android系统中的地位很重要,它几乎无所不能,但它也不是你想用就能随便用的,谨防使用不当引起的内存问题。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!