Android Framework源码阅读计划(2)——LocationManagerService.java

本文深入探讨了Android Framework中LocationManagerService的实现,重点关注其使用的设计模式,包括单例模式、里氏替换原则和建造者模式。通过对源码的分析,阐述了如何利用这些模式优化系统服务的管理,以及建造者模式在减少错误和提高灵活性方面的优势。

Android Framework源码阅读计划
Android Framework源码阅读计划(1)——LocationManager.java
Android Framework源码阅读计划(2)——LocationManagerService.java


前言

本篇主要介绍LocationManagerService中用的设计模式和原则,由于LocationManagerService是个非常重要的类,也是非常大的类,可能会有很多的点没有讲到,水平有限,欢迎补充指正。

主要设计单例模式,里氏替换原则,建造者模式

一、LocationManagerService

管理 LocationProviders 并发布位置更新和警报的服务类。
以上就是源码关于这个类的注释,非常简单。代码是最好的注释,通过类名基本可以猜到这个类的主要作用。上篇有提到,LocationManager作为外观类,将LocationManagerService的复制性隐藏了,也能体现出LocationManagerService的复杂。

二、使用的设计模式和原则

2.1 单例模式

2.1.1 定义

单例属于创建型模式,确保系统中某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
优点:减少了内存开支,系统的性能开销

2.1.2 源码

从后缀就可以看到,这是一个Service,而且还是系统的服务。其中有个内部类Lifecycle继承了SystemService,用来管理LocationManagerService的生命周期。

	/**
     * Controls lifecycle of LocationManagerService.
     */
    public static class Lifecycle extends SystemService {

        private final SystemInjector mSystemInjector;
        private final LocationManagerService mService;

        public Lifecycle(Context context) {
            super(context);
            mUserInfoHelper = new LifecycleUserInfoHelper(context);
            mSystemInjector = new SystemInjector(context, mUserInfoHelper);
            // LocationManagerService初始化
            mService = new LocationManagerService(context, mSystemInjector);
        }
        
        @Override
        public void onStart() {
        }

        @Override
        public void onBootPhase(int phase) {
        }
        ...
	}

基本上所有系统服务都是以该方法实现控制生命周期的,也是通过这种方式实现的单例模式。由于注册系统服务不是多线程的,所以不需要通过双重校验等方式来确保单例。

	//SystemServiceRegistry.java
	registerService(Context.LOCATION_SERVICE, LocationManager.class,
               new CachedServiceFetcher<LocationManager>() {
           @Override
           public LocationManager createService(ContextImpl ctx) throws ServiceNotFoundException {
               IBinder b = ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE);
               return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
           }});

由于系统服务需要被很多地方调用,所以并没有使用传统的方式使用getInstance()方法获取单例对象,还是在系统服务注册的时候,就将所有服务存放到了一个叫做SYSTEM_SERVICE_FETCHERS的容器,其本质就是就一个ArrayMap.java。统一管理,节省资源,使用起来也更加方便。

    /**
     * SystemServiceRegistry.java
     * 使用上下文静态注册系统服务。
     * This method must be called during static initialization only.
     */
    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());
    }

以上两个代码块,充分展现了设计模式之美。通过策略模式,实现对不同ServiceFetcher的初始化,降低代码的耦合,代码更加简洁(SystemServiceRegistry中有100多个系统服务在这里注册)。之所以能过策略模式,最后实现将所有方法注册到通一个容器,依靠的是继承和多态,这里就不得不提到的是里氏替换原则

2.2 里氏替换原则

2.2.1 定义

1.如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型
2.所有引用基类的地方必须能透明地使用其子类的对象

里式替换原则最重要一点是子类必须能够替换其基类,也因此100+的系统方法都可以通过一个方法实现注册。里氏替换原则是对于开闭原则的补充,对继承进行了规则上的约束。

子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
子类中可以增加自己特有的方法。
当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

关于里氏替换原则,可以查看参考链接中的‘‘里氏替换原则’’。里氏替换原则虽然很好,但是只是指导性的原则,不是强制原则,具体实现还是要结合实际情况。

2.3建造者模式

2.3.1 定义

将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。属于创建型模式,采用链式调用一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

2.3.2 源码

	ProviderProperties properties = new ProviderProperties.Builder()
		.setHasNetworkRequirement(Boolean.parseBoolean(fragments[1]))
		.setHasSatelliteRequirement(Boolean.parseBoolean(fragments[2]))
		.setHasCellRequirement(Boolean.parseBoolean(fragments[3]))
		.setHasMonetaryCost(Boolean.parseBoolean(fragments[4]))
		.setHasAltitudeSupport(Boolean.parseBoolean(fragments[5]))
		.setHasSpeedSupport(Boolean.parseBoolean(fragments[6]))
		.setHasBearingSupport(Boolean.parseBoolean(fragments[7]))
		.setPowerUsage(Integer.parseInt(fragments[8]))
		.setAccuracy(Integer.parseInt(fragments[9]))
		.build();

除了这个其实还有很多用到建造者模式类,比如Intent, LocationRequest只是使用到建造者模式的类,LocationManagarService并不是
建造者模式,主要包含
Builder:抽象建造者 上面的源码指Builder抽象基类,但是ProviderProperties.Builder并没有基类
ConcreteBuilder:上面的源码指Builder具体实现类
Director:指挥者 上面的源码没有用到,并非必要
Product:产品角色 上面的源码指最后返回的properties实例

    // ProviderProperties.Builder
	public static final class Builder {
        ...

        public Builder() {
            ...
        }

        public Builder(@NonNull ProviderProperties providerProperties) {
            ...
        }

        /**
         * Sets whether a provider requires network access. False by default.
         */
        public @NonNull Builder setHasNetworkRequirement(boolean requiresNetwork) {
            ...
            return this;
        }

        /**
         * Sets whether a provider requires satellite access. False by default.
         */
        public @NonNull Builder setHasSatelliteRequirement(boolean requiresSatellite) {
            ...
            return this;
        }

        ...
        
        public @NonNull ProviderProperties build() {
            return new ProviderProperties(mHasNetworkRequirement, mHasSatelliteRequirement,
                    mHasCellRequirement, mHasMonetaryCost, mHasAltitudeSupport, mHasSpeedSupport,
                    mHasBearingSupport, mPowerUsage, mAccuracy);
        }
    }

通过Builder源码可以看到,除build方法,其他方法均是对builder实例进行相关操作,返回的也是更加复制完整的builder对象。最后通过build方法,获取Product。

2.3.3 扩展

除了使用建造者模式,参数过多且存在非必选项的,可以采用折叠构造函数,以及JAVABean的方式。

  1. 折叠构造函数
    通过重载,使用不同参数、不同数量的参数来对类进行初始化。这种方式比较常见,除了用在类的初始化,在方法的调用的时候也有类似的思想, 利用重载,可以使用多种方式发起定位请求。使用重载方法的例子,只是没找到使用折叠构造函数初始化话的类,参考链接关于建造者模式的链接中,也很详细例子
    在没有好的注释情况下,多个参数可能是同类型的,容易传错。
	// LocationManager
	@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
	public void requestLocationUpdates(@NonNull String provider, long minTimeMs, float minDistanceM,
	        @NonNull LocationListener listener) {...}
	@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
	public void requestLocationUpdates(@NonNull String provider, long minTimeMs, float minDistanceM,
	        @NonNull LocationListener listener, @Nullable Looper looper) {...}
	@RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
	public void requestLocationUpdates(
            @NonNull String provider,
            long minTimeMs,
            float minDistanceM,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull LocationListener listener) {...}
    ...
  1. Javabean
    这个就没有什么好讲的了,学习java的同学这个应该都很熟。由于是分步设置的,用的时候需要判断必须值是否为空。

建造者模式可以解决上诉其他两种方式的问题,但是在特定场合下使用那两种方式或许也是不错的选择。

建造者模式适用情况包括:
需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;
需要生成的产品对象的属性相互依赖,需要指定其生成顺序;(笔者的理解是把需要生成的放到构造器中)
对象的创建过程独立于创建该对象的类;(创建多个不同类型的产品的时候,可以使用Director)
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。

参考链接

虽然看了很多文档,但是只会列出一些笔者觉得对我帮助很大的
里氏替换原则
秒懂设计模式之建造者模式
建造者模式

总结

其中还有用到很多东西,比如代理模式,但是LocationManagerService不是代理模式,只是用到了,和建造模式一样,相比而言,建造者模式的类比较简单,所以放到这里写了,其他会拆出来写。当然也存在水平不足没发现的,欢迎各位大佬提出指正了。

须知少年凌云志,曾许人间第一流

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MGYH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值