你需要知道的Android上下文Context

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_435559203/article/details/54912278

Context对于Android开发者来说肯定不陌生,在我们跳转新的Activity、弹出Toast,创建View等行为时都需要用到Context,可见Context每天都伴随着我们,但是Context究竟是什么意思呢?Context从中文翻译上是上下文、环境、场景。我个人更倾向用场景来理解Context,例如创建一个View,我们在构造方法传一个Context的,而传进去的Context则代表着这个View所处的一个布局的父类场景中,相当于该View(子View)与父View进行了一个场景关联。

源码的Context

这里写图片描述
源码对Context的解析是Context是一个关于应用程序环境全局信息的接口。Context提供了一个抽象类的实现Android系统。它允许访问特定于应用程序的资源和类,包括应用程序级操作,如启动Activity,发广播,接收Intent等。

下面我们来看一下Context的继承结构:
这里写图片描述

在源码我们可以看到Context是一个抽象类,从上图我们可以看出实现Context抽象类有ContextImpl与ContextWrapper,从类名我们可以理解到分别是实现类与包装类。我们可以在图中看到我们很熟悉的Activity、Service、Application,他们都是ContextWrapper的子类,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,所以在Activity、Service、Application类中的attch()中都有attachBaseContext()方法,调用该方法都会被转向其所包含的真正的Context对象。而ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。
这里写图片描述

Context数量

理解Context的作用,可以通过Context的个数来进行理解。从上图的继承关系可以看出,Context数量 = Activity数量 + Service数量 + Application数量。但是有人会有这个疑问,那么我们的四大组件,还有Broadcast Receiver和Content Provide并不是Context的子类,使用他们的时候用到的Context是从其他地方传过去的,所以是不算进Context的总数中,从上面的种种可以看出Context类在Android系统中地位是多么崇高。

Context的功能

Context实在太多了,启动Activity、弹出Toast、启动Service、创建View等等都用到Context。
TextView textView = new TextView(context);

context.getSharedPreferences(name, mode);

LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

context().startActivity(intent);

context().startService(intent);

context().sendBroadcast(intent);

···

Context应用场景

用到Context的地方是很多,但是Context也有一定的规则。参考Context,What Context?

这里写图片描述

大家注意看到有一些NO上添加了一些数字,其实这些从一定程度上来说是YES,但是为什么说是NO呢?下面一个一个解释:

1.应用程序可以从这里开始一个Activity,但它需要一个新的Task被创建。这可能适合特定的用例,但是一般情况不推荐。
2.这是合法的,但inflation将使用系统默认主题您正在运行的系统,而不是在应用程序中定义的。
3.允许如果Receiver是null,用于获取的当前值的广播,在安卓4.2及以上。

另外上图我选Dialog来说一下。记得我刚开发时犯的错。就是在创建Dialog的时候将getApplication()传进去,因为封装Dialog的方法有tryCatch,导致自己没及时发现,发布到线上后在统计后台中发现报错,这个是我对这Context的时候印象最深刻的。

当时以为Activity的Context与Application的Context是通用的,就直接用了,但XXXActivity和getApplicationContext返回的肯定不是一个对象,一个是当前Activity的实例,一个是项目的Application的实例。既然区别这么明显,那么各自的使用场景肯定不同,乱使用可能会带来一些问题。
那么令有一个问题,我们如果正确获取Context?

获取Context

通常我们获取Context对象有下面的方式:
1.View.getContext(),返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
2.Activity.getApplicationContext,获取当前Activity所在进程的全局Context对象。
3.ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4.Activity.this 返回当前的Activity实例,如果场景在Activity进行,如UI控件需要使用Activity作为Context对象。

另外在输出getApplication时候,IDE会提示getApplicationContext 、getApplication,那么它们究竟有什么不同呢?
这里写图片描述
我们通过上面的代码,打印得出两者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是Application本身的实例。那么问题来了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。

Context引起的内存泄露

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在使用该封装类时,为了方便会直接使用Activity.this传进该类中,问题来了,该类的sInstance是一个静态强引用,然而内部引用了一个Activity的Context,换一句话说,项目一直活着,sInstance就会一直劫持该Context,内存无法被回收,因此导致内存泄露。
那么我们解决方法就是:

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(context.getApplicationContext());
        }

        return sInstance;
    }

    private Context mContext;

    private CustomManager(Context context) {
        mContext = context;
    }
}

将封装类的context.getApplicationContext,让ApplicationContext指向这个静态sInstance单例,sInstance与ApplicationContext的生命周期一直,这样就可以解决了内存泄露的问题,Activity也能finish掉后正常让垃圾回收机制回收。

正确使用Context

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,那么使用Context的正确姿势是:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。


最后总结下,Context的分析基本就如上述内容,Context伴随着我们每天的开发,我们需要的是在使用Context的时候,注意使用Activity.Context是否合适,会不会造成内存泄露的可能。

猜你喜欢

转载自blog.csdn.net/qq_435559203/article/details/54912278