[Android]如何做一个崩溃率少于千分之三噶应用app(30)- SPI加载

大家好,我系苍王。
这个系列已经出到了第30章节了,已经开通了已经有一年半的时间了。
在一年半里,建立了千人的QQ大群,不少编辑也找过我编辑图书,也有同行找过我合作出公众号。但是个人的时间是有限的,并不可能全部愿望都实现。
那么上一年就选了一件对这辈子非常有意义的事情,和电子工业出版社出版一本关于组件化技术的书。非常感谢陈晓猛编辑找到了我一同出书,也感谢在技术群中不断深讨组件化技术的群友们。
书中重点介绍了使用组件化思想去搭建一个Android项目,介绍了组件化的思想,组件化的编程技术,多人管理组件化,组件化的编译优化,以及对项目演进的思想感悟。
此书并不是只是介绍技术,也包含了我对一些生活的理解,技术思维的理解。
京东淘宝当当均可以购买,有兴趣可以点击链接就可以跳转了。

Android组件化架构

以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。

[Android]如何做一个崩溃率少于千分之三噶应用app--章节列表

关于spi,其全名是Service Provider Interfaces。ServiceLoder用于动态加载接口实现类的加载器。
1.其可以动态加载一些继承某个接口的实体类
2.需要将实体类名声明到resources/META-INF/services目录,可以取巧使用@AutoService
3.其加载的并不是单例,而且构造方法不带任何参数,因为ServiceLoader底层是使用了反射的机制来加载。
4.加载文件顺序应该是按照resources/META-INF/services目录中顺序加载,所以如果使用@AutoService是不可控的。
5.ServiceLoader继承iterator接口,可以像List一样遍历实体类。
6.其实际也是通过反射来实现初始化操作,使用接口的方式使模块,ServiceLoader装载器、启动器之间更加解耦。
7.比较适合于组件化中,模块入口初始化的统一加载场景。

SPI原理图

以下借用一个Modular框架中的加载为例
1.声明接口

public interface IModule {
    /**
     * 模块初始化,只有组建时才调用,用于开启子线程轮训消息
     */
    void init();

    /**
     * 模块ID
     *
     * @return 模块ID
     */
    int getModuleId();

    /**
     * 模块注册并连接成功后,可以做以下事情:
     * <p>
     * 1、注册监听事件
     * 2、发送事件
     * 3、注册服务
     * 4、调用服务
     */
    void afterConnected();
}

2.使用@AutoService,将全路径名写到resources/META-INF/services目录

@AutoService(IModule.class)
public class Module extends BaseModule {
    @Override
    public void afterConnected() {


    }

    @Override
    public int getModuleId() {
        return Constants.MODULE_B;
    }
}

3.使用ServiceLoder加载模块

@Override//只有当是组建单独运行时,才当Application运行,才会走onCreate,最终打包时根本没有这个类
    public void onCreate() {
        super.onCreate();
        ……

        //自动注册服务器(如果是独立模块内声明只有一个IModule)
        ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
        mBaseModule = (BaseModule) modules.iterator().next();

        //模块初始化
        mBaseModule.init();
        ……
    }
public void onCreate() {
        super.onCreate();
        ……

        //SPI自动注册服务(主module装载的时候,已经将全部META_INF文件合并)
        ServiceLoader<IModule> modules = ServiceLoader.load(IModule.class);
        for (IModule module : modules) module.afterConnected();
    }

使用看起来非常简单,我们研究一下ServiceLoader源码的特别之处。

    //调用静态load方法来初始化XXXInterface接口信息。
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //获取当前线程ClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    //构建ServiceLoader对象
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //检测接口是否否存在
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //检测classloader是否为空,为空使用系统classloader加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // Android-changed: Do not use legacy security code.
        // On Android, System.getSecurityManager() is always null.
        // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
   
    public void reload() {
        //清理provides配置加载器
        providers.clear();
        //初始化懒加载迭代器
        lookupIterator = new LazyIterator(service, loader);
    }

可以看到使用的是懒加载的迭代器,只有迭代器被使用的时候,才会真正初始化每一个继承接口的实体类。

   //判断是否有下一个对象
   private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //PREFIX = "META-INF/services/"
                    //加载配置地址
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        //加载配置
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
           //解析配置文件,只要找到一个需要解析的接口就跳出
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //解析config的节点
                pending = parse(service, configs.nextElement());
            }
           
            nextName = pending.next();
            return true;
        }

通过反射完成接口类的初始化

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //通过类路径名,加载类信息
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     // Android-changed: Let the ServiceConfigurationError have a cause.
                     "Provider " + cn + " not found", x);
                     // "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                // Android-changed: Let the ServiceConfigurationError have a cause.
                ClassCastException cce = new ClassCastException(
                        service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
                fail(service,
                     "Provider " + cn  + " not a subtype", cce);
                // fail(service,
                //        "Provider " + cn  + " not a subtype");
            }
            try {
               //反射初始化类,并转化成接口
                S p = service.cast(c.newInstance());
                //记录映射关系
                providers.put(cn, p);
                //返回接口实体
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

ServiceLoader实际还是通过路径名反射来完成,只是其通过配置到META_INF中的目录文件来完成解耦。
ServiceLoader使用场景是用于不需要区分module加载顺序的情况,如果有加载顺序,还需要重新排序后再初始化方法,这里最后还是使用优先级机制。

SPI的原理上,还是通过配置文件反射加载类,而在开编的时候已经介绍了SPI的优点和局限性,跳出SPI,依然能做一个更灵活更可控的加载机制,例如json脚本,xml脚本动态更新。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,394评论 25 707
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • VUE系列教程目录 vue-cli 3.X系列:vue最简单的入门教程+实战(vue-cli3.X) vue入门之...
    侬姝沁儿阅读 10,518评论 0 69
  • 小时候给别人写同学录时,自己写的理想不是成为一个科学家,也不是一个发明家,而是不想长大。 结果愿望真的实现了,从初...
    你好啊艾伦郭阅读 181评论 0 0