(一)Android中的单例模式

作为一个Android开发的老司机,或者刚入行的司机,我觉得你还是有必要学习下Android的单例模式,毕竟
单例模式是我们很常用的一个设计模式。

1. 介绍

1.1 定义

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

实现单例主要有如下几个关键点:

  1. 构造方法不对外开放,一般是private,防止外部实例化。
  2. 通过一个静态方法返回实例对象。
  3. 保证实例只有一个,尤其是多线程环境下。
  4. 确保单例对象在反序列化的情况下,不会重新构建对象。

1.2 单例模式的几种实现方式

单例模式的写法真的太多了,但是如果你读过Effective Java这本书的话,里面着重推荐的就是使用枚举的方式来实现单例模式。

我们还是要总结下单例的实现方式:

  1. 饿汉模式(多线程安全,需要处理反序列化问题)
  2. 饱汉模式(多线程不安全、需要处理反序列化问题)
  3. DCL(高并发会出现DCL检查失效问题,1.6之前使用volatile修饰实例变量)
  4. 静态内部类(多线程安全,需要处理反序列化问题)
  5. 枚举(简单、推荐、防止反序列化)
  6. 使用容器实现单例模式

具体的写法我们不做多的介绍了,要是感兴趣可以自行查询。


2. Android中的单例模式

首先明确一点,Android中的单例模式绝大部分采用了[容器]来实现。
使用容器实现单例模式,主要有以下特点:

  1. 在程序初始化时,将多种单例注入到一个统一的管理类中。
  2. 使用时根据key获取单例对象。
  3. 我们可以管理多种类型的单例,并且在需要时通过一个统一的接口来进行获取。
  4. 对用户隐藏了具体的实现,降低了耦合度。

好了,带着这个先入为主的概念,接下来我来带你分析下具体的实现。

在日常开发中,我们经常使用系统提供的服务,如WindowManagerService,PackageManagerService,ActivityManagerService等,但是我们最经常使用的一定少不了LayoutInflater,这些服务会在合适的时候以单例的形式注册在系统中,我们需要的时候只需要通过context.getSystemService(String serviceName)方法获取即可。

我们本章节以LayoutInflater为例,来一探Android源码中单例模式究竟。

2.1 LayoutInflater获取

我们首先来回顾下LayoutInflater的使用方式:

    // 1. 调用LayoutInflater.from方法
    LayoutInflater inflater = LayoutInflater.from(this);

    // 2. 调用context.getSystemService获取,并转换
    LayoutInflater inflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    //  LayoutInflater.from
    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;
    }

代码示例中,我们列出了我们经常使用的获取LayoutInflater的方法,如果你翻看一下LayoutInflater.from的源码,你会惊奇的发现LayoutInflater.from是对我们经常使用的第二种方式的封装。
看来LayoutInflater.from只是系统方便我们使用简化的方法,你看Android的设计者多为我们考虑啊。
根据这个思路,我们甚至可以封装一个工具类来获取我们常用的服务类,而不用再写一大坨获取并强制转换的代码。

2.2 getSystemService

经过上面的分析,我们知道我们常用的服务类,是通过调用context.getSystemService来获取的。
追溯到cotext的源码,我们发现Context只是一个接口,而不是具体的实现类,而它的具体实现类是ContextImpl,我们看下getSystemService的源码。

    
    // ContextImpl用来缓存已经创建的服务实例
    final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

    @Override
    public Object getSystemService(String name) {
        // 原来是调用了SystemServiceRegistry类的静态方法
        return SystemServiceRegistry.getSystemService(this, name);
    }

ContextImpl也并未存储具体的单例对象,调用了SystemServiceRegistry.getSystemService获取并返回实例。

2.3 SystemServiceRegistry

为了不影响我们的分析视线,我摘取了SystemServiceRegistry的核心代码,代码如下:

final class SystemServiceRegistry {
    // 你要的容器类在这里
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();


    // 通过该方法来获取service
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

    
    // 这个是注册服务的方法
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

    static {
        
        // 我们使用的ActivityManager在这注册
        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});

        // 我们使用的AlarmManager在这注册
        registerService(Context.ALARM_SERVICE, AlarmManager.class,
                new CachedServiceFetcher<AlarmManager>() {
            @Override
            public AlarmManager createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
                IAlarmManager service = IAlarmManager.Stub.asInterface(b);
                return new AlarmManager(service, ctx);
            }});

        // 终于找到你,LayoutInflater就是在这注册的
        // 现在我们还知道LayoutInflater的实现类为PhoneLayoutInflater
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
        }

}

getSystemService并不是在HashMap中直接存储服务实例,而是存储的ServiceFetcher。我们看看下ServiceFetcher的源码。

    static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

我们看到ServiceFetcher只是一个接口,它定义通过ContextImpl获取实实例。

我们看一个ServiceFetcher的具体实现类CachedServiceFetcher,也是我们上面分析的代码中使用的类。

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;
        public CachedServiceFetcher() {
            // 记录当前服务实例(T)所对应的缓存Index
            mCacheIndex = sServiceCacheSize++;
        }
        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
            // 获取ContextImpl中的缓存数组
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                // 从ContextImpl中的缓存数组中获取服务实例,存在直接返回,不存在则创建并加入缓存数组中
                Object service = cache[mCacheIndex];
                if (service == null) {
                    try {
                        service = createService(ctx);
                        cache[mCacheIndex] = service;
                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);
                    }
                }
                return (T)service;
            }
        }
        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }

分析下getService流程如下:

  1. 首先从ContextImpl缓存数组中获取对应的实例。
  2. 如果实例存在,直接返回实例对象;否则创建实例,并加入ContextImpl缓存数组中。
    CachedServiceFetcher的实现了保证了,实例对象只在首次获取时才会被初始化,否则就返回已经缓存的对象,节省了系统资源。

2.4 综合分析

综合分析我们得出如下结论:

  1. 我们常用的一些系统Service是通过SystemServiceRegistry进行管理的。
  2. 系统加载SystemServiceRegistry类时,通过静态块将各种服务类存储在SYSTEM_SERVICE_FETCHERS(HashMap<ServiceName,ServiceFether>)中,每一个具体的服务类都对应一个ServiceFether。
  3. 使用时从HashMap中获取ServiceFetcher,并从中获取需要的实例,获取的实例被缓存在ContextImpl中备用。
  4. CachedServiceFetcher负责创建具体的实例,并将实例缓存到ContextImpl的缓存数组中。

涉及单例模式的几个要点:

  1. SystemServiceRegistry使用HashMap管理ServiceFether。
  2. ServiceFether保证实例只创建一次,且通过使用synchronized机制保证多线程安全。
  3. 加入缓存机制,保证实例只会被创建一次。

3. 总结

单例模式经常出现在我们的日常开发中,我们对于常规的写法(getInstance)应该都十分熟悉了,那么了解下Android系统的单例实现也算是拓展了一种思路吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,143评论 4 34
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • 前言 本文主要参考 那些年,我们一起写过的“单例模式”。 何为单例模式? 顾名思义,单例模式就是保证一个类仅有一个...
    tandeneck阅读 2,438评论 1 8
  • 最美不过夏花, 最痛不过绝望, 最凉不过人心, 最痴不过女人。 月夜如诗, 夜景如画, 红酒一杯, 谁人与共?
    3acc31e6b8fd阅读 278评论 11 7
  • 你离开了我 又重新回来 那一面湖 那一面镜 那一棵棵垂杨柳 相濡以沫吧 你就在我的旁边 看着花开花落 别忘记我 好吗
    Ling_00阅读 231评论 1 3