组件化通信方案第三选择

组件化通信方案Arouter WMRouter之外的选择

主流通信基本原理(这里不讲解路由)2个

  1. Arouter利用注解存储实现类的path,使用的时候做下反射就可以。
  2. WMRouter虽然说参考SPI,但实现和SPI没啥关系。利用注解在编译期存储实现类的Class。
    甚少直接采用SPI,而且两家为何没有选择正宗的SPI呢,原因很简单,无法通过Key来查找服务,只能通过一个接口获取服务列表。没有key,谁知道提供给我的是什么服务,谁还敢用。
    但SPI的简单的吸引力又很足。因为没有注解,没有生成其他类,没有初始化,没有注解处理器,只有一个配置文件,其简洁性秒杀所有Router方案,而且Java的JNDI采用了SPI,是可以落地的。但天生有缺点,尴尬的是在组件化中几乎没有落地。

组件化是否有其他方案呢,是否可以利用SPI的简洁性思想呢。

答案是肯定的,其他方案有2个。一个是我自己实现的,一个是我同事实现的。

  1. 基于spi,保证单实现。第一个方案基于一个前提,在公共服务lib 暴露实现方的服务接口,接口唯一。在这个前提下,那么可以采用改造过的SPI,需要自己实现ServiceLoader
  2. 基于指令分发。定义一个统一接口,组件module要在清单文件注册定义实现类,然后将实现类注册到map容器。实现类中是基于指令方式再分发不同的处理逻辑。

先不说具体实现,这个两个方案何以能替换Arouter,WMRouter.有什么理由去替换呢。
从我个人角度而言,理由有下面几个。
5. 编译期注解一般需要单独module,这个就增加了gradle编译时间。会生成中间类,难于调试。
6. 比之Arouter,WMRouter使用更加方便。对实现方简化逻辑,屏蔽中间类。

第一个方案:基于SPI的单实现。

spi是Java提供的一套用来被第三方实现或者扩展的API,比较方便。可移植到组件化中,但要解决一个问题,就是spi获得的实现是多个,因为是自己项目中,极少两个个不同业务的module暴露的服务基于同个接口。所以需要改造,原理就是移除掉原来的迭代即可,自定义的serviceLoader如下:

public class ServiceLoader {

  private static final String PREFIX = "META-INF/services/";
  /**
   * 存储services的class 对象,应该使用LruCache算法,默认存100个
   */
  private LruCache<String, Class> providers = new LruCache<>(100);

  private ClassLoader mClassLoader;

  private volatile static ServiceLoader instance = null;

  private ServiceLoader() {
    mClassLoader = Thread.currentThread().getContextClassLoader();
  }

  public static ServiceLoader getInstance() {
    if (instance == null) {
      synchronized (ServiceLoader.class) {
        instance = new ServiceLoader();
      }
    }
    return instance;
  }


  public <T >T load(Class<T> obj) {
    String fullName = PREFIX + obj.getName();
    URL url = mClassLoader.getResource(fullName);
    Class<T> classInstance;
    if ((classInstance = providers.get(obj.getName())) == null) {
      classInstance = parse(url);
      providers.put(obj.getName(), classInstance);
    }

    return createInstance(classInstance);
  }

  private <T >T createInstance(Class<T> obj) {
    T instance = null;
    try {
      instance = obj.newInstance();
    } catch (Exception e) {
      Log.e("ServiceLoader","instance falied:" + e.getMessage());
    }
    return instance;
  }

  /**
   * 一个接口是否只有一个实例?
   */
  private Class parse(URL url) {
    Class obj = null;
    InputStream inputStream = null;
    BufferedReader bufferedReader = null;
    try {
      long start = System.currentTimeMillis();
      inputStream = url.openStream();
      bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
      String line = bufferedReader.readLine();
      long middle = System.currentTimeMillis();
      Log.i("ServiceLoader", "IO 读写耗时:" + (middle - start));

      if (!TextUtils.isEmpty(line)) {
        obj = Class.forName(line.trim());
      }
      Log.i("ServiceLoader", "反射耗时:" + (System.currentTimeMillis() - middle));
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        if (inputStream != null) {
          inputStream.close();
        }
        if (bufferedReader != null) {
          bufferedReader.close();
        }
      } catch (Exception e) {

      }
    }
    return obj;
  }

}

用法:

  ModuleA1Service moduleA1Service = ServiceLoader.getInstance()
        .load(ModuleA1Service.class);


    Log.i(TAG,"获取服务需要时间:" + (System.currentTimeMillis() - start));
    if (moduleA1Service != null) {
      moduleA1Service.modulea();
      Log.i(TAG, "获取服务成功");
    }else {
      Log.i(TAG, "获取服务失败");
    }

ModuleA1Service 的注册和原生的SPI一致,不细说,如果有需要,可以找我要demo。就是这么简单,没有生成中间类,没有注解相关的,没有路由表,门槛极低。但也不尽然是优点,有个缺点就是要做一次io读写,需要300ms左右(在我的三星j5手机上)的时间,但有缓存,只有第一次会消耗一点性能。有个解决方案,就是在application的时候,把服务定义的文件从apk包里拿出来存到另外一个目录,那么后面就可以用nio去做读写,性能提高会非常可观。有人会问,啥不直接用nio,因为apk相当于压缩包,调用File file = new File(url.toURI()) 会抛出 URI is not hierarchical 的错误。别看serviceloader简单,有个非常重要的知识点,也是关键之处,为何用Thread.currentThread().getContextClassLoader()返回的classloader,而不是用**.class.getClassLoader()返回的classloader,这里涉及到破坏双亲委托的概念,这里不展开,下篇文章单独论述这个问题。我们再看第二种解决方案。

基于指令分发的实现

因为这个方案是我同事写的,我贴出他的分析链接 艾瑞Android组件化框架使用说明

新出的方案

因为基于spi的需要读写io,在没有用nio做读写的情况下,牺牲性能,总让人不舒服。所以有了一个折中方案。方案前提是module之间是强依赖,采用手动注册,原理也简单,一看遍知。

public class ServiceManager {

  private static HashMap<Class, Class> service = new HashMap<>();

  public static void registService(Class key, Class value) {
    if (key != null) {
      service.put(key, value);
    }
  }

  public static <T> T getService(Class<T> key) {
    Class value = service.get(key);

    try {
      return (T) value.newInstance();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    }
    return null;
  }


}
总结

我原本是是一个注解爱好者,最近还写过一个IOC的框架,也是基于注解。但是我最近反思一个问题,编译器注解的利用,虽然简化了过程,但对于用注解的人而言,实际上比较尴尬,追本溯源比较繁琐,不下载他的源码,你是不知道细节的。当然注解很高级,在运行期描述元数据是最适合的场景,只是个人的想法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值