单例模式在android的应用_android 单例模式 应用 - CSDN
  • 单例模式应用最广的设计模式之一,在应用这种模式的时候,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个全局对象,这样有利于协调系统的整体行为。如一个应用中,应该只有ImageLoader...

    单例模式是应用最广的设计模式之一,在应用这种模式的时候,单例对象的类必须保证只有一个实例存在。许多时候,整个系统只需要拥有一个全局对象,这样有利于协调系统的整体行为。如一个应用中,应该只有ImageLoader实例,这个ImageLoader实例中又包含网络请求、缓存系统、线程池等,很耗资源,因此没有理由让他构造多个实例。这种不能自由构造对象的情况就是使用单例模式的场景。在Android系统中存在很多这种场景,比如最常用的context.getSystemService(),BluetoothAdapter.getDefaultAdapter()等等都是使用的单例模式。下面就列出几种单例模式的构建方式以及各种方式的优缺点。

    1.懒汉模式

    懒汉模式是申明一个静态变量,并且在用户第一次调用getInstance时进行初始化,懒汉模式实现如下:

    public class Singleton {
    	private static Singleton sInstance;
    	private Singleton(){
    	}
    	public static synchronized Singleton getInstance(){
    		if(sInstance == null){
    			sInstance = new Singleton();
    		}
    		return sIntance;
    	}
    }

    
    

    getIntance()方法中添加了synchronized关键字,也就是getInstance是一个同步方法,这就是上面所说的在多线程情况下保证单例对象唯一性的手段。但是这样存在一个问题,即使sInstance已经被初始化,每次调用getInstance都会进行同步,这样会消耗不必要的资源,这也是懒汉单例模式存在的最大问题。懒汉模式的最大优点是单例只有在使用的时候才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时初始化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。一般不建议使用。

    2.Double Check Lock(DCL)实现单例

    DCL方式实现单例模式的优点是既能够在需要的时候才初始化单例又能保证线程安全,且单例对象的初始化后调用getInstance不进行同步锁。实现如下:

    public class Singleton {
        private static Singleton sInstance;
        private Singleton(){}
        public static Singleton getInstance(){
            if(sInstance == null){
                  synchronized(Singleton.class){
    			if(sInstance == null){
    				sInstance = new Singleton();
    			}
    		}
            }
    	return sInstance;
        }
    }

    本程序的亮点在于getInstance()方法中对sIntance进行了两次非空判断:第一层主要是为了避免不必要的同步,第二层的判断则是为了在null的情况下创建实例。假设线程A执行到sInstance = new Singleton()的语句,这看起来是原子操作,但并不是,这句代码会被编译成多条汇编指令:给Singleton的实例分配内存,调用Singleton的构造函数,初始化成员字段,将sInstance对象指向分配的内存空间(此时的sInstance已经不是null了)。但是由于Java编译器允许处理器乱序执行,所以上述三个步骤的执行顺序无法得到保证。这就会导致DCL失效,而且这种难以跟踪重现的错误会隐藏很久。在JDK1.5后,只需要将sInstance的定义改成private volatile static Singleton sInstance = null就可以保证sInstance对象每次都是从内存中读取,就可以使用DCL的写法来完成单例模式。当然,volatile或多或少会影响到性能,但考虑到程序的正确性,还是值得的。DCL的优点是资源利用率高,第一次执行getInstance时单例才会被实例化,效率高。缺点是第一次加载时反应稍慢,也由于Java内存模型的原因会偶尔失败。在高并发环境下也有一定的缺陷,虽然发生概率较小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例,并且在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6的情况下使用,否则这种方式一定能够满足要求。

    3.静态内部类单例模式

    DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是它还是在某些情况下出现失效的问题。这个问题被才会被称为DCL双锁失效。静态内部类实现代码如下:

    public class Singleton{
       	 private Singleton(){}
       	 public static Singleton getInstance(){
           		 return SingletonHolder.sInstance; 
        	}
    
       	private static class SingletonHolder {
    	private static final Singleton sInstance = new Singleton();
    	}
    
    }
    当第一次加载的时候Singleton类时,并不会初始化sInstance,只有第一次在调用Singleton的getS方法时才会导致sIn被初始化。因此第一次调用get方法会导致虚拟机加载SingletonHolder类,这种方法不但保证能够=线程安全们也能够,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,。

    4.枚举单例模式档
    Java1.5版本起,单元素枚举实现单例模式成为最佳的方法,实现代码如下

    class Resource{
    }
    
    public enum SomeThing {
        INSTANCE;
        private Resource instance;
        SomeThing() {
            instance = new Resource();
        }
        public Resource getInstance() {
            return instance;
        }
    }

    上面的类Resource是我们要应用单例模式的资源,具体可以表现为网络连接,数据库连接,线程池等等。
    获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:
    首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
    也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
    可以看到,枚举实现单例还是比较简单的,除此之外我们再来看一下Enum这个类的声明:

    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable


    可以看到,枚举也提供了序列化机制。某些情况,比如我们要通过网络传输一个数据库连接的句柄,会提供很多帮助。

    在上述的几种单例模式中,存在一种情况它们会出现重复创建对象的情况,那就是反序列化。通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效的获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的私有构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve,这个方法可以让开发人员控制对象的反序列化。上述几个示例中如果要杜绝对象下被反序列化中重新生成对象,那么需要加入如下方法:

    private Object readResolve() throws ObjectStreamException{
        return sInstance;
    }

    也就是在readResolve()中将sInstance对象返回,而不是默认的重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即使反序列化也不会生成新的实例。

    5.使用容器实现单例模式

    看看这种实现方式:

    public class SingletonManager {
    private static Map<String,Object> objMap = new HashMap<String,Object>();
    private SingletonManager();
    public static viud registerServuce(String key,Object instance){
         if(!objMap.containsKey(key){
             objMao.put(key,instance);
    }
    } 
    
    public static Object getService(String key){
        return objMap.get(key);
    }
    }
    
    
    
    
    

    
    

    在程序初始化的时候,将多种单例类型注入到一个统一的管理类中,在使用时根据Key获取对象对应类型的独享,这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体的实现,降低了耦合度。

    总结:不管以哪种方式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在获取这个的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。选择哪种方式取决于项目本身,如是否是复杂的并发环境、JDK版本过低、单例对象的资源消耗等。

    
    

    展开全文
  • 单例模式,故名思议,是指一个类中通过设置静态使得其仅创造一个唯一的实例。这样设置的目的是满足开发者的希望??这个类只需要被实例化创建一次,同时因为其为静态的缘故,加载的速度也应该快于正常实例化一个类的...
    1.设计模式实例-单例模式
    

    单例模式,故名思议,是指在一个类中通过设置静态使得其仅创造一个唯一的实例。这样设置的目的是满足开发者的希望??这个类只需要被实例化创建一次,同时因为其为静态的缘故,加载的速度也应该快于正常实例化一个类的速度(理论上)。

    在Android开发中,当我们需要创建一个Fragment的时候常常会用到这样的模式,没有代码的学习是虚无的,接下来亮代码学习:

    public class SelectFrame extends Fragment {
    
      private final static String selectFrameKey = "SFKey";
      private static SelectFrame mSelectFrame;
      private ArrayList<String> frameList;
      public static SelectFrame getInstance(ArrayList<String> frameList){
        if (mSelectFrame == null) {
          mSelectFrame = new SelectFrame();
        }
        Bundle bundle = new Bundle();
        bundle.putStringArrayList(selectFrameKey, frameList);
        mSelectFrame.setArguments(bundle);
        return mSelectFrame;
      }
    ......


    这是我在一个Fragment类里面定义的其中一部分,首先必须要定义一个自身的静态mSelectFrame。然后通过一个静态方法 getInstance()来实例化这个mSelectFrame,很明显,这里通过判断其是否为空的方式,使得每一次我们使用getInstance的时候都返回的是同一个对象mSelectFrame。

    getInstance方法中有时我们也会放入一些我们需要传递的参数,比如我这个方法中放入了一个List对象,然后直接在里面用Bundle装载这个List对象,原本我们可能是在外部来做这些操作的,然而现在却直接通过这个方法将数据传入的操作集成到了这个Fragment中,即减少了外部Activity的逻辑代码,也使得这个Fragment在复用的时候操作更方便。(再通过setArguments的方法保存数据)。

    接下来看我们的onCreateView方法:

      @Override
      public View onCreateView(LayoutInflater inflater,
          @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.select_frame_fragment, container, false);
        Bundle bundle = mSelectFrame.getArguments();
        frameList = bundle.getStringArrayList(selectFrameKey);
    }
    这里我们就将从前面getInstance中setArguments保存下来的数据通过Fragment的getArguments方法重新将数据读取出来。这样就完成了我们的目的。

    接下来就是在Activity中创建我们这个Fragment对象了:

        selectFrameFrag = SelectFrame.getInstance(nameFrameThumbnail);
        fm.beginTransaction().add(R.id.fragment_frame_container, selectFrameFrag).commit();
    (fm是FragmentManager..)

    这样就OK了。

    2.单例模式的分类

    单例模式有两种创建方式,分别为饿汉式和懒汉式。

    饿汉式,故名思议,很饿,在一开始就直接创建了这个对象。

    懒汉式,顾名思义,很懒,只有在你要调用它的时候,通过自己写的方法里面来对他实例化。刚刚上面那个例子就是懒汉式的。

    也许这么说你会有一点不明白(其实应该没有人不明白吧= =),然后其实只要看代码的这个地方:

      private static SelectFrame mSelectFrame;

    上面这个代码,这里一开始定义自身的静态时,没有实例化它,那么就是个懒汉.然后只有在我调用getInstance方法的时候,才在里面对他进行实例化(new SelectFrame()).

    然后我们再看看饿汉:


      private static SelectFrame mSelectFrame = new SelectFrame();

    在定义的时候就已经实例化了.

    3.单例模式的线程安全问题

    没错,单例模式毕竟就是像上面讲的这么简单。但需要注意的是,单例模式中的饿汉式是线程安全的,而懒汉式是线程不安全的,我们一般会再懒汉式的getInstance方法中通过synchronized上锁。类似于这样:

        if (mSelectFrame == null) {
          synchronized (SelectFrame.class) {
            if (mSelectFrame == null) {
              mSelectFrame = new SelectFrame();
            }
          }
        }

    有人说为什么要用两个if XX == null。这里是因为提高效率,只有一个也是可以的,但效率上大大减低,因为不是每一次调用的时候我们要判断他是否同步,应该是如果判断当前为null的话,我们就直接不让他进入了,不需要判断是否同步。只有满足第一个条件的时候,我们才需要用锁来判断它是否线程安全。(这里的意思也是说明if一条语句的判断速度当然比synchronized (SelectFrame.class)快。。)


    另外,在我们使用单例模式的时候,有些人会去私有化这个类的构造方法,使得这个类只能通过自己写的getInstance()来创建。类似于这样:

      private SelectFrame() {
        // 并不需要做什么,只需要将这个外面的public改成private就好了=。=
      }

    嗯嗯,这么一来外部就只能按我要求的方法来创建这个对象了.

    在这里也基本将单例模式的使用基本讲完了,接下来还会继续写其他的设计模式的使用,如果有涉及到Android上的都会尽力用Android上的例子来讲,加深印象。


    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


    单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式懒汉式这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧。

    原文链接: http://tianweili.github.io/blog/2015/03/02/singleton-pattern/

    简介

    单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

    许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

    基本的实现思路

    单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

    单例的实现主要是通过以下两个步骤:

    1. 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
    2. 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

    注意事项

    单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

    单例模式的八种写法

    1、饿汉式(静态常量)[可用]

    public class Singleton {
    
        private final static Singleton INSTANCE = new Singleton();
    
        private Singleton(){}
    
        public static Singleton getInstance(){
            return INSTANCE;
        }
    }
    

    优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

    缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

    2、饿汉式(静态代码块)[可用]

    public class Singleton {
    
        private static Singleton instance;
    
        static {
            instance = new Singleton();
        }
    
        private Singleton() {}
    
        public Singleton getInstance() {
            return instance;
        }
    }
    

    这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

    3、懒汉式(线程不安全)[不可用]

    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

    4、懒汉式(线程安全,同步方法)[不推荐用]

    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {}
    
        public static synchronized Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。

    缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

    5、懒汉式(线程安全,同步代码块)[不可用]

    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    singleton = new Singleton();
                }
            }
            return singleton;
        }
    }
    

    由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

    6、双重检查[推荐用]

    public class Singleton {
    
        private static volatile Singleton singleton;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

    Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

    优点:线程安全;延迟加载;效率较高。

    7、静态内部类[推荐用]

    public class Singleton {
    
        private Singleton() {}
    
        private static class SingletonInstance {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    

    这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

    类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

    优点:避免了线程不安全,延迟加载,效率高。

    8、枚举[推荐用]

    public enum Singleton {
        INSTANCE;
        public void whateverMethod() {
    
        }
    }
    

    借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。

    优点

    系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

    缺点

    当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。

    适用场合

    • 需要频繁的进行创建和销毁的对象;
    • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
    • 工具类对象;
    • 频繁访问数据库或文件的对象。

    展开全文
  • 单例模式(Singleton Pattern)一般被认为是最简单、最易理解的...本文会重点总结一下Android开发中常用的单例模式场景,理论与实践结合,深入学习设计模式,从而提高大家的开发水平,完美解决开发中遇到的类似问题。

    前言

    单例模式(Singleton Pattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最常用、最易被识别出来的模式。

    本文会重点总结一下Android开发中常用的单例模式场景,理论与实践结合,深入学习设计模式,从而提高大家的开发水平,完美解决开发中遇到的类似问题。

    单例模式介绍

    单例模式的定义

    确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    单例模式的使用场景

    确保某个类有且只有一个对象的场景。避免产生多个对象消耗过多的资源,或者某种类型的对象应该有且只有一个。如在一个应用中,应用只有一个ImageLoader 实例,这个 ImageLoader 中又含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它构造多个实例。

    优点

    • 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

    • 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

    • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

    • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

    缺点

    扩展很困难,容易引发内存泄露,测试困难,一定程度上违背了单一职责原则,进程被杀时可能有状态不一致问题。

    实现方式

    单例模式的实现方式 有很多种,先来一个使用最为常见的实现方式。

    1.双重检查锁定(Double CheckLock)( DCL)

    
    public class Singleton {
        //JDK>=1.5 增加了volatile关键字,定义时加上它即可保证执行顺序(虽然会影响性能),从而单例生效。
        private static volatile Singleton instance = null;
    
        private Singleton(){}
     
        public static Singleton getInstance() {
            if (instance == null) { //第一个if
                synchronized (Singleton.class) {//同步判断
                    if (instance == null) {//第二个if
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    DCL 方式实现单例的优点是既能在需要时才初始化单例,又能够保证线程安全,且单例对象初始后调用getInstance不进行同步锁。

    上面版本是java语言实现的,用kotlin实现代码如下,同时用到了Kotlin的延迟属性 Lazy

    class Singleton private constructor() {
        companion object {
            val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            Singleton() }
        }
    }
    

    2.静态内部类单例模式

    public class SingleTon {
        private SingleTon(){}
        public static SingleTon getInstance(){
            return SingletonHolder.sInstance
        }
        private static class SingletonHolder{
            private static final SingleTon sInstance = new SingleTon();
        }
    }
    

    DCL 虽然在一定程序上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这《Java并发编程实践》一书中最后谈到了这个总是,并指出这种“优化”是丑陋的,不赞成使用,而建议用上述代码替代。

    当第一次加载Singleton类时不会初始化sInstance,只有在第一次调用SingleTon的getInstance方法时才会去初始化sInstance。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder,这种方式不仅能够保证线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐的单例模式实现方式。

    3.其他实现方式汇总

    public class Singleton {
    	private static Singleton mInstance = null;
    
    	private Singleton() {
    
    	}
    
    	public void doSomething() {
    		System.out.println("do sth.");
    	}
        
        
    
    	/**
    	 * 方式二、double-check, 避免并发时创建了多个实例, 该方式不能完全避免并发带来的破坏.
    	 * 
    	 * @return
    	 */
    	public static Singleton getInstance() {
    		if (mInstance == null) {
    			synchronized (Singleton.class) {
    				if (mInstance == null) {
    					mInstance = new Singleton();
    				}
    			}
    		}
    		return mInstance;
    	}
    
    	/**
    	 * 方式三 : 在第一次加载SingletonHolder时初始化一次mOnlyInstance对象, 保证唯一性, 也延迟了单例的实例化,
    	 * 如果该单例比较耗资源可以使用这种模式.
    	 * 
    	 * @return
    	 */
    	public static Singleton getInstanceFromHolder() {
    		return SingletonHolder.mOnlyInstance;
    	}
    
    	/**
    	 * 静态内部类
    	 * 
    	 *
    	 */
    	private static class SingletonHolder {
    		private static final Singleton mOnlyInstance = new Singleton();
    	}
    
    	/**
    	 *  方式四 : 枚举单例, 线程安全
    	 *
    	 */
    	enum SingletonEnum {
    		INSTANCE;
    		public void doSomething() {
    			System.out.println("do sth.");
    		}
    	}
    
    	/**
    	 * 方式五 : 注册到容器, 根据key获取对象.一般都会有多种相同属性类型的对象会注册到一个map中
    	 * instance容器
    	 */
    	private static Map<string singleton=""> objMap = new HashMap<string singleton="">();
    	/**
    	 * 注册对象到map中
    	 * @param key
    	 * @param instance
    	 */
    	public static void registerService(String key, Singleton instance) {
    		if (!objMap.containsKey(key) ) {
    			objMap.put(key, instance) ;
    		}
    	}
    	
    	/**
    	 * 根据key获取对象
    	 * @param key
    	 * @return
    	 */
    	public static Singleton getService(String key) {
    		return objMap.get(key) ;
    	}
    
    }
    

    不管以哪种形式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中你必须保证线程安全、反序列化导致重新生成实例对象等问题,该模式简单,但使用率较高。

    Android源码中的模式实现

    在Android系统中,我们经常会通过Context获取系统级别的服务,比如WindowsManagerService, ActivityManagerService等,更常用的是一个叫LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。我们以LayoutInflater为例来说明, 平时我们使用LayoutInflater较为常见的地方是在ListView的getView方法中。

    @Override
    public View getView(int position, View convertView, ViewGroup parent)	
    	View itemView = null;
    	if (convertView == null) {
    		itemView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
    		// 其他代码
    	} else {
    		itemView = convertView;
    	}
    	// 获取Holder
    	// 初始化每项的数据
    	return itemView;
    }
    

    通常我们使用LayoutInflater.from(Context)来获取LayoutInflater服务, 下面我们看看LayoutInflater.from(Context)的实现。

        /**
         * Obtains the LayoutInflater from the given context.
         */
        public static LayoutInflater from(Context context) {
            LayoutInflater LayoutInflater =
                    (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (LayoutInflater == null) {
                throw new AssertionError("LayoutInflater not found.");
            }
            return LayoutInflater;
        }
    

    可以看到from(Context)函数内部调用的是Context类的getSystemService(String key)方法,我们跟踪到Context类看到, 该类是抽象类。

    public abstract class Context {
        // 省略
    }
    

    使用的getView中使用的Context对象的具体实现类是什么呢 ?其实在Application,Activity, Service,中都会存在一个Context对象,即Context的总个数为Activity个数 + Service个数 + 1。而ListView通常都是显示在Activity中,那么我们就以Activity中的Context来分析。

    我们知道,一个Activity的入口是ActivityThread的main函数。在该main函数中创建一个新的ActivityThread对象,并且启动消息循环(UI线程),创建新的Activity、新的Context对象,然后将该Context对象传递给Activity。下面我们看看ActivityThread源码。

        public static void main(String[] args) {
            SamplingProfilerIntegration.start();
    
            // CloseGuard defaults to true and can be quite spammy.  We
            // disable it here, but selectively enable it later (via
            // StrictMode) on debug builds, but using DropBox, not logs.
            CloseGuard.setEnabled(false);
    
            Environment.initForCurrentUser();
    
            // Set the reporter for event logging in libcore
            EventLogger.setReporter(new EventLoggingReporter());
            Process.setArgV0("<pre-initialized>");
            // 主线程消息循环
            Looper.prepareMainLooper();
            // 创建ActivityThread对象
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            AsyncTask.init();
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    
        private void attach(boolean system) {
            sThreadLocal.set(this);
            mSystemThread = system;
            if (!system) {
                ViewRootImpl.addFirstDrawHandler(new Runnable() {
                    public void run() {
                        ensureJitEnabled();
                    }
                });
                android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                        UserHandle.myUserId());
                RuntimeInit.setApplicationObject(mAppThread.asBinder());
                IActivityManager mgr = ActivityManagerNative.getDefault();
                try {
                    mgr.attachApplication(mAppThread);
                } catch (RemoteException ex) {
                    // Ignore
                }
            } else {
                   // 省略
            }
    }
    

    在main方法中,我们创建一个ActivityThread对象后,调用了其attach函数,并且参数为false. 在attach函数中, 参数为false的情况下, 会通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数 ( 具体分析请参考老罗的博客 : Activity的启动流程),我们看看该函数的实现 。

        private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            // 代码省略
            Activity a = performLaunchActivity(r, customIntent);
            // 代码省略
        }
        
         private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
            // 代码省略
            Activity activity = null;
            try {
                java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
                activity = mInstrumentation.newActivity(         // 1 : 创建Activity
                        cl, component.getClassName(), r.intent);
             // 代码省略
            } catch (Exception e) {
             // 省略
            }
    
            try {
                Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    
                if (activity != null) {
                    Context appContext = createBaseContextForActivity(r, activity); // 2 : 获取Context对象
                    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                    Configuration config = new Configuration(mCompatConfiguration);
                    // 3: 将appContext等对象attach到activity中
                    activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config);
    
                    // 代码省略
                    // 4 : 调用Activity的onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                    // 代码省略
            } catch (SuperNotCalledException e) {
                throw e;
            } catch (Exception e) {
                // 代码省略
            }
    
            return activity;
        }
    
    
        private Context createBaseContextForActivity(ActivityClientRecord r,
                final Activity activity) {
            // 5 : 创建Context对象, 可以看到实现类是ContextImpl
            ContextImpl appContext = new ContextImpl();           appContext.init(r.packageInfo, r.token, this);
            appContext.setOuterContext(activity);
    
            // 代码省略
            return baseContext;
        }
        
    

    通过上面1~5的代码分析可以知道, Context的实现类为ComtextImpl类。我们继续跟踪到ContextImpl类。

    class ContextImpl extends Context {
      
        // 代码省略
        /**
         * Override this class when the system service constructor needs a
         * ContextImpl.  Else, use StaticServiceFetcher below.
         */
         static class ServiceFetcher {
            int mContextCacheIndex = -1;
    
            /**
             * Main entrypoint; only override if you don't need caching.
             */
            public Object getService(ContextImpl ctx) {
                ArrayList<Object> cache = ctx.mServiceCache;
                Object service;
                synchronized (cache) {
                    if (cache.size() == 0) {
                        for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                            cache.add(null);
                        }
                    } else {
                        service = cache.get(mContextCacheIndex);
                        if (service != null) {
                            return service;
                        }
                    }
                    service = createService(ctx);
                    cache.set(mContextCacheIndex, service);
                    return service;
                }
            }
    
            /**
             * Override this to create a new per-Context instance of the
             * service.  getService() will handle locking and caching.
             */
            public Object createService(ContextImpl ctx) {
                throw new RuntimeException("Not implemented");
            }
        }
    
        // 1 : service容器
        private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
                new HashMap<String, ServiceFetcher>();
    
        private static int sNextPerContextServiceCacheIndex = 0;
        // 2: 注册服务器
        private static void registerService(String serviceName, ServiceFetcher fetcher) {
            if (!(fetcher instanceof StaticServiceFetcher)) {
                fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
            }
            SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
        }
    
    
        // 3: 静态语句块, 第一次加载该类时执行 ( 只执行一次, 保证实例的唯一性. )
        static {
            //  代码省略
            // 注册Activity Servicer
            registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                    public Object createService(ContextImpl ctx) {
                        return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                    }});
    
            // 注册LayoutInflater service
            registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                    public Object createService(ContextImpl ctx) {
                        return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                    }});
            // 代码省略
        }
    
        // 4: 根据key获取对应的服务, 
        @Override
        public Object getSystemService(String name) {
            // 根据name来获取服务
            ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
            return fetcher == null ? null : fetcher.getService(this);
        }
    
        // 代码省略
    }
    

    从ContextImpl类的部分代码中可以看到,在虚拟机第一次加载该类时会注册各种服务,其中就包含了LayoutInflater Service, 将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取到对应的服务,从而达到单例的效果。这种模式就是上文中提到的“单例模式的实现方式5”。系统核心服务以单例形式存在,减少了资源消耗。

    在Android中的其他运用场景

    1.开源控件图片加载库Android-Universal-Image-Loader

    private volatile static ImageLoader instance;
    
    public static ImageLoader getInstance() {
            if (instance == null) {
                synchronized (ImageLoader.class) {
                    if (instance == null) {
                        instance = new ImageLoader();
                    }
                }
            }
            return instance;
        }
    

    2.Android的事件发布-订阅总线 EventBus

    private static volatile EventBus defaultInstance;
    
    public static EventBus getDefault() {
    	if (defaultInstance == null) {
    		synchronized (EventBus.class) {
    			if (defaultInstance == null) {
    				defaultInstance = new EventBus();
    			}
    		}
    	}
    	return defaultInstance;
    }
    

    3.ActivityManager中管理Activity堆栈

    public class ActivityManager {
        
        private static ActivityManager instance;
        private Stack<Activity> activityStack;// activity栈
    
        // 单例模式
        public static ActivityManager getInstance() {
            if (instance == null) {
                instance = new ActivityManager();
            }
            return instance;
        }
    
        // 把一个activity压入栈中
        public void pushOneActivity(Activity actvity) {
            if (activityStack == null) {
                activityStack = new Stack<>();
            }
            activityStack.add(actvity);
        }
    
        // 获取栈顶的activity,先进后出原则
        public Activity getLastActivity() {
            return activityStack.lastElement();
        }
    
        // 移除一个activity
        public void popOneActivity(Activity activity) {
            if (activityStack != null && activityStack.size() > 0) {
                if (activity != null) {
                    activity.finish();
                    activityStack.remove(activity);
                    activity = null;
                }
    
            }
        }
    
        // 退出所有activity
        public void finishAllActivity() {
            if (activityStack != null) {
                while (activityStack.size() > 0) {
                    Activity activity = getLastActivity();
                    if (activity == null)
                        break;
                    popOneActivity(activity);
                }
            }
        }
    }
    

    在 Android 中使用单例还有哪些需要注意的地方

    单例在 Android 中的生命周期等于应用的生命周期,所以要特别小心它持有的对象是否会造成内存泄露。如果将 Activity 等 Context 传递给单例又没有释放,就会发生内存泄露,所以最好仅传递给单例 Application Context。

    总结

    单例模式作为最容易理解的一种设计模式,同时也是运用频率很高的模式,由于客户端通常没有高并发的情况,因此,选择哪种实现方式不会有太大的影响。但是出于效率考虑,推荐使用使用DCL静态内部类实现方式。

    每个模式都有它的优缺点和适用范围,相信大家看过的每一本介绍模式的书籍,都会详细写明某个模式适用于哪些场景。我的观点是,我们要做的是更清楚地了解每一个模式,从而决定在当前的应用场景是否需要使用,以及如何更好地使用这个模式。就像《深入浅出设计模式》里说的:

    使用模式最好的方式是:“把模式装进脑子里,然后在你的设计和已有的应用中,寻找何处可以使用它们。”

    单例模式是经得起时间考验的模式,只是在错误使用的情况下可能为项目带来额外的风险,因此在使用单例模式之前,我们一定要明确知道自己在做什么,也必须搞清楚为什么要这么做。

    参考资料:

    1.何红辉,关爱民. Android 源码设计模式解析与实战[M]. 北京:人民邮电出版社

    2.单例模式都用在什么地方?能举几个例子吗

    展开全文
  • 比如在android实际APP 开发中用到的 账号信息对象管理, 数据库对象(SQLiteOpenHelper)等都会用到单例模式。下面针对一些例子分析一下我们开发过程中应用单例模式需要注意的点。 一、作用 单例模式(Singleton...

    单例模式是设计模式中最常见也最简单的一种设计模式,保证了在程序中只有一个实例存在并且能全局的访问到。比如在android实际APP 开发中用到的 账号信息对象管理, 数据库对象(SQLiteOpenHelper)等都会用到单例模式。下面针对一些例子分析一下我们在开发过程中应用单例模式需要注意的点。


    一、作用
    单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点

    二、适用场景

    1. 应用中某个实例对象需要频繁的被访问。

    2. 应用中每次启动只会存在一个实例。如账号系统,数据库系统。

    三、常用的使用方式

    (1)懒汉式

    这是在开发中很容易就能写出来的一种方式,如下

    public class Singleton {
    
        /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
        private static Singleton instance = null;
    
        /* 私有构造方法,防止被实例化 */
        private Singleton() {
        }
    
        /* 1:懒汉式,静态工程方法,创建实例 */
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    调用:
    Singleton.getInstance().method();
    优点:延迟加载(需要的时候才去加载)

    缺点: 线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行的频繁读写操作时。

    (2)加同步锁

    既然线程不安全,那就加上同步锁,一种加法如下:

       /*2.懒汉式变种,解决线程安全问题**/
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    更一般的写法是这样
       /*加上synchronized,但是每次调用实例时都会加载**/
        public static Singleton getInstance() {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
            return instance;
        }
    调用:
    Singleton.getInstance().method();
    优点:解决了线程不安全的问题。

    缺点:效率有点低,每次调用实例都要判断同步锁

    补充:在Android源码中使用的该单例方法有:InputMethodManager,AccessibilityManager等都是使用这种单例模式

    (3)双重检验锁

    要优化(2)中因为每次调用实例都要判断同步锁的问题,很多人都使用下面的一种双重判断校验的办法

      /*3.双重锁定:只在第一次初始化的时候加上同步锁*/
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    这种方法貌似很完美的解决了上述效率的问题,它或许在并发量不多,安全性不太高的情况能完美运行,但是,这种方法也有不幸的地方。问题就是出现在这句
     instance = new Singleton();

    在JVM编译的过程中会出现指令重排的优化过程,这就会导致当 instance实际上还没初始化,就可能被分配了内存空间,也就是说会出现 instance !=null 但是又没初始化的情况,这样就会导致返回的 instance 不完整(可以参考:http://www.360doc.com/content/11/0810/12/1542811_139352888.shtml)。

    调用:

    Singleton.getInstance().method();
    优点:在并发量不多,安全性不高的情况下或许能很完美运行单例模式

    缺点:不同平台编译过程中可能会存在严重安全隐患。

    补充:在android图像开源项目Android-Universal-Image-Loader https://github.com/nostra13/Android-Universal-Image-Loader中使用的是这种方式

    (4)内部类的实现

    内部类是一种好的实现方式,可以推荐使用一下:

    public class SingletonInner {
    
        /**
         * 内部类实现单例模式
         * 延迟加载,减少内存开销
         * 
         * @author xuzhaohu
         * 
         */
        private static class SingletonHolder {
            private static SingletonInner instance = new SingletonInner();
        }
    
        /**
         * 私有的构造函数
         */
        private SingletonInner() {
    
        }
    
        public static SingletonInner getInstance() {
            return SingletonHolder.instance;
        }
    
        protected void method() {
            System.out.println("SingletonInner");
        }
    }

    调用:

    SingletonInner.getInstance().method();
    优点:延迟加载,线程安全(java中class加载时互斥的),也减少了内存消耗

    (5)枚举的方法

    这是网上很多人推荐的一种做法,但是貌似使用的不广泛,大家可以试试,

    /**
     * @function:单例模式枚举实现
     * @author xuzhaohu
     * 
     */
    public enum SingletonEnum {
        /**
         * 1.从Java1.5开始支持;
         * 2.无偿提供序列化机制;
         * 3.绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候;
         */
    
        instance;
    
        private String others;
    
        SingletonEnum() {
    
        }
    
        public void method() {
            System.out.println("SingletonEnum");
        }
    
        public String getOthers() {
            return others;
        }
    
        public void setOthers(String others) {
            this.others = others;
        }
    }

    调用:

    SingletonEnum.instance.method();
    优缺点:如代码中注释。

    上面主要讲了单例模式5种创建方法,大家可以根据其优缺点进行个人实际项目中的使用。讲的属于抛砖引玉,大家多提意见。


    参考:http://zz563143188.iteye.com/blog/1847029

    展开全文
  • 单例模式介绍及它的使用场景单例模式应用最广的模式,也是我最先知道的一种设计模式,深入了解单例模式之前,每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式的实现。...
  • 概念 单例模式就是整个系统有且只有一个... - 多线程下注意单例模式的创建; - 确保单例类对象反系列化时不会重新构建对象; 单例模式从实现角度来分可以分:饿汉式单例和懒汉式单例,先看一下它们的实现: ...
  • 1、什么是单例模式 单例模式意味着只有一个对象,至于单例模式如何实现就不这里就不多说了,有很多种实现办法。 2、什么时候该使用单例模式?如果不使用单例模式会出现什么问题? 先举2个例子 例子1 ...
  • 之前开发中老用到一些设计模式可是呢又不是很懂,于是狠下心来琢磨一番。下面是我琢磨后总结的,希望对您有用。如果发现了问题,请帮忙指正。一、单例模式是什么? 单例模式最初的定义出现于《设计模式》:“保证...
  • 单例模式:一个应用程序中 只有一个实例 特点是: ① 私有的构造方法 ② 私有的静态实例 ③ 公有静态访问方法单例分为饿汉式和懒汉式:其中懒汉式存在线程安全问题,所以需要加锁。饿汉式: public class ...
  • 单例模式在我们日常开发中或多或少都会遇见,使用场景也是多种多样。今天根据项目中需求,正好用到了单例模式此记录一下。 一、跟随app存活 有什么方式,可以让一个变量跟随app存活呢?是一个单例模式维护的...
  • android单例模式的用途

    2019-11-10 09:06:48
    我是这样认为的,首先单例模式就是为了使这个对象单一,这样可以节省堆内存,永远保证当前类的实例只有一个,先看代码: class A{ private static A s; private A(){}; public static A getInstance(){ if(s...
  • 因为绝大部分开发者平时对单例模式的认识,可能仅仅停留“会用”的阶段。为什么会有这个模式?为什么要用这个模式?哪里用单例模式最合适?乱用了会有什么负面影响? 这些可能大多数人都一知半解。今天就让我们...
  • 对于开发人员来说,设计模式有时候就是一道坎,但是设计模式又非常有用,过了这道坎,它可以让你水平提高一个档次。而在android开发中,必要的了解一些设计模式又是非常有必要的...这篇文章介绍一个模式,就是单例模式
  • 这篇主要介绍单例在Android开发中的各种应用场景以及和静态类方法的对比考虑,举实际例子说明。 单例的思考 写了这么多单例,都快忘记我们到底为什么需要单例,复习单例的本质 单例的本质:控制实例的...
  • 本篇简单介绍如何在Android studio中 使用单例模式和使用注意事项。 单例模式 为什么要使用单例模式? 有一些对象我们只需要一个,只需要一个线程池 、缓存或是只有一台打印机、机器人 、机器人上面只有一个寻磁...
  • 单例模式是一种对象创建型模式,用来编写一个类,整个应用系统中只能有该类的一个实例对象。 UML 结构图: 单例模式的三要点: 1、某个类只能有一个实例 2、必须自行创建这个实例 3、必须自行向整个系统提供这个....
  • SQLITE数据库的使用是Android单例模式典型的应用场景。 单例模式好处: 将对象写成static,避免内存频繁实例化,因此对象静态内存区只有一份。直接使用getInstance()取得对象。 模型类 ...
  • 单例模式的使用场景其实还挺简单,就是一个类只允许创建一个对象,全局共享使用这个对象。 Java 中实现单例,需要考虑是否懒加载、是否线程安全的问题,实现方式:饿汉式、懒汉式、双重检查、静态内部类、枚举。...
  • java模式之单例模式单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例。特点:1,一个类只能有一个实例2,自己创建这个实例3,整个系统都要使用这个实例 Singleton模式主要作用是保证...
1 2 3 4 5 ... 20
收藏数 22,034
精华内容 8,813
关键字:

单例模式在android的应用