欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android context源码详解及深入分析

程序员文章站 2024-02-14 16:14:04
android context详解 前言: context都没弄明白,还怎么做android开发? activity mactivity =new activity(...

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

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