一、Context 概念
Context是个抽象类,经过类的结构能够看到:Activity、Service、Application都是Context的子类;android
从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,表明与操做系统的交互的一种过程。安全
从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。app
查看类的继承关系:ctrl + H (Windows系统)ide
应用在三种状况下会建立Context对象(即一般说的context):
1> 建立Application 对象时,即第一次启动app时。 整个App共一个Application对象,因此也只有一个Application 的Context,Application销毁,它也销毁;
2> 建立Activity对象时。Activity销毁,它也销毁;
3> 建立Service对象时。Service销毁,它也销毁。工具
由此能够获得应用程序App能够建立的Context(Activity和Service没启动就不会建立)个数公式通常为:
总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context对象)oop
二、源码中的Contextui
`/** * 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能够查看到他的子类最终能够获得以下关系图:this
Context 继承结构
三、Context做用域
虽然Context神通广大,但并非随便拿到一个Context实例就能够随心所欲,它的使用仍是有一些规则限制的。因为Context的具体实例是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是能够通用的。不过有几种场景比较特殊,好比启动Activity,还有弹出Dialog。出于安全缘由的考虑,Android是不容许Activity或Dialog凭空出现的,一个Activity的启动必需要创建在另外一个Activity的基础之上,也就是以此造成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),所以在这种场景下,咱们只能使用Activity类型的Context,不然将会出错。spa
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引用的持有,防止内存泄漏。
4 Context数量
那么一个应用程序中到底有多少个Context呢?其实根据上面的Context类型咱们就已经能够得出答案了。Context一共有Application、Activity和Service三种类型,所以一个应用程序中Context数量的计算公式就能够这样写:
Context数量 = Activity数量 + Service数量 + 1
上面的1表明着Application的数量,由于一个应用程序中能够有多个Activity和多个Service,可是只能有一个Application。
5 Application Context的设计
基本上每个应用程序都会有一个本身的Application,并让它继承自系统的Application类,而后在本身的Application类中去封装一些通用的操做。其实这并非Google所推荐的一种作法,由于这样咱们只是把Application当成了一个通用工具类来使用的,而实际上使用一个简单的单例类也能够实现一样的功能。可是根据个人观察,有太多的项目都是这样使用Application的。固然这种作法也并无什么反作用,只是说明仍是有很多人对于Application理解的还有些欠缺。那么这里咱们先来对Application的设计进行分析,讲一些你们所不知道的细节,而后再看一下平时使用Application的问题。
首先新建一个MyApplication并让它继承自Application,而后在AndroidManifest.xml文件中对MyApplication进行指定,以下所示:
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > ...... </application>
指定完成后,当咱们的程序启动时Android系统就会建立一个MyApplication的实例,若是这里不指定的话就会默认建立一个Application的实例。
前面提到过,如今不少的Application都是被看成通用工具类来使用的,那么既然做为一个通用工具类,咱们要怎样才能获取到它的实例呢?以下所示:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); } }
能够看到,代码很简单,只须要调用getApplication()方法就能拿到咱们自定义的Application的实例了,打印结果以下所示:
那么除了getApplication()方法,其实还有一个getApplicationContext()方法,这两个方法看上去好像有点关联,那么它们的区别是什么呢?咱们将代码修改一下:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyApplication myApp = (MyApplication) getApplication(); Log.d("TAG", "getApplication is " + myApp); Context appContext = getApplicationContext(); Log.d("TAG", "getApplicationContext is " + appContext); } }
一样,咱们把getApplicationContext()的结果打印了出来,如今从新运行代码,结果以下图所示:
咦?好像打印出的结果是同样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,由于前面已经说过了,Application自己就是一个Context,因此这里获取getApplicationContext()获得的结果就是MyApplication自己的实例。
那么有的朋友可能就会问了,既然这两个方法获得的结果都是相同的,那么Android为何要提供两个功能重复的方法呢?实际上这两个方法在做用域上有比较大的区别。getApplication()方法的语义性很是强,一看就知道是用来获取Application实例的,可是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数状况下咱们都是在Activity或者Service中使用Application的,可是若是在一些其它的场景,好比BroadcastReceiver中也想得到Application的实例,这时就能够借助getApplicationContext()方法了,以下所示:
public class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { MyApplication myApp = (MyApplication) context.getApplicationContext(); Log.d("TAG", "myApp is " + myApp); } }
也就是说,getApplicationContext()方法的做用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法均可以拿到咱们的Application对象。
那么更加细心的朋友会发现,除了这两个方法以外,其实还有一个getBaseContext()方法,这个baseContext又是什么东西呢?咱们仍是经过打印的方式来验证一下:
哦?此次获得的是不一样的对象了,getBaseContext()方法获得的是一个ContextImpl对象。这个ContextImpl是否是感受有点似曾相识?回去看一下Context的继承结构图吧,ContextImpl正是上下文功能的实现类。也就是说像Application、Activity这样的类其实并不会去具体实现Context的功能,而仅仅是作了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。那么这样的设计究竟是怎么实现的呢?咱们仍是来看一下源码吧。由于Application、Activity、Service都是直接或间接继承自ContextWrapper的,咱们就直接看ContextWrapper的源码,以下所示:
/** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed to modify behavior without changing * the original Context. */ public class ContextWrapper extends Context { Context mBase; /** * Set the base context for this ContextWrapper. All calls will then be * delegated to the base context. Throws * IllegalStateException if a base context has already been set. * * @param base The new base context for this wrapper. */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } /** * @return the base context as set by the constructor or setBaseContext */ public Context getBaseContext() { return mBase; } @Override public AssetManager getAssets() { return mBase.getAssets(); } @Override public Resources getResources() { return mBase.getResources(); } @Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); } @Override public Looper getMainLooper() { return mBase.getMainLooper(); } @Override public Context getApplicationContext() { return mBase.getApplicationContext(); } @Override public String getPackageName() { return mBase.getPackageName(); } @Override public void startActivity(Intent intent) { mBase.startActivity(intent); } @Override public void sendBroadcast(Intent intent) { mBase.sendBroadcast(intent); } @Override public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) { return mBase.registerReceiver(receiver, filter); } @Override public void unregisterReceiver(BroadcastReceiver receiver) { mBase.unregisterReceiver(receiver); } @Override public ComponentName startService(Intent service) { return mBase.startService(service); } @Override public boolean stopService(Intent name) { return mBase.stopService(name); } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { return mBase.bindService(service, conn, flags); } @Override public void unbindService(ServiceConnection conn) { mBase.unbindService(conn); } @Override public Object getSystemService(String name) { return mBase.getSystemService(name); } ...... }
因为ContextWrapper中的方法仍是很是多的,我就进行了一些筛选,只贴出来了部分方法。那么上面的这些方法相信你们都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱们常常要用到的方法。那么全部这些方法的实现又是什么样的呢?其实全部ContextWrapper中方法的实现都很是统一,就是调用了mBase对象中对应当前方法名的方法。
那么这个mBase对象又是什么呢?咱们来看第16行的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法实际上是由系统来调用的,它会把ContextImpl对象做为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,以后ContextWrapper中的全部方法其实都是经过这种委托的机制交由ContextImpl去具体实现的,因此说ContextImpl是上下文功能的实现类是很是准确的。
那么另外再看一下咱们刚刚打印的getBaseContext()方法,在第26行。这个方法只有一行代码,就是返回了mBase对象而已,而mBase对象其实就是ContextImpl对象,所以刚才的打印结果也获得了印证。
6正确使用Context
通常Context形成的内存泄漏,几乎都是当Context销毁的时候,却由于被引用致使销毁失败,而Application的Context对象能够理解为随着进程存在的,因此咱们总结出使用Context的正确姿式:
1:当Application的Context能搞定的状况下,而且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽可能不要在Activity中使用非静态内部类,由于非静态内部类会隐式持有外部类实例的引用,若是使用静态内部类,将外部实例引用做为弱引用持有。
7总结
总之Context在Android系统中的地位很重要,它几乎无所不能,但它也不是你想用就能随便用的,谨防使用不当引发的内存问题。
好了,文章到这里就结束了,若是你以为文章写得不错就给个赞呗?若是你以为那里值得改进的,请给我留言。必定会认真查询,修正不足。谢谢。