咱们知道,Android应用都是使用Java语言来编写的,那么你们能够思考一下,一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析,Android程序不像Java程序同样,随便建立一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,咱们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并非像一个普通的Java对象new一下就能建立实例的了,而是要有它们各自的上下文环境,也就是咱们这里讨论的Context。能够这样讲,Context是维持Android程序中各组件可以正常工做的一个核心功能类。html
下面咱们来看一下Context的继承结构:java
Context的继承结构仍是稍微有点复杂的,能够看到,直系子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就能够看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。android
那么在这里咱们至少看到了几个所比较熟悉的面孔,Activity、Service、还有Application。由此,其实咱们就已经能够得出结论了,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各类承担着不一样的做用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。git
那么Context到底能够实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操做数据库等等等等都须要用到Context。因为Context的具体能力是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是能够通用的。不过有几种场景比较特殊,好比启动Activity,还有弹出Dialog。出于安全缘由的考虑,Android是不容许Activity或Dialog凭空出现的,一个Activity的启动必需要创建在另外一个Activity的基础之上,也就是以此造成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),所以在这种场景下,咱们只能使用Activity类型的Context,不然将会出错。github
你们注意看到有一些NO上添加了一些数字,其实这些从能力上来讲是YES,可是为何说是NO呢?下面一个一个解释:数据库
数字1:启动Activity在这些类中是能够的,可是须要建立一个新的task。通常状况不推荐。安全
数字2:在这些类中去layout inflate是合法的,可是会使用系统默认的主题样式,若是你自定义了某些样式可能不会被使用。网络
数字3:在receiver为null时容许,在4.2或以上的版本中,用于获取黏性广播的当前值。(能够无视)app
注:ContentProvider、BroadcastReceiver之因此在上述表格中,是由于在其内部方法中都有一个context用于使用。框架
好了,这里咱们看下表格,重点看Activity和Application,能够看到,和UI相关的方法基本都不建议或者不可以使用Application,而且,前三个操做基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity作为Context来处理;其余的一些操做,Service,Activity,Application等实例均可以,固然了,注意Context引用的持有,防止内存泄漏。
那么一个应用程序中到底有多少个Context呢?其实根据上面的Context类型咱们就已经能够得出答案了。Context一共有Application、Activity和Service三种类型,所以一个应用程序中Context数量的计算公式就能够这样写:
上面的1表明着Application的数量,由于一个应用程序中能够有多个Activity和多个Service,可是只能有一个Application。
1. getApplicationContext() :
这个函数返回的这个Application的上下文,因此是与app挂钩的,因此在整个生命周期里面都是不变的,这个好理解,可是使用的时候要注意,该context是和引用的生命周期一致的,因此和activity生命周期挂钩的任务不要使用该context,好比网络访问,防止内存泄露
2. getBasecontext():
stackoverflow上面写的是,这个函数不该该被使用,用Context代替,而Context是与activity相关连,因此当activity死亡后可能会被destroyed,我举个我本身写的例子
public Dialog displayDialog(int choice) { switch(choice){ case 0: AlertDialog aDialog = new AlertDialog.Builder(this) .setIcon(R.drawable.ic_launcher) .setTitle("Hello World") .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { Toast.makeText(getBaseContext(), "OK clicked", Toast.LENGTH_SHORT).show(); } }); } }
这个例子中的getBaseContext()就不能被this代替,由于上面的this返回的是这个activity的context,而在这个onClick函数中若是使用this的话,则返回的是这个AlertDialog的context,因此要使用的是当前activity名.this 去使用,好比当前activity为 TestActivity,那么在里面就是用TestActivity.this便可
3. getApplication():
getApplication只能被Activity和Services使用,虽然在如今的Android的实现中,getApplication和getApplicationContext返回同样的对象,但也不能保证这两个函数同样(例如在特殊的提供者来讲),因此若是你想获得你在Manifest文件里面注册的App class,你不要去调用getApplicationContext,觉得你可能得不到你所要的app实例(你显然有测试框架的经验)。。。。
翻译完成,一目了然(哪里翻译错误,请指出,水B一只),原文:
getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).
4. getParent() :
返回activity的上下文,若是这个子视图的话,换句话说,就是当在子视图里面调用的话就返回一个带有子视图的activity对象,一目了然。。。
5.getActivity():
在fragment中使用,返回该fragment所依附的activity上下文
6.this
记住Activity,Service类,Application类是继承自Context类的,因此在有的时候须要上下文,只须要使用this关键字便可,可是有的时候再线程里面,this关键字的意义就改变了,但这个时候若是须要上下文,则须要使用 类名.this,这样就能够了
这里有点注意的:
作项目时碰见的,提一下吧,动态注册广播,在调用registerBroadcast函数的时候,须要传入一个上下文和broadcastReceiver,查看源码能够知道,存储的时候context是做为一个key的做用,因此使用同一个context来注册同一个广播,onreceive只会调用一次,可是若是使用不一样的context,则会调用屡次,虽然不调用unregisterBroadcast有时也没事,不会报错,可是必定不要忘记取消注销
后续:为了简化context的使用方法,如今有这么一种方法,就是在Application类里面维护一个弱引用:
/** 用来保存当前该Application的context */ private static Context instance; /** 用来保存最新打开页面的context */ private volatile static WeakReference<Context> instanceRef = null;
再写一个方法,
最后在应用的Activity基类中(这个应该有的吧)加上两个语句:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RootApplication.setInstanceRef(this); }
protected void onResume() { super.onResume(); //也要在onresume函数里面进行设置,保证弱引用一直引用当前的可见页面 RootApplication.setInstanceRef(this); }
这样每次调用application的getInstance()方法必定可以返回一个context,并且是当前惟一可见activity的context,其余地方就能够直接使用了,不用处处传递context,再此处统一维护便可,
1 public class SampleActivity extends Activity { 2 3 private final Handler mLeakyHandler = new Handler() { 4 @Override 5 public void handleMessage(Message msg) { 6 // ... 7 } 8 } 9 }
若是没有仔细观察,上面的代码可能致使严重的内存泄露。Android Lint会给出下面的警告:
In Android, Handler classes should be static or leaks might occur.
可是究竟是泄漏,如何发生的?让咱们肯定问题的根源,先写下咱们所知道的
一、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程建立一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。全部主要的应用程序框架事件(如活动生命周期方法调用,单击按钮,等等)都包含在Message对象,它被添加到Looper的消息队列而后一个个被处理。主线程的Looper在应用程序的整个生命周期中存在。
二、当一个Handle在主线程被实例化,它就被关联到Looper的消息队列。被发送到消息队列的消息会持有一个Handler的引用,以便Android框架能够在Looper最终处理这个消息的时候,调用Handler#handleMessage(Message)。
三、在Java中,非静态的内部类和匿名类会隐式地持有一个他们外部类的引用。静态内部类则不会。
那么,究竟是内存泄漏?好像很难懂,让咱们如下面的代码做为一个例子
1 public class SampleActivity extends Activity { 2 3 private final Handler mLeakyHandler = new Handler() { 4 @Override 5 public void handleMessage(Message msg) { 6 // ... 7 } 8 } 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 14 // 延时10分钟发送一个消息 15 mLeakyHandler.postDelayed(new Runnable() { 16 @Override 17 public void run() { } 18 }, 60 * 10 * 1000); 19 20 // 返回前一个Activity 21 finish(); 22 } 23 }
当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler有隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。所以Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第15行的匿名Runnable类也同样。匿名类的非静态实例持有一个隐式的外部类引用,所以context将被泄露。
为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。因此不会致使它的Activity泄露。若是你须要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外致使context泄露。为了解决咱们实例化匿名Runnable类可能致使的内存泄露,咱们将用一个静态变量来引用他(由于匿名类的静态实例不会隐式持有他们外部类的引用)。
1 public class SampleActivity extends Activity { 2 /** 3 * 匿名类的静态实例不会隐式持有他们外部类的引用 4 */ 5 private static final Runnable sRunnable = new Runnable() { 6 @Override 7 public void run() { 8 } 9 }; 10 11 private final MyHandler mHandler = new MyHandler(this); 12 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 17 // 延时10分钟发送一个消息. 18 mHandler.postDelayed(sRunnable, 60 * 10 * 1000); 19 20 // 返回前一个Activity 21 finish(); 22 } 23 24 /** 25 * 静态内部类的实例不会隐式持有他们外部类的引用。 26 */ 27 private static class MyHandler extends Handler { 28 private final WeakReference<SampleActivity> mActivity; 29 30 public MyHandler(SampleActivity activity) { 31 mActivity = new WeakReference<SampleActivity>(activity); 32 } 33 34 @Override 35 public void handleMessage(Message msg) { 36 SampleActivity activity = mActivity.get(); 37 38 if (activity != null) { 39 // ... 40 } 41 } 42 } 43 }
静态和非静态内部类的区别是比较难懂的,但每个Android开发人员都应该了解。开发中不能碰的雷区是什么?不在一个Activity中使用非静态内部类, 以防它的生命周期比Activity长。相反,尽可能使用持有Activity弱引用的静态内部类。
基本上每个应用程序都会有一个本身的Application,并让它继承自系统的Application类,而后在本身的Application类中去封装一些通用的操做。其实这并非Google所推荐的一种作法,由于这样咱们只是把Application当成了一个通用工具类来使用的,而实际上使用一个简单的单例类也能够实现一样的功能。可是根据个人观察,有太多的项目都是这样使用Application的。固然这种作法也并无什么反作用,只是说明仍是有很多人对于Application理解的还有些欠缺。那么这里咱们先来对Application的设计进行分析,讲一些你们所不知道的细节,而后再看一下平时使用Application的问题。
首先新建一个MyApplication并让它继承自Application,而后在AndroidManifest.xml文件中对MyApplication进行指定,以下所示:
指定完成后,当咱们的程序启动时Android系统就会建立一个MyApplication的实例,若是这里不指定的话就会默认建立一个Application的实例。
前面提到过,如今不少的Application都是被看成通用工具类来使用的,那么既然做为一个通用工具类,咱们要怎样才能获取到它的实例呢?以下所示:
能够看到,代码很简单,只须要调用getApplication()方法就能拿到咱们自定义的Application的实例了,打印结果以下所示:
那么除了getApplication()方法,其实还有一个getApplicationContext()方法,这两个方法看上去好像有点关联,那么它们的区别是什么呢?咱们将代码修改一下:
一样,咱们把getApplicationContext()的结果打印了出来,如今从新运行代码,结果以下图所示:
咦?好像打印出的结果是同样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,由于前面已经说过了,Application自己就是一个Context,因此这里获取getApplicationContext()获得的结果就是MyApplication自己的实例。
那么有的朋友可能就会问了,既然这两个方法获得的结果都是相同的,那么Android为何要提供两个功能重复的方法呢?实际上这两个方法在做用域上有比较大的区别。getApplication()方法的语义性很是强,一看就知道是用来获取Application实例的,可是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数状况下咱们都是在Activity或者Service中使用Application的,可是若是在一些其它的场景,好比BroadcastReceiver中也想得到Application的实例,这时就能够借助getApplicationContext()方法了,以下所示:
也就是说,getApplicationContext()方法的做用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法均可以拿到咱们的Application对象。
那么更加细心的朋友会发现,除了这两个方法以外,其实还有一个getBaseContext()方法,这个baseContext又是什么东西呢?咱们仍是经过打印的方式来验证一下:
哦?此次获得的是不一样的对象了,getBaseContext()方法获得的是一个ContextImpl对象。这个ContextImpl是否是感受有点似曾相识?回去看一下Context的继承结构图吧,ContextImpl正是上下文功能的实现类。也就是说像Application、Activity这样的类其实并不会去具体实现Context的功能,而仅仅是作了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。那么这样的设计究竟是怎么实现的呢?咱们仍是来看一下源码吧。由于Application、Activity、Service都是直接或间接继承自ContextWrapper的,咱们就直接看ContextWrapper的源码,以下所示:
因为ContextWrapper中的方法仍是很是多的,我就进行了一些筛选,只贴出来了部分方法。那么上面的这些方法相信你们都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱们常常要用到的方法。那么全部这些方法的实现又是什么样的呢?其实全部ContextWrapper中方法的实现都很是统一,就是调用了mBase对象中对应当前方法名的方法。
那么这个mBase对象又是什么呢?咱们来看第16行的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法实际上是由系统来调用的,它会把ContextImpl对象做为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,以后ContextWrapper中的全部方法其实都是经过这种委托的机制交由ContextImpl去具体实现的,因此说ContextImpl是上下文功能的实现类是很是准确的。
那么另外再看一下咱们刚刚打印的getBaseContext()方法,在第26行。这个方法只有一行代码,就是返回了mBase对象而已,而mBase对象其实就是ContextImpl对象,所以刚才的打印结果也获得了印证。
虽然说Application的用法确实很是简单,可是咱们平时的开发工做当中也着实存在着很多Application误用的场景,那么今天就来看一看有哪些比较容易犯错的地方是咱们应该注意的。
Application是Context的其中一种类型,那么是否就意味着,只要是Application的实例,就能随时使用Context的各类方法呢?咱们来作个实验试一下就知道了:
这是一个很是简单的自定义Application,咱们在MyApplication的构造方法当中获取了当前应用程序的包名,并打印出来。获取包名使用了getPackageName()方法,这个方法就是由Context提供的。那么上面的代码能正常运行吗?跑一下就知道了,你将会看到以下所示的结果:
应用程序一启动就马上崩溃了,报的是一个空指针异常。看起来好像挺简单的一段代码,怎么就会成空指针了呢?可是若是你尝试把代码改为下面的写法,就会发现一切正常了:
运行结果以下所示:
在构造方法中调用Context的方法就会崩溃,在onCreate()方法中调用Context的方法就一切正常,那么这两个方法之间到底发生了什么事情呢?咱们从新回顾一下ContextWrapper类的源码,ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,以后mBase对象就有值了。而咱们又知道,全部Context的方法都是调用这个mBase对象的同名方法,那么也就是说若是在mBase对象还没赋值的状况下就去调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种状况。Application中方法的执行顺序以下图所示:
Application中在onCreate()方法里去初始化各类全局的变量数据是一种比较推荐的作法,可是若是你想把初始化的时间点提早到极致,也能够去重写attachBaseContext()方法,以下所示:
以上是咱们平时在使用Application时须要注意的一个点,下面再来介绍另一种很是广泛的Application误用状况。
其实Android官方并不太推荐咱们使用自定义的Application,基本上只有须要作一些全局初始化的时候可能才须要用到自定义Application,官方文档描述以下:
可是就个人观察而言,如今自定义Application的使用状况基本上能够达到100%了,也就是咱们平时本身写测试demo的时候可能不会使用,正式的项目几乎所有都会使用自定义Application。但是使用归使用,有很多项目对自定义Application的用法并不到位,正如官方文档中所表述的同样,多数项目只是把自定义Application当成了一个通用工具类,而这个功能并不须要借助Application来实现,使用单例多是一种更加标准的方式。
不过自定义Application也并无什么反作用,它和单例模式二选一均可以实现一样的功能,可是我见过有一些项目,会把自定义Application和单例模式混合到一块儿使用,这就让人大跌眼镜了。一个很是典型的例子以下所示:
就像单例模式同样,这里提供了一个getInstance()方法,用于获取MyApplication的实例,有了这个实例以后,就能够调用MyApplication中的各类工具方法了。
可是这种写法对吗?这种写法是大错特错!由于咱们知道Application是属于系统组件,系统组件的实例是要由系统来去建立的,若是这里咱们本身去new一个MyApplication的实例,它就只是一个普通的Java对象而已,而不具有任何Context的能力。有不少人向我反馈使用 LitePal 时发生了空指针错误其实都是因为这个缘由,由于你提供给LitePal的只是一个普通的Java对象,它没法经过这个对象来进行Context操做。
那么若是真的想要提供一个获取MyApplication实例的方法,比较标准的写法又是什么样的呢?其实这里咱们只需谨记一点,Application全局只有一个,它自己就已是单例了,无需再用单例模式去为它作多重实例保护了,代码以下所示:
getInstance()方法能够照常提供,可是里面不要作任何逻辑判断,直接返回app对象就能够了,而app对象又是什么呢?在onCreate()方法中咱们将app对象赋值成this,this就是当前Application的实例,那么app也就是当前Application的实例了。