大家好, 我系苍王.
这个系列已经出到了第 30 章节了, 已经开通了已经有一年半的时间了.
在一年半里, 建立了千人的 QQ 大群, 不少编辑也找过我编辑图书, 也有同行找过我合作出公众号. 但是个人的时间是有限的, 并不可能全部愿望都实现.
那么上一年就选了一件对这辈子非常有意义的事情, 和电子工业出版社出版一本关于组件化技术的书. 非常感谢陈晓猛编辑找到了我一同出书, 也感谢在技术群中不断深讨组件化技术的群友们.
书中重点介绍了使用组件化思想去搭建一个 Android 项目, 介绍了组件化的思想, 组件化的编程技术, 多人管理组件化, 组件化的编译优化, 以及对项目演进的思想感悟.
此书并不是只是介绍技术, 也包含了我对一些生活的理解, 技术思维的理解.
京东 https://search.jd.com/Search?keyword=Android 组件化架构 & enc=utf-8&wq=Android 组件化架构 & pvid=f86138a85ea1487cba5c6bc0da3cbe50 , 淘宝 https://s.taobao.com/search?style=grid&ie=utf8&initiative_id=staobaoz_20180406&stats_click=search_radio_all:1&js=1&imgfile=&q=android 组件化架构 & suggest=0_1&_input_charset=utf-8&wq=Android 组件化 & suggest_query=Android 组件化 & source=suggest 和当当 http://product.dangdang.com/1081281879.html 均可以购买, 有兴趣可以点击链接就可以跳转了.
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 脚本动态更新.
来源: http://www.jianshu.com/p/a8f09bb61317