俗话说得好,Context都没弄明白,怎么做Android开发。
谈谈你对Activity的Context的认识?⭐⭐⭐⭐⭐
Application和Activity,Context的区别?⭐⭐⭐⭐⭐
getApplication()和getApplicationContext()的区别?⭐⭐⭐⭐
context错误用法有哪些?⭐⭐⭐
如何正确使用Context?⭐⭐⭐⭐
目录
1、什么是Context,能干什么?
2、一个应用程序有几个Context?
3、如何获取Context
4、Context的错误用法和正确使用方法
4.1 错误使用静态方法
4.2 错误使用静态View对象
4.3 如何正确使用Context
1、 什么是Context,能干什么?
Context直译过来是“语境”,“上下文”,“环境”的意思。以前在学习嵌入式Linux的时候,也经常说到进程上下文,中断上下文。而在“安卓上下文”中,我们需要先明白安卓的应用模型是基于组件的应用设计模式,比如Activity和Service这些组件在运行的时候,都需要一个完整的Android工程环境。那么在代码里,这个“环境”由谁提供?那自然就是Context类。 作为Android代码里出镜率最高的Context,除了负责四大组件的交互场景外,还有很多的场景都需要用到Context,如:
获取系统属性,系统资源(color、string、drawable等)场景;
数据存储场景,如使用文件,SharedPreference,数据库的场景;
我们来看看源码里Context类系列:
![](https://i-blog.csdnimg.cn/blog_migrate/45a9e585a9043a1075b6b1815c6cd0eb.png)
Context如下代码,本身是一个纯abstract类,那么自然有对应的实现子类:ContextImpl和ContextWrapper,其中ContextImpl是Context真正的实现类,ContextWrapper类则和其名字一样,只是一个封装类,并沒有真正的实现,真正的实现是其包含了一个mBase变量,是通过attachBaseContext() 方法来设置的,本质上是 ContextImpl对象。
public abstract class Context {
/**
* File creation mode: the default mode, where the created file can only
* be accessed by the calling application (or all applications sharing the
* same user ID).
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
public static final int MODE_PRIVATE = 0x0000;
...
}
接着,ContextThemeWrapper又是继承于ContextWrapper,正如其名,ContextThemeWrapper相对于ContextWrapper多包含了与主题(Theme)相关的接口,这个主题就是AndroidManitest.xml里面application元素或者Activity元素通过android:theme指定的主题。如下面的theme,虽然在application元素里指定,但只在Activity界面才会使用到。
<application
android:name=".MyApplication"
...
android:theme="@style/Theme.XrTest">
正因为只有Activity需要指定主题,而Service和Application是不需要使用主题的。因此才有Actvity继承ContextThemeWrapper,而Service和Application直接继承ContextWrapper。Application、Activity、Service通过attach()调用父类ContextWrapper的attachBaseContext(), 从而设置父类成员变量 mBase 为 ContextImpl 对象。
2、一个应用程序有几个Context?
这个问题现在就很好回答了,一个Application的基础还是有安卓四大组件构成的,既:
Application = Activity + Service + Broadcast Receiver + Content Provider
从第1节的图,我们知道Application和Activity和Service都继承与Context,而Broadcast Receiver和Content Provider虽然也需要context,但不是继承于Context,而是由外部传进入。因此一个应用的context个数就由其包含了多少个Activity和Service决定,并且最后再加上自己本身持有的context,即
应用Context数量 = Activity数 + Service数 + 1;
3、如何获取Context
既然绝大多数场景都需要在安卓完整的工程环境实现,那么我们如何能获取到context呢?主要有以下方法:
Activity.this:很多情况下要求传入context,此时发现我们可以直接传入activity本身,这是因为activity只是继承与context的;
getApplication()和getApplicationContext():这个方法也很常见,因为应用本省就是一个context,所以获取应用实例即可。不过说明一下这两个方法返回的结果确实是同样的结果,但getApplication()是获取应用实例,只有在Activity和Service中可以调用,而getApplicationContext()含义是为了获取的是整个应用程序的工程环境,可以在Broadcast Receiver和Content Provider中调用;
view.getContext:每个view都需要在安卓的完整工程环境下才能正常显示,操作,因此每个view本身都持有context对象,这个context对象一般是该view所在的Activity实例;
4、Context的错误用法和正确使用方法
4.1 错误使用静态方法
下面是一个非线程安全的单例模式,instance作为静态对象,生命周期会高于普通对象。因此如果传入某个应用的实例,会导致XuruiEnvelopeImpl一直持有XrActivity对象,当XrActivity被销毁时,因为它还有一个引用被别人持有,因此无法被垃圾回收机制回收,造成了内存泄露。
public class XuruiEnvelopeImpl {
private static XuruiEnvelopeImpl instance;
private Context mContext;
private XuruiEnvelopeImpl(Context context) {
this.mContext = context;
}
public static XuruiEnvelopeImpl getInstance(Context context){
if(mEnvelopeImpl == null){
mEnvelopeImpl = new XuruiEnvelopeImpl(context);
}
return mEnvelopeImpl;
}
}
//使用
XuruiEnvelopeImpl.getInstance(XrActivity.this);
4.2 错误使用静态View对象
public class MainActivity extends Activity {
private static Drawable mDrawable; //1
@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); //2
}
}
注释1定义了一个静态的mDrawable,在注释2传入非静态ImageView对象里,第3节有说过,一个view对象都会持有所在的activity的引用,因此静态的mDrawable会一直持有非静态ImageView对象所在的activity引用,该引用跟着静态的mDrawable会一直保存在内存,因此导致activity销毁时,垃圾回收机制发现该activity还有一个引用被别人持有,将无法进行垃圾回收,造成内存泄露。
4.3 如何正确使用Context
因为Application和activity都有context对象,如果可以使用Application的context则优先使用;
注意别让生命周期可能长于actvity的对象持有activity的引用;
尽量避免在activity里使用非静态内部类,这一点在Handler那一节就着重说明了,因为非静态内部类会持有外部类的引用。推荐使用静态内部类,并将外部类的引用改为弱引用。
PS:什么情况必须用Activity的Context呢?
答:启动新的Activity或者弹出一个Dialog,只能用Activity的Context,不能用Application的Context,因为启动Activity需要任务栈,而非Activity的Context是没有的。同时,Dialog必须基于Activity上弹出。