Context对象是如此常见和传递使用,它可能会很容易产生并不是你预期的情形。加载资源、启动一个新的Activity、获取系统服务、获取内部文件路径以及创建view(其实还远不止这些)统统都需要Context对象来完成。我(原文作者)想做的只是给大家提供一些Context是如何工作的见解,以及让大家在应用中更有效的使用Context的技巧。
Context的类型
并不是所有的context实例都是等价的。根据Android应用的组件不同,你访问的context推向有些细微的差别。
Application - 是一个运行在你的应用进程中的单例。在Activity或者Service中,它可以通过getApplication()函数获得,或者人和继承于context的对象中,通过getApplicationContext()方法获得。不管你是通过何种方法在哪里获得的,在一个进程内,你总是获得到同一个实例。
Activity/Service - 继承于ContextWrapper,它实现了与context同样API,但是代理这些方法调用到内部隐藏的Context实例,即我们所知道的基础context。任何时候当系统创建一个新的Activity或者Service实例的时候,它也创建一个新的ContextImpl实例来做所有的繁重的工作。每一个Activity和Service以及其对应的基础context,对每个实例来说都是唯一的。
BroadcastReciver - 它本身不是context,也没有context在它里面,但是每当一个新的广播到达的时候,框架都传递一个context对象到onReceive()。这个context是一个ReceiverRestrictedContext实例,它有两个主要函数被禁掉:registerReceiver()和bindService()。这两个函数在BroadcastReceiveronReceive()不允许调用。每次Receiver处理一个广播,传递进来的context都是一个新的实例。
ContentProvider - 它本身也不是一个Context,但是它可以通过getContext()函数给你一个Context对象。如果ContentProvider是在调用者的的本地(例如,在同一个应用进程),getContext()将返回的是Application单例。然而,如果调用这和ContentProvider在不同的进程的时候,它将返回一个新创建的实例代表这个Provider所运行的包。
保存引用
第一个我们需要解决问题是,在一个对象或者类内部保存一个context引用,而它生命周期却超过其保存引用的对象的生命周期。例如,创建一个自定义的单例,它需要一个context来加载资源或者获取ContentProvider,从而保存一个指向当前Activiy或者Service的引用在单例中。
糟糕的单例
[java] view plain copy
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
这里的问题在于,我们不知道这个context是从哪里来的,并且如果保存一个最终指向的是Activity或者Servece的引用是并不安全的。这是一个问题,是因为一个单例在类的内部维持一个唯一的静态引用,这意味着我们的对象,以及所有其他它所引用的对象,将永远不能被垃圾回收。假如这个Context是一个Activity,我们将保存与这个Activity相关的所有的view以及其他大的对象,从而造成内存泄漏。
为了解决这个问题,我们修改单例永远只是保存Application context:
改善的单例:
[java] view plain copy
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
//Always pass in the Application Context
sInstance = new CustomManager(contextgetApplicationContext());
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
现在这个例子中,我们的Context来自哪里都没有关系,因为我们这里保存引用是安全的。Application Context 本身就是一个单例,所以我们再创建另外一个static引用,不会造成任何内存泄漏。另外一个很好的例子是,在后台线程或者一个等待的Handler中保存Context的引用,也可以使用这样的方法。
为什么我们不能总是引用Application context呢?正如前面说的,引用Application context永远不用担心内存泄漏的问题。问题的答案,就像我在开始的介绍中说的,是因为不同context并不是等价的。
Context的能力
Conext能做的通用 *** 作决定于这个context最初来源于哪里。下表所列的是,在应用中常见的会收到context对象的,以及对应的每种情况,它可以用于哪些地方:
Application
Activity
Service
ContentProvider
BroadcastReceiver
Show a Dialog NO YES NO NO NO
Start an Activity NO1 YES NO1 NO1 NO1
Layout Inflation NO2 YES NO2 NO2 NO2
Start a Service YES YES YES YES YES
Bind to a Service YES YES YES YES NO
Send a Broadcast YES YES YES YES YES
Register BroadcastReceiver YES YES YES YES NO3
Load Resource Values YES YES YES YES YES
注:NO1 表示Application context的确可以开始一个Activity,但是它需要创建一个新的task。这可能会满足一些特定的需求,但是在你的应用中会创建一个不标准的回退栈(back stack),这通常是不推荐的或者不是是好的实践。
NO2 表示这是非法的,但是这个填充(inflation)的确可以完成,但是是使用所运行的系统默认的主题(theme),而不是你app定义的主题。
NO3 在Android42以上,如果Receiver是null的话(这是用来获取一个sticky broadcast的当前 值的),这是允许的。
用户界面UI
从前面的表格中可以看到,application context有很多功能并不是合适去做,而这些功能都与UI相关。实际上,只有Activity能够处理所有与UI相关的任务。其他类别的context实例功能都差不多。
幸运的是,在应用中这三种 *** 作基本上都不需要在Activity范围之外进行,这很可能是android框架故意这么设计的。尝试显示一个使用Aplication context创建的Dialog,或者使用Application context开始一个Activity,系统会抛出一个异常,让你的application崩溃,非常强的告诉你某些地方出了问题。
一个并不明显的问题是填充布局(inflating layout)。如果你已经读过了我(原文作者)的上一篇文章Layout inflation,你就已经知道它可能是一个非常神秘过程,伴随一些隐藏的行为。使用正确的context关系到其中的一个行为。当你使用Application context来inflate一个布局的时候,框架并不会报错,并返回一个使用系统默认的主题创建一个完美的view给你,而没有考虑你的applicaiton自定义的theme和style。这是因为Acitivity是唯一的绑定了在manifast文件种定义主题的Context。其他的Context实例将会使用系统默认的主题来inflater你的view。导致显示的结果并不是你所希望的。
规则的路口
可能有些读者已经得出两个规则互相矛盾的结论。可能有些情况下,在某些Application的设计中,我们可能既必须长期保存一个的引用,并且为了完成与UI相关的工作又必须保存一个Activity。如果出现这种情况,我将会强烈建议你重新考虑你的设计,它将是一个很好的“反框架”教材。
经验法则
绝大多数情况下,使用在你的所工作的组建内部能够直接获取的Context。只要这个引用没有超过这个组建的生命周期,你可以安全的保存这个引用。一旦你要保存一个context的引用,它超过了你的Activity或者Service的生命周期范围,甚至是暂时的,你就需要转换你的引用为Application context。
说到Activity与Context关系,少不了Application与二者的关系,上图可以明确看到Context是Application的父类,那么对于参数传递,需要传递Context对象的时候,
我们是传activitythis还是thisgetApplicationContext()呢?
activity的生命周期肯定没有application长,所以为了防止内存泄露:
只要application可以满足的就传thisgetApplicationContext()。比如用来ShowToast、获取LayoutInflater对象、获取数据库对象、获取SharedPreferences对象、发广播contextsendBroadcast等,都可以传thisgetApplicationContext()。
application不能满足的必须传activity。比如showDialog、activity之间跳转等
相信很多人都知道是这样计算的,那到底为什么是这样呢?
源码分析基于Android28源码
什么是Context呢?可以理解为上下文、运行环境,当需要获取资源、系统服务以及启动Activity、Service用到,也可以通过它跟系统交互。
通过以下继承关系可以看出,Activity是继承ContextWrapper
ContextWrapper内部有一个Context类型的成员变量mBase
mBase是通过attachBaseContext()方法赋值
是创建Activity的关键,
主要工作
(1)createBaseContextForActivity()内部实例化ContextImpl 对象;
(2)mInstrumentationnewActivity()内部通过反射实例化Activity对象;
(3)activityattach()内部会调用attachBaseContext()方法给mBase对象赋值;
通过以下继承关系可以看出,Application是继承ContextWrappe
是创建Application的关键,
主要工作:
(1)ContextImplcreateAppContext()实例化ContextImpl ;
(2)mActivityThreadmInstrumentationnewApplication(),内部通过反射实例化Application,并把appContext传递过去,通过attach()方法给mBase赋值;
跟Activity类似就不再做分析。
经过分析发现:
1每个Activity,Service,Application都有一个ContextImpl 类型的成员变量mBase,ContextImpl是Context的实现类。
2细心的读者可能发现,Activity,Service,Application都是继承Context,其实他们本身是一个Context,也都实现了Context的抽象方法,
那么一个Activity是否就拥有两个Context呢
是不是
这样计算比较合适呢?
下面看下Context中常用的三个方法,
ContextImpl继承Context,并实现了这三个方法,
Activity间接继承Context,主要是在ContextWrapper实现了以上三个方法,从源码中可以看出,最终还是调用了ContextImpl的实现。
下图可以看出这几个的关系,ContextWrapper顾名思义就是Context的包装类(有ContextImpl的成员变量),并且实现了Context,这是一种装饰者设计模式。当在Activity中调用getAsset()时,其实最终是调用mBase的getAsset()。
Activity间接继承了Context,是为了拥有跟ContextImpl一样的功能,但真正起作用的是mBase这个成员变量,所以一个Activity其实就只有一个Context起作用,那就是ContextImpl类型的mBase。
这种计算方法应该是没有问题呢。
或许有人有这样的疑问,一个应用不是只有一个Application吗,为什么计算公式是加上Application个数?单进程应用来说,一个应用确实只有一个Application,而多进程应用,那么一个应用就有多个Application,所以应该说一个应用有一个或多个Application,一个进程有一个Application。
另外其他关于Context的常见面试题
1Activity的this跟getBaseContext区别。
前者是Activity对象本身,后者是通过attachBaseContext传入的ContextImpl对象mBase,两者功能是一样的,通过this最终还是会调到mBase对象中。
2getApplication和geApplicationContext区别。
两者都是返回Application对象,前者是Activity和Service里面的方法,后者是Context中定义的方法。
3应用组件的构造,onCreate、attachBaseContext的执行顺序。
先是组件构造化,接着attachBaseContext,传入ContextImpl对象,最后是onCreate方法。
4谈谈你对Context的理解
先是Context的作用,然后是有几种Context,Application、Service、Activity的Context有什么区别以及继承关系,
最后是mBase变量是如何实例化的。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
很多初入Android开发的网友向我们问到Context有什么作用,很多地方都用到它,这里Android123给这些新入门的网友做个简单的解释:
Context字面意思上下文,位于framework package的androidcontentContext中,其实该类为LONG型,类似Win32中的Handle句柄,很多方法需要通过Context才能识别调用者的实例,比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用this时就会报错,所以我们可能使用ActivityNamethis来解决,主要原因是因为实现Context的类主要有Android特有的几个模型,Activity、Service以及BroadcastReceiver。
常规需要Context实例的方法主要有各种Service实现的类,比如说SensorManager在实例化时需要getSystemService(String)方法就必须由Context的实例执行,还有一些私有的文件系统I/O比如说openFileInput以及常用的Toast的makeText方法。
首先看继承关系:
可以看到Activity继承于ContextThemeWrapper,ContextThemeWrapper继承于ContextWrapper,ContextWrapper继承于Context。也就是说,Context是Activity的父类。
相关延伸:
说到Activity与Context关系,少不了Application与二者的关系,上图可以明确看到Context是Application的父类,那么对于参数传递,需要传递Context对象的时候,
我们是传activitythis还是thisgetApplicationContext()呢?
activity的生命周期肯定没有application长,所以为了防止内存泄露:
只要application可以满足的就传thisgetApplicationContext()。比如用来ShowToast、获取LayoutInflater对象、获取数据库对象、获取SharedPreferences对象、发广播contextsendBroadcast等,都可以传thisgetApplicationContext()。
application不能满足的必须传activity。比如showDialog、activity之间跳转等。
我是这么认为的!下面是自己的理解,建议参考下,不对的地方还请指证。
一、getContext()getContentResolver()返回的当然是ContentResolver对象了,ContentResolver负责获取ContentProvider提供的数据
二、关于它在api的哪个包中,请看下面(首先,如果查询getContentResolver()可以参考Context):
1、getContext()就是获得一个上下文对象(Context),一般在四大组件中会获取上下文对象。
2、在Activity,没必要获取Context了,因为他本身就是,所以可以直接调用getContentResolver()
3、在Service中和Activity相同
4、在ContentProvider中,就需要先调用getContext()获取到Context,然后调用getContentResolver()获得ContentResolver对象,也就是,getContext()getContentResolver()
以上就是关于Android中,Context,什么是Context全部的内容,包括:Android中,Context,什么是Context、什么时候用Application的Context,什么时候用Activity的Context、源码分析->一个应用到底有几个Context等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)