Context单例模式的一枝独秀

来源:互联网 发布:华威医药 知乎 编辑:程序博客网 时间:2024/05/17 08:46

Context单例模式的一枝独秀
在公司有着很多的空闲时间,本以为能在工作中能学到很多的实践知识,但是感觉怎么TM的和我想象不太一样呢?初来公司咋到,由于写代码的风格和质量不能达到一定的要求,索性自己就在这些时间内学习android的源码和设计模式的分析。我觉得挺有意思的。可以通过剖析源码从而增加了自己的知识面。坚持把自己掌握的点点滴滴以笔记的形式积累起来。以此来见证自己的成长之路。
唠叨了一下,我们直接进入正题。众所周知,单利模式应用范围几乎在android的开发中最广泛的了,然而单利模式的写法大致分为以下类,分别是:懒汉模式,DCL,饿汉模式,静态内部类单例模式,枚举模式,利用容器实现的单例模式。
既然提到了单例模式,那它在什么情况下才能应用呢?答案就是在当创建一个对象消耗的资源过多时,比如IO和数据库的访问。这个情况下为了避免创建过多的对象而造成内存空间的浪费。
这下明白了原理,就可以更好的理解它了,我们大致介绍一下上述的各种单例模式。
1、饿汉模式

public class Singleton{    private static final Singleton instance = new Singleton();    private Singleton(){};//私有构造函数,避免外界可以通过new 获取对象    public static Singleton getInstance(){        return instance;    }}

通过代码分析。我们不难看出通过私有构造函数,避免外界可以通过new 获取对象实例。只能通过静态方法返回一个静态的对象,这种方法缺点就是不能实现同步。
2、懒汉模式

public class Singleton{    private static final Singleton instance = null;    private Singleton(){};//私有构造函数,避免外界可以通过new 获取对象    public static synchronized Singleton getInstance(){        if(instance == null){            instance = new Singleton();        }        return instance;    }}

为了避免多线程的访问对象出现抢占资源的情况,所以就出现了懒汉模式,通过加上锁的方法来实现资源的同步。缺点:每次同步都会getInstance(),从而造成了不必要的开销。
3、DCL

public class Singleton{    private static final Singleton instance = null;    private Singleton(){};//私有构造函数,避免外界可以通过new 获取对象    public static Singleton getInstance(){        if(instance == null){            synchronized(Singleton.class);            if(instance == null){                instance = new Singleton();            }        }        return instance;    }}

这种方法是懒汉模式的改进版,利用双重判断机制虽然避免了懒汉模式每次同步都会getInstance(),从而造成了不必要的开销。但是也存在自己的缺点:如果用在高并发的情况下会有很小的偶然几率DCL就会失效。景观如此,这种模式还是使用最广泛的。
4、静态内部类单例模式

public class Singleton{    private Singleton(){};//私有构造函数,避免外界可以通过new 获取对象    public static Singleton getInstance(){      return SingletonHolder.instance;    }    /**    *静态代码块    */    public static class SingletonHolder{      private static final Singleton instance = new Singleton();    }}

这种方法为了避免DCL可能存在的失效,这种方法在调用Singleton 的时候虚拟机才会去加载SingletonHolder类,从而返回Singleton 的对象instance ,同时也确保了线程的安全。这种方法很值得推荐使用。
5、枚举模式

public enum Singleton{    INSTANCE;}

我们知道枚举其实也是java类,它也具有线程安全,同时也是为一个不会被通过反射手段获取对象的方法。
6、利用容器实现的单例模式

public class SingletonManager{    private static Map<String,Object> map = new HashMap<String,Object>;    private Singleton(){};//私有构造函数,避免外界可以通过new 获取对象    public static void registMap(String key,Object instance){    if(!map.containsKey(key)){        map.add(key);    }      }}

这种方法是通过在一个哈希表中由于键值不能重复,通过插入哈希表这一种容器来添加一个实例对象。

说了这么多的单例,那么Context又是怎么回事呢?接下来慢慢讲解。
通过分析源码可以知道,一个android 应用的Context的个数可以用以下公式表达,
Context个数 = Activity的个数 + Service的个数 +1;
其中这个1很关键,他也是有系统单例模式生成的Application.
Activity/Service - 继承于ContextWrapper,它实现了与context同样API,但是代理这些方法调用到内部隐藏的Context实例,即我们所知道的基础context。任何时候当系统创建一个新的Activity或者Service实例的时候,它也创建一个新的ContextImpl实例来做所有的繁重的工作。每一个Activity和Service以及其对应的基础context,对每个实例来说都是唯一的。关于Activity和Service我就不做多的介绍,自己分析源码通过继承关系图可以了解很多。
1、糟糕的代码示范

public class Singleton{    private static final Singleton instance = null;    private Context mContext;    private Singleton(Context context){        this.mContext = context;    };//私有构造函数,避免外界可以通过new 获取对象    public static Singleton getInstance(){        if(instance == null){            synchronized(Singleton.class);            if(instance == null){                instance = new Singleton(context);            }        }        return instance;    }}

这种方法为什么不好呢?因为我们不知道这个context是从哪里来的,并且如果保存一个最终指向的是Activity或者Servece的引用是并不安全的。这是一个问题,是因为一个单例在类的内部维持一个唯一的静态引用,这意味着我们的对象,以及所有其他它所引用的对象,将永远不能被垃圾回收。假如这个Context是一个Activity,我们将保存与这个Activity相关的所有的view以及其他大的对象,从而造成内存泄漏。

2、正确的代码演示

public class Singleton{    private static final Singleton instance = null;    private Context mContext;    private Singleton(Context context){        this.mContext = context;    };//私有构造函数,避免外界可以通过new 获取对象    public static Singleton getInstance(){        if(instance == null){            synchronized(Singleton.class);            if(instance == null){                instance = new Singleton(context.getApplicationContext());            }        }        return instance;    }}

这两种方法区别很简单。区别在于
instance = new Singleton(context.getApplicationContext); 。
我们获取的ApplicationContext()。这样就通过Application的上下文获得一个全局的唯一对象。
绝大多数情况下,根据实际情况获得不同的Context.使用在你的所工作的组建内部能够直接获取的Context。只要这个引用没有超过这个组建的生命周期,你可以安全的保存这个引用。一旦你要保存一个context的引用,它超过了你的Activity或者Service的生命周期范围,甚至是暂时的,你就需要转换你的引用为Application context。
每天坚持这样下去,肯定会成功的。

0 0
原创粉丝点击