多平台
前言
在上一篇 构建Retrofit 文章的最后,在真正通过 Buidler#build()
方法去构建 Retrofit
实例时,我们有看到 Platform
相关的代码:
Platform platform = Platform.get();
由于 Retrofit
是支持 Java 和 Android 多平台的第三方库,所以它内部需要判断当前运行的平台类型,针对不同的平台提供不同的参数与实现。
本篇文章我们就学习下 Retrofit
中是如何对多平台做差异化处理的,所以本文主要学习 Platform
类。
Platform
我们先总览下 Platform
的类图关系:
通过类图可以看出,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
的实例:
- 通过
System.getProperty("java.vm.name")
获取当前 Java 虚拟机的名称, - 根据 Java 虚拟机的名称判断当前运行的平台类型,
- 我们知道 "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 版本的实现:Android21
和 Android24
。
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,所以:
- 判断是否是默认方法直接返回 false,
- 调用默认方法直接抛出异常,
- 适配器工厂集合只有默认的适配器工厂,
- 转换器工厂集合是空集合,
- 回调执行器是主线程执行器。
Android24
开始支持 Java8,它比 Android21
复杂一些:
- 判断是否是默认方法,通过
method.isDefault();
判断, - 调用默认方法比较复杂,首先判断了 API 版本,即 API 24 和 25 还不支持默认方法,API 26 才开始支持默认版本,接下来通过反射调用默认方法,
- 适配器工厂集合除了有默认的适配器工厂外,由于 Java8 支持
CompletableFuture
,所以增加了CompletableFutureCallAdapterFactory
, - 转换器工厂也不再是空集合,由于 Java8 支持
Optional
,所以增加了OptionalConverterFactory
, - 回调执行器没有变化,还是主线程执行器。
主线程执行器
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
, Java14
和 Java16
。
我们还是先以 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;
}
}
Java8
与 Android24
非常类似,只有一点不同:它没有回调执行器,即接口数据在哪个线程返回,结果回调就在哪个线程执行。
Java14
与 Java8
相比,只是在调用默认方法的实现上有些不同,看 Java14
的类注释,说 Java14
有提供调用默认方法的常规 API。
Java16
与 Java14
相比,也是在调用默认方法的实现上有些不同,看 Java16
的类注释,说 Java16
提供了在代理上调用默认方法的公共 API。
总结与思考
虽然 Retrofit
与多平台相关的代码比较少,但是我们还是可以学到不少知识点:
Retrofit
通过定义抽象类和抽象方法,不同平台实现各自的逻辑来隔离外部调用方对平台类型的无感知。这种定义上层接口,下层逻辑各自实现的设计原则与设计模式非常值得我们学习。- 通过
System.getProperty("java.vm.name")
获取当前 Java 虚拟机的名称,我们自己写开源库时或许可以用的上, - 如何获取当前系统支持 Java 版本,或许可以从
Java14
或Java16
中的isSupported()
方法中找到答案, - 对于不同平台,不同版本如何调用接口默认方法,我们是不是也学到了呢,
- 通过分析 Android 主线程执行器的实现,我们可以举一反三,自己定义 Java 平台的回调执行器吧。
好啦,就到这里吧,下期再见,希望可以帮你更好的使用 Retrofit
。
happy~~