Retrofit学习日记-多平台

600 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 2 天,点击查看活动详情

前言

在上一篇 构建Retrofit 文章的最后,在真正通过 Buidler#build() 方法去构建 Retrofit 实例时,我们有看到 Platform 相关的代码:

 Platform platform = Platform.get();

由于 Retrofit 是支持 Java 和 Android 多平台的第三方库,所以它内部需要判断当前运行的平台类型,针对不同的平台提供不同的参数与实现。

本篇文章我们就学习下 Retrofit 中是如何对多平台做差异化处理的,所以本文主要学习 Platform 类。

Platform

我们先总览下 Platform 的类图关系:

platform-uml

通过类图可以看出,Retrofit 主要支持 Java 和 Android 平台,并且同一平台也适配了不同的版本,可以说非常强大。

其中的 RoboVm 平台笔者也不大了解,这里我们就不再介绍了。

Platform 本身是个抽象类,它一共有七个方法,其中两个是静态方法,其余的都是抽象方法。

两个静态方法分别是:get()createPlatform() ,其中 createPlatform() 是私有静态方法。

 private static final Platform PLATFORM = createPlatform();
 ​
 static Platform get() {
     return PLATFORM;
 }
 ​
 private static Platform createPlatform() {
     switch (System.getProperty("java.vm.name")) {
         case "Dalvik":
             if (Android24.isSupported()) {
                 return new Android24();
             }
             return new Android21();
         case "RoboVM":
             return new RoboVm();
         default:
             if (Java16.isSupported()) {
                 return new Java16();
             }
             if (Java14.isSupported()) {
                 return new Java14();
             }
             return new Java8();
     }
 }

createPlatform() 方法在类加载时调用,用于构建 Platform 的实例:

  1. 通过 System.getProperty("java.vm.name" 获取当前 Java 虚拟机的名称,
  2. 根据 Java 虚拟机的名称判断当前运行的平台类型,
  3. 我们知道 "Dalvik" 是 Android 平台上的虚拟机实现,除开 "RoboVM",即认为是 Java 平台。

get() 方法比较简单,直接返回 Platform 的实例。

下面我们看看 Platform 提供的五个抽象方法,这些抽象方法才是 Platform 的核心。

defaultCallbackExecutor

 abstract @Nullable Executor defaultCallbackExecutor();

defaultCallbackExecutor 方法用于获取平台默认的回调执行器,即请求成功或失败后回调结果在哪个线程执行。

createDefaultCallAdapterFactories

 abstract List<? extends CallAdapter.Factory> createDefaultCallAdapterFactories(@Nullable Executor callbackExecutor);

createDefaultCallAdapterFactories 方法用于获取平台默认的适配器工厂,即我们定义的接口方法返回值适配器,比如:Call<T>Observable<String>CompletableFuture<String> 等。

createDefaultConverterFactories

 abstract List<? extends Converter.Factory> createDefaultConverterFactories();

createDefaultConverterFactories 方法用于获取平台默认的转换器工厂,主要用于转换调用 http 接口返回的数据为我们定义的接口方法返回值内的泛型类型,比如 http 接口返回 JSON 数据,我们定义的返回值为 Call<User>,那么转换器会把 接口返回的 JSON 数据转换为 User 类型的实例。

isDefaultMethod

 abstract boolean isDefaultMethod(Method method);

isDefaultMethod 方法用于判断在当前平台下,入参 Method 是否是默认方法,因为 Java8 开始接口支持定义默认方法。

invokeDefaultMethod

 abstract @Nullable Object invokeDefaultMethod(
       Method method, Class<?> declaringClass, Object proxy, Object... args) throws Throwable;

通过上面介绍的 isDefaultMethod 方法判断入参 Method 是否是默认方法,如果是默认方法则通过 invokeDefaultMethod 方法根据不同平台类型实现调用默认方法。

Platform 提供的方法我们已经分析完毕,接下来我们看看不同平台的实现吧(RoboVm 除外)。由于笔者是 Android 开发者,比较关注 Android 平台上的实现,所以我们先看看 Android 平台下的实现。

Android 平台

Android 平台下提供了两个针对 Android 版本的实现:Android21Android24

 private static final class Android21 extends Platform {
     @Override
     boolean isDefaultMethod(Method method) {
         return false;
     }
 ​
     @Nullable
     @Override
     Object invokeDefaultMethod(
         Method method, Class<?> declaringClass, Object proxy, Object... args) {
         throw new AssertionError();
     }
 ​
     @Override
     Executor defaultCallbackExecutor() {
         return MainThreadExecutor.INSTANCE;
     }
 ​
     @Override
     List<? extends CallAdapter.Factory> createDefaultCallAdapterFactories(
         @Nullable Executor callbackExecutor) {
         return singletonList(new DefaultCallAdapterFactory(callbackExecutor));
     }
 ​
     @Override
     List<? extends Converter.Factory> createDefaultConverterFactories() {
         return emptyList();
     }
 }

Android21 比较简单,Android API 21 还不支持 Java8,所以:

  1. 判断是否是默认方法直接返回 false,
  2. 调用默认方法直接抛出异常,
  3. 适配器工厂集合只有默认的适配器工厂,
  4. 转换器工厂集合是空集合,
  5. 回调执行器是主线程执行器。

Android24 开始支持 Java8,它比 Android21 复杂一些:

  1. 判断是否是默认方法,通过 method.isDefault(); 判断,
  2. 调用默认方法比较复杂,首先判断了 API 版本,即 API 24 和 25 还不支持默认方法,API 26 才开始支持默认版本,接下来通过反射调用默认方法,
  3. 适配器工厂集合除了有默认的适配器工厂外,由于 Java8 支持 CompletableFuture ,所以增加了 CompletableFutureCallAdapterFactory
  4. 转换器工厂也不再是空集合,由于 Java8 支持 Optional ,所以增加了 OptionalConverterFactory
  5. 回调执行器没有变化,还是主线程执行器。

主线程执行器

 private static final class MainThreadExecutor implements Executor {
     static final Executor INSTANCE = new MainThreadExecutor();
 ​
     private final Handler handler = new Handler(Looper.getMainLooper());
 ​
     @Override
     public void execute(Runnable r) {
         handler.post(r);
     }
 }

主线程执行器的实现比较简单,直接实现 Executor 接口,在实现的接口方法中,通过 Android 平台上的 Handler 分发到主线程运行。

Java 平台

Java 平台下提供了三个针对 Java 版本的实现:Java8 , Java14Java16

我们还是先以 Java8 为基准与其他两个进行差异对比吧。

 private static final class Java8 extends Platform {
     private @Nullable Constructor<Lookup> lookupConstructor;
 ​
     @Nullable
     @Override
     Executor defaultCallbackExecutor() {
         return null;
     }
 ​
     @Override
     List<? extends CallAdapter.Factory> createDefaultCallAdapterFactories(
         @Nullable Executor callbackExecutor) {
         return asList(
             new CompletableFutureCallAdapterFactory(),
             new DefaultCallAdapterFactory(callbackExecutor));
     }
 ​
     @Override
     List<? extends Converter.Factory> createDefaultConverterFactories() {
         return singletonList(new OptionalConverterFactory());
     }
 ​
     @Override
     public boolean isDefaultMethod(Method method) {
         return method.isDefault();
     }
 ​
     @Override
     public @Nullable Object invokeDefaultMethod(
         Method method, Class<?> declaringClass, Object proxy, Object... args) throws Throwable {
         Constructor<Lookup> lookupConstructor = this.lookupConstructor;
         if (lookupConstructor == null) {
             lookupConstructor = Lookup.class.getDeclaredConstructor(Class.class, int.class);
             lookupConstructor.setAccessible(true);
             this.lookupConstructor = lookupConstructor;
         }
         return lookupConstructor
             .newInstance(declaringClass, -1 /* trusted */)
             .unreflectSpecial(method, declaringClass)
             .bindTo(proxy)
             .invokeWithArguments(args);
     }
 }
 ​
 // 获取 Java 版本
 static boolean isSupported() {
     try {
         Object version = Runtime.class.getMethod("version").invoke(null);
         Integer feature = (Integer) version.getClass().getMethod("feature").invoke(version);
         return feature >= 14;
     } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException ignored) {
         return false;
     }
 }

Java8Android24 非常类似,只有一点不同:它没有回调执行器,即接口数据在哪个线程返回,结果回调就在哪个线程执行。

Java14Java8 相比,只是在调用默认方法的实现上有些不同,看 Java14 的类注释,说 Java14 有提供调用默认方法的常规 API。

Java16Java14 相比,也是在调用默认方法的实现上有些不同,看 Java16 的类注释,说 Java16 提供了在代理上调用默认方法的公共 API。

总结与思考

虽然 Retrofit 与多平台相关的代码比较少,但是我们还是可以学到不少知识点:

  1. Retrofit 通过定义抽象类和抽象方法,不同平台实现各自的逻辑来隔离外部调用方对平台类型的无感知。这种定义上层接口,下层逻辑各自实现的设计原则与设计模式非常值得我们学习。
  2. 通过 `System.getProperty("java.vm.name" 获取当前 Java 虚拟机的名称,我们自己写开源库时或许可以用的上,
  3. 如何获取当前系统支持 Java 版本,或许可以从 Java14Java16 中的 isSupported() 方法中找到答案,
  4. 对于不同平台,不同版本如何调用接口默认方法,我们是不是也学到了呢,
  5. 通过分析 Android 主线程执行器的实现,我们可以举一反三,自己定义 Java 平台的回调执行器吧。

好啦,就到这里吧,下期再见,希望可以帮你更好的使用 Retrofit

happy~~