跳至主要內容

多平台

guodongAndroid大约 6 分钟

前言

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

Platform platform = Platform.get();

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

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

Platform

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

Platfrom 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~~