重温Context

简介: 从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。

对于Context,我们并不陌生,项目中,几乎无处不在,启动Activity,Service,Broadcast需要它,弹Dialog,Toast需要它,适配器里创建布局也需要它,自定义View中,封装的工具类中,它的影子随处可见,出镜率如此之高,你真的了解它吗?


image.png


Context,从翻译中我们得知,上下文,环境,听起来比较笼统,举一个很简单的例子,你从家要去目的地上班,会怎么去?火车,飞机还是自驾,那么你选择的交通工具,我们就可以理解为一个Context,指的就是我们交通运输的载体;也正因为如此,在加载资源、启动Activity、获取系统服务、创建View等操作,Context都要参与。


我们再做一个剖析,查看Context的源码,我们可以得知,Context是个抽象类,通过类的结构可以看到:Activity、Service、Application都是Context的子类,如下图。


image.png


通过以上的分析,我们可以简单的对Context做个小结:


从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程。


从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。


了解了Context是个什么玩意后,不妨我们再进一步分析分析,它的继承结构,它的常用方法,它的作用域等。


1、继承结构


image.png


从上图可以看出 Context的继承结构,直接子类有两个ContextWarpper和ContextImpl。


ContextWarpper: 是上下文功能的封装类,它又有三个直接的子类,ContextThemeWarpper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而他有一个直接子类就是Activity。这里我们看到了几个比较熟悉的类:Activity、Service、Application。由此我们大致得出结论,Context一共有三种类型,分别是Activity、Service、Application。这三个类虽然分别承担各种不同的作用,但他们都属于Context的一种,而他们具体Context的功能是由ContextImpl类实现的。


ContextImpl:是上下文功能实现类。


2、Context常用方法

//获取应用程序包的AssetManager实例publicabstractAssetMangergetAssets();
//获取应用程序包Resources 实例publicabstractResourcesgetResources();
//获取PackageManager实例,以查看全局package信息publicabstractPackageManagergetPackageManager();
//获取应用程序包的ContentResolverpublicabstractContentResolvergetContentResolved();
//它返回当前进程的主线程的Looper,此线程分发调用给应用组件(activities,services等)publicabstractLoopergetMainLooper();
// 返回当前进程的单实例全局Application对象的ContextpublicabstractContextgetApplicationContext();
//从strng表中获取本地化的。格式化的字符序列publicfinalCharSequencegetText(intresId){
returngetResources().getText(resId);
}
// 从string表中获取本地化的字符串publicfinalStringgetString(intresId) {
returngetResources().getString(resId);
}
publicfinalStringgetString(intresId, Object... formatArgs) {
returngetResources().getString(resId, formatArgs);
}
//返回一个可用于获取包中类信息的class loaderpublicabstractClassLoadergetClassLoader();
//返回应用程序包名publicabstractStringgetPackageName();
//返回应用程序信息publicabstractApplicationInfogetApplicationInfo():
//根据文件名获取SharedPreferencespublicabstractSharedPreferencesgetSharedPreferences(Stringname , intmode);
//其根目录为: Environment.getExternalStorageDirectory()publicabstractFilegetExternalFilesDir(Stringtype);
//返回应用程序obb文件路径publicabstractFilegetObbDir();
//启动一个新的ActivitypublicabstractvoidstartActivity(Intentintent);
// 启动一个新的activitypublicvoidstartActivityAsUser(Intentintent, UserHandleuser) {
thrownewRuntimeException("Not implemented. Must override in a subclass.");
}
// 启动一个新的activity// intent: 将被启动的activity的描述信息// options: 描述activity将如何被启动publicabstractvoidstartActivity(Intentintent, Bundleoptions);
// 启动多个新的activitypublicabstractvoidstartActivities(Intent[] intents);
// 启动多个新的activitypublicabstractvoidstartActivities(Intent[] intents, Bundleoptions);
// 广播一个intent给所有感兴趣的接收者,异步机制publicabstractvoidsendBroadcast(Intentintent);
// 广播一个intent给所有感兴趣的接收者,异步机制publicabstractvoidsendBroadcast(Intentintent,StringreceiverPermission);
//发送有序广播publicabstractvoidsendOrderedBroadcast(Intentintent,StringreceiverPermission);
publicabstractvoidsendOrderedBroadcast(Intentintent,
StringreceiverPermission, BroadcastReceiverresultReceiver,
Handlerscheduler, intinitialCode, StringinitialData,
BundleinitialExtras);
publicabstractvoidsendBroadcastAsUser(Intentintent, UserHandleuser);
publicabstractvoidsendBroadcastAsUser(Intentintent, UserHandleuser,
StringreceiverPermission);
// 注册一个BroadcastReceiver,且它将在主activity线程中运行publicabstractIntentregisterReceiver(BroadcastReceiverreceiver,
IntentFilterfilter);
//取消注册BroadcastReceiverpublicabstractIntentregisterReceiver(BroadcastReceiverreceiver,
IntentFilterfilter, StringbroadcastPermission, Handlerscheduler);
publicabstractvoidunregisterReceiver(BroadcastReceiverreceiver);
// 请求启动一个application servicepublicabstractComponentNamestartService(Intentservice);
// 请求停止一个application servicepublicabstractbooleanstopService(Intentservice);
// 连接一个应用服务,它定义了application和service间的依赖关系publicabstractbooleanbindService(Intentservice, ServiceConnectionconn,
intflags);
// 断开一个应用服务,当服务重新开始时,将不再接收到调用,// 且服务允许随时停止publicabstractvoidunbindService(ServiceConnectionconn);
// 返回系统级servicepublicabstractObjectgetSystemService(Stringname);
//检查权限publicabstractintcheckPermission(Stringpermission, intpid, intuid);
// 返回一个新的与application name对应的Context对象publicabstractContextcreatePackageContext(StringpackageName,
intflags) throwsPackageManager.NameNotFoundException;
// 返回基于当前Context对象的新对象,其资源与display相匹配publicabstractContextcreateDisplayContext(Displaydisplay);

3、Context主要功能:


1)启动Activity


2)启动和停止Service


3)发送广播消息(Intent)


4)注册广播消息(Intent)接收者


5)可以访问APK中各种资源(如Resources和AssetManager等)


6)可以访问Package的相关信息


7)APK的各种权限管理


4、Context 应用场景


因为Context的具体能力是由ContextImpl类去实现的,所以在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。


Context的应用场景图


image.png


5、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()方法了。


6、Context 使用过程中的注意项


1)、Activity mActivity =new Activity()


这样写语法上没有任何错误,Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象。但是,


Android程序不像Java程序一样,随便创建一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,才能使得其正常工作。即走正常的onCreate-onStart-onResume。


2)、我们在编写成单例的方式时,这些工具类大多需要去访问资源,也就说需要Context的参与。


在这样的情况下,就需要注意Context的引用问题。


publicclassTestInstence{ 
privatestaticTestInstencesInstance; 
privateContextmContext; 
privateTestInstence(Contextcontext)  
    { 
this.mContext=context;  
    } 
publicstaticsynchronizedTestInstencegetInstance(Contextcontext)  
    {
if (sInstance==null)  
        {  
sInstance=newTestInstence(context);  
        } 
returnsInstance;  
    } 
}


对于上述的单例,内部保持了一个Context的引用;这么写是没有问题的,问题在于,这个Context哪来的我们不能确定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,我们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity作为Context,也就是说,我们的这个Activity只要我们的项目活着,就没有办法进行内存回收。而我们的Activity的生命周期肯定没这么长,所以造成了内存泄漏。那么,我们如何才能避免这样的问题呢?有人会说,我们可以软引用,嗯,软引用,假如被回收了,你不怕NullPointException么。把上述代码做下修改:


publicstaticsynchronizedTestInstencegetInstance(Contextcontext)  
    {
if (sInstance==null)  
        {  
sInstance=newTestInstence(context.getApplicationContext());  
        } 
returnsInstance;  
    } 


这样,我们就解决了内存泄漏的问题,因为我们引用的是一个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。


publicstaticvoidstartActivity(Contextcontext){
Intentintent=newIntent(context.getApplicationContext(), SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.getApplicationContext().startActivity(intent);
    }


总结,如何在程序中正确的使用Context:


一般Context造成的内存泄漏,几乎都是当Context销毁的时候,因为被引用导致销毁失败。而Application的Context对象可以简单的理解为伴随着进程存在的(它的生命周期也很长,毕竟APP加载的时候先加载Application,我们可以自定义Application然后继承系统的Application)。


正确使用:


1、当Applicatin的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context;


2、不要让生命周期长于Activity的对象持有Activity的引用。


3、尽量不要在Activity中使用非静态内部类。非静态内部类会隐式持有外部类实例的引用。如果使用静态内部类,将外部实例引用作为弱引用持有。

相关文章
|
5月前
|
设计模式 安全 Java
程序与技术分享:Context详解
程序与技术分享:Context详解
77 0
|
6月前
|
Kubernetes 安全 容器
k8s学习-CKS真题-Context安全上下文
k8s学习-CKS真题-Context安全上下文
126 0
|
前端开发
前端学习笔记202305学习笔记第二十五天-listeners和setMaxListener详解
前端学习笔记202305学习笔记第二十五天-listeners和setMaxListener详解
52 0
|
Java 编译器 C语言
【重学C/C++系列(八)】:如何理解C++中的void*?
首先void*中的void代表一个任意的数据类型,"星号"代表一个指针,所以其就是一个任意数据类型的指针。
【重学C/C++系列(八)】:如何理解C++中的void*?
|
监控 安全 Go
Go语言核心手册-11.context.Context
我们今天主要讨论的是context包中的函数和Context类型,该包中的函数都是用于产生新的Context类型值的,Context类型是一个可以帮助我们实现多goroutine 协作流程的同步工具,不但如此,我们还可以通过此类型的值传达撤销信号或传递数据。
103 0
Go语言核心手册-11.context.Context
|
XML JSON 测试技术
lombok的@Builder注解原理背后干了啥?
lombok的@Builder注解原理背后干了啥?
542 0
lombok的@Builder注解原理背后干了啥?
C#基础知识学习之 ✨ “精神小伙“——委托(delegate) 的含义和用法
C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。 委托就是用来储存方法的结构 委托(Delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。
|
设计模式 安全 IDE
Context都没弄明白,还怎么做Android开发?
什么是 Context? 四大组件和 Context Application 和 Context 为什么 Application 的 Context 不可以创建 Dialog ?
|
消息中间件 Android开发