Context究竟是什么?
在我们开发中,Context随处可见,它究竟是什么呢?
1、Context 概念:
从程序的角度上来理解: Context是个抽象类,通过类的结构可以看到:Activity、Service、Application都是Context的子类;
从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程。

应用在三种情况下会创建Context对象:
1)、创建Application对象时,即第一次启动App时。整个App共一个Application对象,所以也只有一个Application的Context,Application销毁,它也销毁
2)、创建Activity对象时,Activity销毁,它也销毁。
3)、创建Service对象时,Service销毁,它也销毁。
由此可以得到应用程序APP创建的Context的个数为:
Service个数+Activity个数+Application。

2、Context的继承结构:
从上图可以看出 Context的继承结构还是比较复杂的。直接子类有两个ContextWarpper和ContextImpl。
ContextWarpper: 是上下文功能的封装类,它又有三个直接的子类,ContextThemeWarpper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而他有一个直接子类就是Activity。这里我们看到了几个比较熟悉的类:Activity、Service、Application。由此我们大致得出结论,Context一共有三种类型,分别是Activity、Service、Application。这三个类虽然分别承担各种不同的作用,但他们都属于Context的一种,而他们具体Context的功能是由ContextImpl类实现的。
ContextImpl:是上下文功能实现类。
其实Context还有一个直接子类MockContext,该类可以理解为模拟context, 源码位android.test.mock包中,API文档中找不到。 当我们要测试一个模块A,他依赖于其他模块B,但是模块B还没有实现或者现在根本没有,这时就要使用MockContext和其他同样位于android.test.mock包中的类。通过它可以注入其他依赖,模拟Context,或者监听测试的类。主要是在TDD中使用这些MOCK类来代替真是的类,用法可参考Mock在Android TDD中的使用。想查看ContextImpl源码时,无法找到ContextImpl这个类。由于ContextImpl是抽象类Context的实现类。然而查看Context类的继承结构,没有发现ContextImpl。原因是:这个文件是保护文件,就是注解了内部保护文件,所以在Android Studio 中不显示。所以可以去SKD的安装目录中sources文件夹中直接找到那个Java文件,/android-sdk/sources/android-19/android/app/ContextImpl.java。
3、Context常用方法
//获取应用程序包的AssetManager实例
public abstract AssetManger getAssets();
//获取应用程序包Resources 实例
public abstract Resources getResources();
//获取PackageManager实例,以查看全局package信息
public abstract PackageManager getPackageManager();
//获取应用程序包的ContentResolver
public abstract ContentResolver getContentResolved();
//它返回当前进程的主线程的Looper,此线程分发调用给应用组件(activities,services等)
public abstract Looper getMainLooper();
// 返回当前进程的单实例全局Application对象的Context
public abstract Context getApplicationContext();
//从strng表中获取本地化的。格式化的字符序列
public final CharSequence getText(int resId){
return getResources().getText(resId);
}
// 从string表中获取本地化的字符串
public final String getString(int resId) {
return getResources().getString(resId);
}
public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
//返回一个可用于获取包中类信息的class loader
public abstract ClassLoader getClassLoader();
//返回应用程序包名
public abstract String getPackageName();
//返回应用程序信息
public abstract ApplicationInfo getApplicationInfo():
//根据文件名获取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name , int mode);
//其根目录为: Environment.getExternalStorageDirectory()
public abstract File getExternalFilesDir(String type);
//返回应用程序obb文件路径
public abstract File getObbDir();
//启动一个新的Activity
public abstract void startActivity(Intent intent);
// 启动一个新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
// 启动一个新的activity
// intent: 将被启动的activity的描述信息
// options: 描述activity将如何被启动
public abstract void startActivity(Intent intent, Bundle options);
// 启动多个新的activity
public abstract void startActivities(Intent[] intents);
// 启动多个新的activity
public abstract void startActivities(Intent[] intents, Bundle options);
// 广播一个intent给所有感兴趣的接收者,异步机制
public abstract void sendBroadcast(Intent intent);
// 广播一个intent给所有感兴趣的接收者,异步机制
public abstract void sendBroadcast(Intent intent,String receiverPermission);
//发送有序广播
public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);
public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);
// 注册一个BroadcastReceiver,且它将在主activity线程中运行
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);
//取消注册BroadcastReceiver
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);
public abstract void unregisterReceiver(BroadcastReceiver receiver);
// 请求启动一个application service
public abstract ComponentName startService(Intent service);
// 请求停止一个application service
public abstract boolean stopService(Intent service);
// 连接一个应用服务,它定义了application和service间的依赖关系
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);
// 断开一个应用服务,当服务重新开始时,将不再接收到调用,
// 且服务允许随时停止
public abstract void unbindService(ServiceConnection conn);
// 返回系统级service
public abstract Object getSystemService(String name);
//检查权限
public abstract int checkPermission(String permission, int pid, int uid);
// 返回一个新的与application name对应的Context对象
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;
// 返回基于当前Context对象的新对象,其资源与display相匹配
public abstract Context createDisplayContext(Display display);
5、Context主要功能:
1)启动Activity
2)启动和停止Service
3)发送广播消息(Intent)
4)注册广播消息(Intent)接收者
5)可以访问APK中各种资源(如Resources和AssetManager等)
6)可以访问Package的相关信息
7)APK的各种权限管理
6、Context如何获取
1)、View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
2)、Activity.getApplicationContext,获取当前Activity所在的进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context.
3)、ContextWarpper.getBaseContext():用来获取一个ContextWarpper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4)、Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
getApplicationContext()和getApplication()
getApplicationContext :取得的是当前app所使用的application,这在AndroidManifest中唯一指定。意味着,在当前app的任意位置使用这个函数得到的是同一个Context,getApplicationContext():返回应用的上下文,生命周期是整个应用,应用摧毁,它才摧毁。
getApplication:android开发中共享全局数据;
getApplication() 只能在Activity和Service里使用,指向的是Application对象,因为Application也是Context的一个子类,所以getApplication()可以被用来指向Context。
比如如果想要获取在应用清单文件中声明的类,最好不要使用getApplicationContext(),并且最好使用强制转换为自己自定义的Application,因为那样可能会得不到Application对象。
如果我们也想在BroadcastReceiver中也想获得Application实例,这时就可以借助getApplicationContext()方法了。
7、Context 应用场景
因为Context的具体能力是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
应用场景图:

大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?下面一个一个解释:
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。
好了,这里我们看下表格,重点看Activity和Application,可以看到,和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
8、Context 使用过程中的注意项
1)、Activity mActivity =new Activity()
这样写语法上没有任何错误,Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象。但是,
Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,才能使得其正常工作。即走正常的onCreate-onStart-onResume。。。
2)、我们在编写成单例的方式时,这些工具类大多需要去访问资源,也就说需要Context的参与。
在这样的情况下,就需要注意Context的引用问题。
public class TestInstence
{
private static TestInstence sInstance;
private Context mContext;
private TestInstence(Context context)
{
this.mContext = context;
}
public static synchronized TestInstence getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new TestInstence(context);
}
return sInstance;
}
}
对于上述的单例,内部保持了一个Context的引用;这么写是没有问题的,问题在于,这个Context哪来的我们不能确定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。那么,我们如何才能避免这样的问题呢?有人会说,我们可以软引用,嗯,软引用,假如被回收了,你不怕NullPointException么。把上述代码做下修改:
public static synchronized TestInstence getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new TestInstence(context.getApplicationContext());
}
return sInstance;
}
这样,我们就解决了内存泄漏的问题,因为我们引用的是一个ApplicationContext,它的生命周期和我们的单例对象一致。
3)、Intent也要求指出上下文,如果想启动一个新的Activity,就必须在Intent中使用Activity的上下文,这样新启动的Activity才能和当前Activity有关联(在activity栈);也可以使用application的context,但是需要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK标志,当作一个新任务。ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错,非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所以这种用Application启动Activity的方式不推荐使用,Service同Application。
public static void startActivity(Context context){
Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.getApplicationContext().startActivity(intent);
}