从android系统服务的缓存机制看基于容器的单例模式

        android的后台运行了很多service,它们在系统启动时被SystemServer开启,比如管理打开的窗口程序的WindowManager,用于管理系统运行的Activity的ActivityManager,取得xml里定义的view的LayoutInflater ,状态栏的NotificationManager,位置服务LocationManager 等等。应用可以通过Context可以获取到这些服务。

        对于Context而言,这一个个Service,一方面不需要反复创建的,另一方面是最好按需创建,也就是懒加载。因此,Service的创建方法被封装起来,统一注册到一个管理类中,在context向管理类获取某个Service时,这个Service才被创建,并以单例形式保存在这个Context中,减少资源消耗。

        概述比较抽象,接下来会基于Android12的源码,分析系统服务在一个Context中的这些注册和创建动作,学习一下源码里是如何运用基于容器的单例模式的。

        首先,获取系统服务的接口是ContextImpl.getSystemService()。调用形式通常是:

ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);

        而context的实现类是 ContextImpl ,那就从ContextImpl.java 看起吧:

class ContextImpl extends Context {
@Override
public Object getSystemService(String name) {
    // ……Check incorrect Context usage.
    return SystemServiceRegistry.getSystemService(this, name);
}
}

        可以看到ContextImpl 里的调用非常简单,只是调用了 SystemServiceRegistry 的一个方法。 SystemServiceRegistry 是用于缓存、注册、获取系统服务的,系统服务的单例实现就是依靠这个类里面的缓存机制。接下来从getSystemService() 开始研究  SystemServiceRegistry 的缓存机制:

@SystemApi
public final class SystemServiceRegistry {
    private static final String TAG = "SystemServiceRegistry";
    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS 
                            = new ArrayMap<String, ServiceFetcher<?>>();
    private static int sServiceCacheSize;

    public static Object getSystemService(ContextImpl ctx, String name) {
        ……
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        final Object ret = fetcher.getService(ctx);
        ……
        return ret;
    }

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

         SystemServiceRegistry .getSystemService()的主要工作就是从自己的map里面,根据service的名称,取出了一个  ServiceFetcher ,并使用相应的context调用这个 ServiceFetcher ​​​​​​.getService()。这里出现了一个非常重要的对象,  ServiceFetcher ,它以service的名称为键,被存储在  SystemServiceRegistry 的map键值对 SYSTEM_SERVICE_NAMES  里。  ServiceFetcher 是一个接口,实现类的对象只能在静态初始化期间创建,——也就是 SystemServiceRegistry .java 的静态初始化过程中创建:

/**
* 用context静态注册一个系统Service(只会在静态初始化过程中调用)
*/
private static <T> void registerService(@NonNull String serviceName,@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}

static {
    //注册一系列的Service,有一百来个,其中就比如:LayoutInflator,ActivityManager
     registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
        new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
               
            }
        }
    );
    registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
        new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }
        }
    );
}

          SystemServiceRegistry 有一系列的静态语句块,在虚拟机第一次加载  SystemServiceRegistry 的时候,这些  ServiceFetcher 会被注册。java 类中的静态域的初始化和静态代码块的执行只在类加载的时候执行且只执行一次,也就是说,这些静态语句块已经保证了这些  ServiceFetcher 实例全局唯—。

        搞清楚了注册信息ServiceFetcher 是怎么以单例形式存在后,可以看看 ServiceFetcher 的作用了,实现类是  CachedServiceFetcher

/**
 * Override this class when the system service constructor needs a
 * ContextImpl and should be cached and retained by that context.
 */
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    private final int mCacheIndex;

    CachedServiceFetcher() {
        //CachedServiceFetcher是 SystemServiceRegistry的内部类,而且只允许静态实例化,
        //而SystemServiceRegistry注册serviceFetcher的过程是线程安全的,所以在这里可以直接访问sServiceCacheSize。
        mCacheIndex = sServiceCacheSize++;
    }

    @Override
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        T ret = null;
        for (; ; ) {
            // 1.同步锁
            synchronized (cache) {
                // cache 中已经存在service 则直接返回
                T service = (T) cache[mCacheIndex];
                if (service != null) {
                    ret = service;
                    break; // exit the for (;;)
                }
                ……
            }
            try {
                //2.不存在则创建并返回
                service = createService(ctx);
            } catch (ServiceNotFoundException e) {
                onServiceNotFound(e);
            } finally {
                //3.写操作的同步锁
                synchronized (cache) {
                    cache[mCacheIndex] = service;
                }
                ret = service;
                ……
            }
        return ret;
    }

    public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;

}

        CachedServiceFetcher 是用来给conext提供具体的Service实例的,从它的仅有两个方法可以看出。一个是final的getService(),getService()的实现表明系统Service的创建是一种懒加载,只有当getService 发生的时候才会去创建,不会造成资源浪费。另一个是代码2 的createService() ,createService() 具体实现由子类实现,子类会在这个方法里创建和返回他们的Service,比如
 return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
或者 return new PhoneLayoutInflater(ctx.getOuterContext());

        CachedServiceFetcher中比较重要的一个成员变量是 int  mCacheIndex CachedServiceFetcherSystemServiceRegistry 的内部类,所以可以直接把  SystemServiceRegistry 的service计数器 sServiceCacheSize赋值给mCacheIndex

        进一步思考一下,这个赋值过程必须是线程安全的,对吧,毕竟这是个唯一标识,总不能两个ServiceFetcher 拥有同个标识。其实很简单,看回前面反复强调的,“java 类中的静态域的初始化和静态代码块的执行只在类加载的时候执行且只执行一次”—— CachedServiceFetcherSystemServiceRegistry在静态代码块中创建的对象,所以CachedServiceFetcher的构造方法天生就是线程安全的, 构造方法里赋值mCacheIndex 的操作自然也是线程安全的。所以说,SystemServiceRegistry里的这一堆静态代码块,不仅保证了 SYSTEM_SERVICE_NAMES这个容器里的实例们的单例和线程安全,还保证了实例里的计数器的线程安全。高级!

        好了,还剩最后一点小问题:单例模式实现中最困难的地方,是多线程环境下构造单例类的对象有且只有一个,也就是线程安全。这里用静态代码块直接免去了线程安全的困扰,那,没有静态代码块,就不能用基于容器的单例模式了吗?

当然不。看代码1和3处,就不是静态代码块中的例子,但仍然需要保证Context的cache数组的单例,我简化了一下代码:

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    @Override
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        T ret = null;
        for (; ; ) {
            // 1.读的同步锁
            synchronized (cache) {
                T service = (T) cache[mCacheIndex];
                if (service != null) {
                    ret = service;
                    break; 
                }
            }
            service = createService(ctx);
            // 3.写操作的同步锁
            synchronized (cache) {
                cache[mCacheIndex] = service;
            }
            ret = service;
        }
        return ret;
}

class ContextImpl extends Context {
    final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
}

        会看到是用sychronized保证线程安全的。对cache这个容器的读写都加了sychronized关键字,当两个线程A 和B同时调用了ContextImpl.getSystemService() ,A线程正在写入,B线程就需要等A线程完成后再读取。

        也就是说,系统Service所使用的单例模式是基于容器的单例模式,有两种保证线程安全的形式:

1. service的注册——发生在类加载的时候,采用静态代码块来保证线程安全。在静态代码块把多种单例类型注入到一个统一的管理类中,使用静态域来存储唯一一个实例,既保证了线程安全又保证了懒加载。(翻译一下就是虚拟机加载SystemServiceRegistry的时候,ServiceFetcher的子类们,会在静态代码块中被初始化,存储在 SYSTEM_SERVICE_NAMES ​​​这个map里。

2. service的创建—— 发生在调用时,用sychronized来保证缓存数组的线程安全。

总结

Service 的获取分为注册和创建两个大步骤,都是通过SystemServiceRegistry 缓存、注册、获取的。

1. Service的注册信息是单例的,基于容器实现。

这里的“容器”具体指的是SystemServiceRegistry的一个map 成员变量,以service的名称为键,存储一系列的  ServiceFetcher 。map的赋值在一系列一系列的静态语句块中,静态语句块在第—次加载该类时执行,只执行—次,因此map的赋值是线程安全的。

2. Context中存储的Service实例是单例的,基于容器实现。

SystemServiceRegistry中的 ServiceFetcher 保证了系统service的创建是懒加载的,只有当context 调用getService() 获取某个service时,这个service才会真正被创建,创建的service对象以单例形式存储在Context 的一个缓存数组里,这个过程中用sychronized来保证缓存数组的线程安全。

3.  这种基于容器的方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口(context.getService())进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值