当前位置: 移动技术网 > IT编程>移动开发>Android > Android context源码详解及深入分析

Android context源码详解及深入分析

2019年07月24日  | 移动技术网IT编程  | 我要评论

思春屋,红天地人才网,单色凌资料

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系统中的地位很重要,它几乎无所不能,但它也不是你想用就能随便用的,谨防使用不当引起的内存问题。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网