Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?
一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序。
public abstract class Context {
// 获取应用程序包的AssetManager实例
public abstract AssetManager getAssets();
// 获取应用程序包的Resources实例
public abstract Resources getResources();
// 获取PackageManager实例,以查看全局package信息
public abstract PackageManager getPackageManager();
// 获取应用程序包的ContentResolver实例
public abstract ContentResolver getContentResolver();
// 它返回当前进程的主线程的Looper,此线程分发调用给应用组件(activities, services等)
public abstract Looper getMainLooper();
// 返回当前进程的单实例全局Application对象的Context
public abstract Context getApplicationContext();
// 从string表中获取本地化的、格式化的字符序列
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);
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);
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);
}
一个应用程序有几个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,否则将会出错。
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。
如何获取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(),这两个方法有什么区别?其实两个方法返回的地址都是相同的,只是作用域不同而已。
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系统中的地位很重要,它几乎无所不能,但它也不是你想用就能随便用的,谨防使用不当引起的内存问题。