Android Context 你不知道的那些事

一、Context类图:


上面就是有关Context的几个关键类的组织结构,一目了然,不多作解释,接下来看看Android源码中的类注释。

二、类注释:

Context:类注释解释的很清楚,Context应用程序环境的全局信息的接口。这是一个抽象类,它的实现是由Android系统提供的。它允许访问特定应用程序的资源和类,以及对应用级操作的追加调用,例如启动活动、广播和接收意图等。

ContextImpl:上下文API的通用实现,它为活动和其他应用程序组件提供基本上下文对象。

ContextWrapper代理上下文的实现,简单地将所有的调用委托给另一个上下文。可以在不更改原始上下文的情况下对其进行子类修改。

ContextThemeWrapper一个上下文包装器,允许修改或替换包装上下文的主题。ps:AndroidManifest.xml中主题的属性功能就是这个类来实现的)

Application维护全局应用程序状态的基类。可以通过创建一个子类来提供自己的实现,并在AndroidManifest.xml中指定这个子类的完全限定名称为“android:name”属性的值这个应用程序类,或者应用程序类的子类,在创建应用程序/包的过程,在任何其他类实例化之前先被实例化。(ps:具体的获取全局Context后面再讲)

ActivityService相信大家已经非常熟悉了,在此就不多作解释了,感兴趣的朋友自己去看源码注释。

三、Context数量:

一个应用程序有几个Context呢?从上面的关系图我们得知Context的具体实现子类就是:ApplicationActivityService,所以一个Android应用程序的Context种类= 3种,Context数量=Application数量(1个)+Activity数量 + Service数量。

四、Context能干什么:

Context能干什么,有哪些功能用到了Context这个确实有点多例如,弹出Toast显示dialoglayout inflation启动Activity、启动Service、发送广播、操作数据库、加载资源等等都需要用到Context

上面是Context的常见的几种应用场景,我简单解释一下:

Show dialog出于安全原因的考虑,Android是不允许Dialog凭空出现的,Dialog必须在一个Activity上面弹出(除非是System Alert类型的Dialog,所以只能用ActivityContext

Start activityAndroid系统中,因为Activity涉及到Activity栈,所以一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。因此在这种场景下,我们最好使用Activity类型的Context

Layout inflationlayout inflate时使用application和service是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。

总结一下只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了要注意防止内存泄漏,那内存泄漏是怎么发生的呢,如何才能避免呢,请接着看

五、Context引起的内存泄露

Context不能随便乱用,用的不好有可能会引起内存泄露的问题,内存泄漏一般发生的原因是错误的引用持有,下面就示例两种错误的引用持有方式。

1.错误的单例模式

public class Singleton {

    private static Singleton instance;
    private Context mContext;
 
    private Singleton(Context context) {
        this.mContext = context;
    } 

    public static Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }

}

这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity AgetInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

2.View持有Activity引用

public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @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);
    }
}

有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的thisMainActivitymContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

六、正确使用Context:

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而ApplicationContext对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

1.ApplicationContext能搞定的情况下,并且生命周期长的对象,优先使用ApplicationContext

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

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

4.因为static修饰的对象常驻内存,生命周期比一般的对象要长,所以在Context中使用static时多想一下是否是必须的,如果可以,尽量不要用。

七、获取全局Context:

一方面,为了防止内存泄漏,我们在使用Context时,优先使用Application的Context另一方面,随着应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离Activityservice类,但此时你又恰恰需要使用Context所以就需要在任何地方都能很方便的获取到ApplicationContext

Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行初始化。而我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如说全局Context。因为Application全局只有一个,它本身就已经是单例了,无需再用单例模式去为它做多重实例保护

public class MyApplication extends Application {

    private static Context instance;

    @Override
    public void onCreate() 
    {
        instance = getApplicationContext();
    }
    
    public static Context getContext()
    {
        return instance;
    }
    
}

接下来我们需要告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的Application类。这一步也很简单,在AndroidManifest.xml文件的<application>标签下进行指定就可以了,代码如下所示:

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" 
        android:theme="@style/AppTheme" 
        android:name="com.dapeng.MyApplication">
</application>

这样我们就已经实现了一种全局获取Context的机制,之后不管你想在项目的任何地方使用Context,只需要调用一下MyApplication.getContext()就可以了。


本文参考阅读:
http://blog.csdn.net/lmj623565791/article/details/40481055/
http://blog.csdn.net/yanbober/article/details/45967639
http://blog.csdn.net/guolin_blog/article/details/47028975
http://www.jianshu.com/p/94e0f9ab3f1d#

猜你喜欢

转载自blog.csdn.net/u012604745/article/details/73105057