设计模式-单例模式(Singleton)在Android开发应用场景思考和遇到的坑

介绍

上篇博客中详细说明了各种单例的写法和问题。这篇主要介绍单例在Android开发中的各种应用场景以及和静态类方法的对比考虑,举实际例子说明。

单例的思考

写了这么多单例,都快忘记我们到底为什么需要单例,复习单例的本质

单例的本质:控制实例的数量

全局有且只有一个对象,并能够全局访问得到。

控制实例数量

有时候会思考如果我们需要控制实例的数量不是只有一个,而是2、3、4或者任意多个呢?我们怎样控制实例的数量,其实实现思路也简单,就是通过Map缓存实例,控制缓存的数量,当有调用就返回某个实例,这其中就涉及到调度问题。考虑在实际Android开发中有这样的情况吗?还真有,如果看过我的上篇分析单例的博客提到郭神和洪洋大神都有LruCache实现图片缓存,不就是控制实例数量的应用场景吗。LruCache内部用LinkedHashMap持有对象。用LruCache缓存图片到内存,图片数量就是我们需要控制的实例数量,一般是根据内存的大小开空间存图片,根据图片地址url取内存中的图片没有访问网络获取,内部采用最近最少使用调度算法控制图片的存储。

具体实现看比较复杂,详情去看两位大神的CDNS博客吧。

单例的应用场景

回到开发的场景中,思考我们为什么需要单例。如果是需要提供一个全局的访问点用getInstance()做些操作。除了单例我们还有其他的选择吗?

回去翻看Android源码,有这样一个类。java.lang.Math类它提供对数字的操作和方法计算,它的实现就是全部方法用static修饰符包装提供类级访问。因为当我们调用Math类时只要它的某个类方法做数据操作并不关心对象状态。

单例不需要维护任何状态,仅仅提供全局访问的方法,这种情况考虑使用静态类,静态方法比单例更快,因为静态的绑定是在编译期就进行。

如果你需要将一些工具方法集中在一起时,你可以选择使用静态方法,但是别的东西,要求单例访问资源并关注对象状态时,应该使用单例模式。

Android开发中单例应用

单例在Android开发中的实际使用场景,图片加载框架就是一个很好的例子。我在刚接触Android的时候使用的Android Universal Image Loader就采用了单例,这是因为它需要缓存图片,对缓存的图片集合做各种操作,需要关注单例中的对象状态,而且明显是需要访问资源的。这就很契合单例的特性。同样在热门的EventBus中也采用了单例,因为它内部缓存了各个组件发送过来的event对象,并负责分发出去,各个组件需要向同一个EventBus对象注册自己,才能接收到event事件,肯定是需要全局唯一的对象,所以采用了单例。

EventBus的单例采用的是双重检查加锁单例

static volatile EventBus defaultInstance;

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

最后在Android源码中发现,一个非常重要的类LayoutInflater本身也采用的是单例模式。

Retrofit网络框架静态类工具类应用

在我的一个项目中使用到Retrofit做网络访问,这就需要一个具体的Retrofit对象操作网络。而且最好提供方法得到这个全局唯一的Retrofit对象。一开始我也在纠结是单例还是静态类。因为国内网站上对Retrofit的分析使用不是很多,而且网络上对这单例和静态类的分析争辩实在太多而且混乱。

最后直到看到这篇博客,感觉还是老外靠谱,最后我的项目采用下面的代码实例化Retrofit对象。具体代码是这样的。目前使用没有问题,大家当做使用Retrofit时候的实例化参考吧。(代码依据最新的Retrofit-2.0版本)

public class ServiceGenerator {

    public static final String API_BASE_URL = "http://your.api-base.url";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create());

    public static <S> S createService(Class<S> serviceClass) {
        Retrofit retrofit = builder.client(httpClient.build()).build();
        return retrofit.create(serviceClass);
    }
}

只所以这么写,采用静态类而不是单例,是因为把网络访问看做工具类,只需要拿到Retrofit实例对象做网络操作,ServiceGenerator工具类内部不维护内部变量也不关心内部变量的状态变化。

单例开发的坑

踩坑是每个开发者必须经历的过程,下面说明我在采用单例之后遇到的坑。相信每个初级Android开发者都遇到这样的问题。两个Activity组件之间传递数据,Intent和Bundle只能传递简单的基本类型数据和String对象

(当然也可以传递对象这就需要Parcelable和Serializable接口)。

当需要传递的只是几个值问题不大,但是如果需要传递的数据比较多就感觉代码不简洁而且key值多容易接收出错,传递对象需要对象继承Parcelable接口写大量的重复的模板代码。有没有优雅一点解决办法呢?

用单例对象传递对象的坑

Application传递对象的坑

相信有些人跟当时的我一样看过这样的博客”优雅的用Application传递对象”。当时的我看见这样博客,真实感觉遇到救星一样,感觉一下就解决了组件间传递对象的问题。

长者语:too young too simple sometimes naive

下面来说说如果你真的用Application传递对象会怎么样。原文博客是这样认为的Application由系统提供是全局唯一的对象,并且任何组件都可以访问到。哪就在自定义继承Application的子类里,保存内部变量,由发送的Activity取出内部变量并设值,startActivity之后在接收的Activity中也访问Application对象取出内部变量得到需要传递的对象。就没有复杂的Intent传值了。

但是如果你真的这么做:程序肯定会崩或者是取不到数据。

实际运行情况是这样的:

1. 如果你在接收数据的Activity中,按下Home键返回桌面,长时间的没有返回你的App。

2. 系统有可能会在系统内存不足的时候杀掉进程。

3. 当你再从最近程序运行列表进入你的App,系统会默认恢复刚刚离开的状态,直接进入接收数据的Activity中。

4. 然后调用各个生命周期方法回调,其中只要运行到从Application取数据行,程序就会弹出空指针NullPointerException异常导致崩溃。

5. 相信我一定是这样的,如果没有崩溃也只是因为你在内部变量中有默认初始化方法。这样肯定也是取不到想要的数据。

因为整个流程需要很长时间,我们可以使用adb命令杀掉进程adb shell kill,模拟长时间没有回到应用而由系统杀死进程的操作。如果觉得麻烦还可以打开Device Monitor-选中你的应用-使用红色按钮 Stop Process杀死进程。

程序崩溃的这主要原因就是:

系统会恢复之前离开的状态,直接进入某个Activity组件而不是再依次打开Activity,这样你的发送数据的Activity没有运行也就不会向Application中传值,自然也取不到值。

所以千万不要相信”优雅的用Application传递对象”这写博客,这是个坑!

指出这个问题原文是dont-store-data-in-the-application-object中文翻译的博客在这,大家可以点击查看会有详细说明。

EventBus的坑

当时也是在写一个项目,觉得Intent传递数据太麻烦,根据Appliaction可以传递数据的思路,其实自己也可以写个单例用来保存全局数据,各个组件取出实现组件间传递数据。然后很网络上搜索,发现EventBus同样实现了这样的思路,EventBus本身就是采用了单例模式。上篇博客的伏笔就在这。

EventBus: Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递

由一个组件发送事件,另一个组件向EventBus注册然后响应的方法就会得到数据。这里面也是个坑啊。

当然我没有说EventBus有问题,只是使用不当会导致Crash程序崩溃。

当时项目是就是按照标准的EventBus使用流程写的代码,没有问题。还是上文的情况,按下Home键长时间没有返回应用,再次进入程序Crash。

原因还是一样的:

系统恢复离开的现场,直接运行接收数据的Activity,而没有运行到发送数据的Activity组件,取不到数据,因为根本就没有数据发送。

顺带提一句,

用Kill App这个方法能够检查出App中很多意想不到的问题

解决办法

用单例传递数据遇到的问题本质其实就是,内存是很容易被虚拟机回收的。我们要解决的就是怎么样保存数据,持久化数据。

其实也没有什么好的解决方案。

  • 还是直接将数据通过intent传递给 Activity 。
  • 使用官方推荐的几种方式将数据持久化到磁盘上,再取数据。
  • 在使用数据的时候总是要对变量的值进行非空检查,这样还是取不到数据
  • 使用EventBus传递数据时采用onSaveInstanceState(Bundle outState)方法保存数据,使用onCreate(Bundle savedInstanceState)等待恢复取值。

包装Activity跳转方法

针对第一项,我提供一个简单的包装跳转方法,简化Inten传递数据的代码逻辑

public class MyActivity extends AppCompatActivity{
    //Intent的key值
    protected static final String TYPE_KEY = "TYPE_KEY";
    protected static final String TYPE_TITLE = "TYPE_TITLE";

    //接收的数据
    public String mKey;
    public String mTitle;

    //包装的跳转方法
public static void launch(Activity activity, String key, String title) {
        Intent intent = new Intent(activity, BoardDetailActivity.class);
        intent.putExtra(TYPE_TITLE, title);
        intent.putExtra(TYPE_KEY, key);
        activity.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //获取数据
         mKey = getIntent().getStringExtra(TYPE_KEY);
        mTitle = getIntent().getStringExtra(TYPE_TITLE);}
 }

使用代码,就一行

 MyActivity.launch(this, key, title);

整个的逻辑是,在跳转的组件中实现类方法,把传递值的key值以成员类变量的形式写定在Activity中,需要传递的数据放入Intent中,简化调用方的使用代码。

onSaveInstanceState保存数据

onSaveInstanceState()方法的调用时机是:

只要某个Activity是做入栈并且非栈顶时(启动跳转其他Activity或者点击Home按钮),此Activity是需要调用onSaveInstanceState的,

如果Activity是做出栈的动作(点击back或者执行finish),是不会调用onSaveInstanceState的。

这正是上文我们程序Crash的场景,产生问题的关键操作点。

所有我们需要做的就是在onSaveInstanceState回调方法中保存数据,等待数据恢复。

代码没什么好贴的就是outState.putParcelable(KEY, mData);,然后在OnCreate中取savedInstanceState中的数据。

提示被put的数据需要实现Parcelable接口,如果不想写大量的模板代码可以使用Android Parcelable Code Generator插件快捷成成代码。

总结

  • 总算写完了,一个单例模式写了两篇博客,上篇博客主要是说明各种单例的写法和分析。
  • 本文主要介绍比较新的枚举单例
  • 还有单例的应用场景和思考,以及在Android开发中单例的应用场景。单例模式其实在源码和很多开源框架中都有应用,写好单例分析单例的和静态类方法和适用场景能够写出好的代码。
  • 最后总结我在使用单例时遇到的坑和提出解决方案。
时间: 2024-12-24 17:34:33

设计模式-单例模式(Singleton)在Android开发应用场景思考和遇到的坑的相关文章

Android设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 1 package com.example.main; 2 3 import android.app.Activity; 4 import

php设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 谷歌的Android设备 华为的Android设备 IOS只属于苹果公司 IOS只属于苹果公司 1 <?php 2 3 /* 4 * php

Java 设计模式 单例模式(Singleton) [ 转载 ]

Java 设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创建对象 第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 5 public static Singleton getInstan

(转)设计模式之——单例模式(Singleton)的常见应用场景

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html .当然也可以自己搜索.

设计模式之——单例模式(Singleton)的常见应用场景(转):

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html . 好多没怎么使用过的人

(转)单例模式(Singleton)的常见应用场景

转自:http://blog.csdn.net/likika2012/article/details/11483167 单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terr

JAVA设计模式-单例模式(Singleton)线程安全与效率

一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式进行探索的. 本文的目的是:结合文章中的八种单例模式的写法,使用实际的示例,来演示线程安全和效率 既然是实际的示例,那么就首先定义一个业务场景:购票.大家都知道在春运的时候,抢票是非常激烈的.有可能同一张票就同时又成百上千的人同时在抢.这就对代码逻辑的要求很高了,即不能把同一张票多次出售,也不能出现

设计模式--单例模式Singleton

单例模式顾名思义整个程序下只有一个实例,例如一个国家只有一个皇帝,一个军队只有一个将军.单例模式的书写又分为饿汉模式和懒汉模式 饿汉模式   类中代码 package demo; public class Singleton { //私有化构造函数 private Singleton() { } //由于外部无法实例对象,顾在类中实例,定义为static将对象直接由类调用 private static Singleton instance = new Singleton(); //定义公有成员函

设计模式-单例模式下对多例的思考(案例:Server服务器)

前述: 在学习单例模式后,对老师课上布置的课后作业,自然要使用单例模式,但是不是一般的单例,要求引起我的兴趣,案例是用服务器. 老师布置的要求是:服务器只有一个,但是使用这个服务器时候可以有多个对象(原版的)和备份数据库,也就是至少要两个对象,因为有可能服务器对象会垮掉,所以要用备份的,所以这里要考虑调用时候,应该返回哪个服务器对象,还有当服务器对象垮掉后,应该怎么处理,保证用户的使用.老师说,两个对象是基本要求,如果能够控制多个对象,分数更高哦. 我觉得蛮有意思的题目,如果只考虑两个对象,无非