今天来聊一聊Android中的context对象
1.Context是什么
我们知道,Android应用都是使用Java语言来编写的,那么大家可以思考一下,一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析,Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,我们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。
context在android中被称之为 “上帝对象”,其具体的类图如下所示:
从类图中我们可以得出一个进程中context的总体数量为 : 注意 这里只是应用级的context对象
总体数量 = service数量 + activity 数量 + 1(application数量)//一个进程中只能存在一个application对象 故application的数量为 1
从类图中可以看出,context结构组成用的是典型的 装饰者模式,ContextImpl 是 context 抽象的具体实现(被装饰者对象),context的大部分功能都是在这个类中实现,ContextWrapper是context的扩展(装饰者对象)。下面来看看 ContextWrapper 的具体代码:
public ContextWrapper(Context base) {//这里传进来的其实是contextImpl对象
mBase = base;
}
/**
* 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;//这里传进来的其实是contextImpl对象
}
有了mBase这个context对象(实际是contextImpl),contextWrapper中大部分的操作都是调用该对象中的方法完成。
2.Context的作用
context中定义了一系列Android引用中相当牛逼的方法,如资源文件获取,文件管理,包管理,类加载,权限管理,系统级别服务获取等。下面列举一部分:
获取AssetManager:getAssets();
获取Resources:getResources();
获取PackageManager:getPackageManager();
获取ContentResolver:getContentResolver();
获取主线程Looper:getMainLooper();
获取Application的Context:getApplicationContext();
获取资源文件:getText,getString,getColor,getDrawable,getColorStateList;
设置主题,获取主题资源id:setTheme,getThemeResId;
获取样式属性TypedArray:obtainStyledAttributes();
获取类加载器ClassLoader:getClassLoader();
获取应用信息对象ApplicationInfo:getApplicationInfo();
获取SharedPreferences:getSharedPreferences();
打开文件FileInputStream:openFileInput();
删除文件:deleteFile();
获取文件File:getFileStreamPath();
打开或者创建数据库:openOrCreateDatabase();
移除或者删除数据库:moveDatabaseFrom(),deleteDatabase();
**启动Activity:startActivity(), startActivityAsUser(), startActivityForResult(), startActivities(); **
注册、发送、注销广播:registerReceiver(), sendBroadcast(), sendOrderedBroadcast(), unregisterReceiver();
启动、绑定、解除绑定、停止服务:startService(), bindService(), unbindService(), stopService();
获取系统服务:getSystemService();
检查权限(Android 6.0以上):checkPermission();
根据应用名创建Context:createPackageContext();
根据应用信息创建Context:createApplicationContext();
获取显示信息对象Display:getDisplay();
高亮显示的是我们常用的几个context的方法,其具体实现在我们的劳动力 ContextImpl中
application与activity的创建都是在activity启动的时候,service的创建过程与这个过程差不多,只不过是在启动service中创建而已
3.Context的作用域
虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
Context的作用域.png这里要注意两点:
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对象,主要有以下四种方法
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也可以。
5.Context引起的内存泄露
但Context并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。
错误的单例模式
这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。
View持有Activity引用
有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。
6.正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
网友评论