最详细Retrofit源码解析
深入解析 Retrofit源码
前言
· 在Andrroid
开发中,网络请求十分常用
· 而在Android
网络请求库中,Retrofit
是当下最热的一个网络请求库
· 今天,我将手把手带你深入剖析Retrofit v2.0
的源码,希望你们会喜欢
在阅读本文前,建议先阅读文章:这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)
目录
1. 简介
特别注意:
· 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
· 原因:网络请求的工作本质上是 OkHttp
完成,而 Retrofit 仅负责 网络请求接口的封装
· App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
· 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析
2. 与其他网络请求开源库对比
除了Retrofit,如今Android中主流的网络请求框架有:
· Android-Async-Http
· Volley
· OkHttp
下面是简单介绍:
一图让你了解全部的网络请求库和他们之间的区别!
附:各个主流网络请求库的Github地址
· Android-Async-Http
· Volley
· OkHttp
· Retrofit
3. Retrofit 的具体使用
具体请看我写的文章:这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)
4. 源码分析
4.1 Retrofit的本质流程
一般从网络通信过程如下图:
· 其实Retrofit的本质和上面是一样的套路
· 只是Retrofit通过使用大量的设计模式进行功能模块的解耦,使得上面的过程进行得更加简单 & 流畅
如下图:
具体过程解释如下:
-
通过解析 网络请求接口的注解 配置 网络请求参数
-
通过 动态代理 生成 网络请求对象
-
通过 网络请求适配器 将 网络请求对象 进行平台适配
平台包括:Android、Rxjava、Guava和java8
-
通过 网络请求执行器 发送网络请求
-
通过 数据转换器 解析服务器返回的数据
-
通过 回调执行器 切换线程(子线程 ->>主线程)
-
用户在主线程处理返回结果
下面介绍上面提到的几个角色
特别注意:因下面的 源码分析 是根据 使用步骤 逐步带你debug进去的,所以必须先看文章这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)
4.2 源码分析
先来回忆Retrofit的使用步骤:
-
创建Retrofit实例
-
创建 网络请求接口实例 并 配置网络请求参数
-
发送网络请求,封装了 数据转换、线程切换的操作
-
处理服务器返回的数据
4.2.1 创建Retrofit实例
a. 使用步骤
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://fanyi.youdao.com/").addConverterFactory(GsonConverterFactory.create()).build();
b. 源码分析
Retrofit实例是使用建造者模式通过Builder类进行创建的
建造者模式:将一个复杂对象的构建与表示分离,使得用户在不知道对象的创建细节情况下就可以直接创建复杂的对象。具体请看文章:建造者模式(Builder Pattern)- 最易懂的设计模式解析
接下来,我将分五个步骤对创建Retrofit实例进行逐步分析
步骤1
<-- Retrofit类 -->public final class Retrofit {private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();// 网络请求配置对象(对网络请求接口中方法注解进行解析后得到的对象)// 作用:存储网络请求相关的配置,如网络请求的方法、数据转换器、网络请求适配器、网络请求工厂、基地址等private final HttpUrl baseUrl;// 网络请求的url地址private final okhttp3.Call.Factory callFactory;// 网络请求器的工厂// 作用:生产网络请求器(Call)// Retrofit是默认使用okhttpprivate final List<CallAdapter.Factory> adapterFactories;// 网络请求适配器工厂的集合// 作用:放置网络请求适配器工厂// 网络请求适配器工厂作用:生产网络请求适配器(CallAdapter)// 下面会详细说明private final List<Converter.Factory> converterFactories;// 数据转换器工厂的集合// 作用:放置数据转换器工厂// 数据转换器工厂作用:生产数据转换器(converter)private final Executor callbackExecutor;// 回调方法执行器private final boolean validateEagerly;
// 标志位
// 作用:是否提前对业务接口中的注解进行验证转换的标志位<-- Retrofit类的构造函数 -->
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories, Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = unmodifiableList(converterFactories); this.adapterFactories = unmodifiableList(adapterFactories); // unmodifiableList(list)近似于UnmodifiableList<E>(list)// 作用:创建的新对象能够对list数据进行访问,但不可通过该对象对list集合中的元素进行修改this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; ...// 仅贴出关键代码
}
成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量,即配置好:
· serviceMethod
:包含所有网络请求信息的对象
· baseUrl
:网络请求的url地址
· callFactory
:网络请求工厂
· adapterFactories
:网络请求适配器工厂的集合
· converterFactories
:数据转换器工厂的集合
· callbackExecutor
:回调方法执行器
所谓xxxFactory
、“xxx工厂”其实是设计模式中工厂模式的体现:将“类实例化的操作”与“使用对象的操作”分开,使得使用者不用知道具体参数就可以实例化出所需要的“产品”类。
具体请看我写的文章
简单工厂模式(SimpleFactoryPattern)- 最易懂的设计模式解析
工厂方法模式(Factory Method)- 最易懂的设计模式解析
抽象工厂模式(Abstract Factory)- 最易懂的设计模式解析
这里详细介绍一下:CallAdapterFactory
:该Factory
生产的是CallAdapter
,那么CallAdapter
又是什么呢?
####CallAdapter
详细介绍
· 定义:网络请求执行器(Call)的适配器
-
Call在Retrofit里默认是
OkHttpCall
-
在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
· 作用:将默认的网络请求执行器(OkHttpCall)转换成适合被不同平台来调用的网络请求执行器形式
- 如:一开始
Retrofit
只打算利用OkHttpCall
通过ExecutorCallbackCall
切换线程;但后来发现使用Rxjava
更加方便(不需要Handler来切换线程)。想要实现Rxjava
的情况,那就得使用RxJavaCallAdapterFactoryCallAdapter
将OkHttpCall
转换成Rxjava(Scheduler)
:
// 把response封装成rxjava的Observeble,然后进行流式操作
Retrofit.Builder.addCallAdapterFactory(newRxJavaCallAdapterFactory().create());
// 关于RxJava的使用这里不作更多的展开
- Retrofit还支持java8、Guava平台。
· 好处:用最小代价兼容更多平台,即能适配更多的使用场景
所以,接下来需要分析的步骤2、步骤3、步骤4、步骤4的目的是配置好上述所有成员变量
步骤2
我们先来看Builder类
请按下面提示的步骤进行查看
<-- Builder类-->
public static final class Builder {private Platform platform;private okhttp3.Call.Factory callFactory;private HttpUrl baseUrl;private List<Converter.Factory> converterFactories = new ArrayList<>();private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();private Executor callbackExecutor;private boolean validateEagerly;// 从上面可以发现, Builder类的成员变量与Retrofit类的成员变量是对应的
// 所以Retrofit类的成员变量基本上是通过Builder类进行配置
// 开始看步骤1<-- 步骤1 -->
// Builder的构造方法(无参)public Builder() {this(Platform.get());
// 用this调用自己的有参构造方法public Builder(Platform platform) ->>步骤5(看完步骤2、3、4再看)
// 并通过调用Platform.get()传入了Platform对象
// 继续看Platform.get()方法 ->>步骤2
// 记得最后继续看步骤5的Builder有参构造方法}
...
}<-- 步骤2 -->
class Platform {private static final Platform PLATFORM = findPlatform();// 将findPlatform()赋给静态变量static Platform get() {return PLATFORM; // 返回静态变量PLATFORM,即findPlatform() ->>步骤3}<-- 步骤3 -->
private static Platform findPlatform() {try {Class.forName("android.os.Build");// Class.forName(xxx.xx.xx)的作用:要求JVM查找并加载指定的类(即JVM会执行该类的静态代码段)if (Build.VERSION.SDK_INT != 0) {return new Android(); // 此处表示:如果是Android平台,就创建并返回一个Android对象 ->>步骤4}} catch (ClassNotFoundException ignored) {}try {// 支持Java平台Class.forName("java.util.Optional");return new Java8();} catch (ClassNotFoundException ignored) {}try {// 支持iOS平台Class.forName("org.robovm.apple.foundation.NSObject");return new IOS();} catch (ClassNotFoundException ignored) {}// 从上面看出:Retrofit2.0支持3个平台:Android平台、Java平台、IOS平台
// 最后返回一个Platform对象(指定了Android平台)给Builder的有参构造方法public Builder(Platform platform) --> 步骤5
// 说明Builder指定了运行平台为Androidreturn new Platform();}
...
}<-- 步骤4 -->
// 用于接收服务器返回数据后进行线程切换在主线程显示结果static class Android extends Platform {@OverrideCallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {return new ExecutorCallAdapterFactory(callbackExecutor);// 创建默认的网络请求适配器工厂// 该默认工厂生产的 adapter 会使得Call在异步调用时在指定的 Executor 上执行回调// 在Retrofit中提供了四种CallAdapterFactory: ExecutorCallAdapterFactory(默认)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory// 采用了策略模式}@Override public Executor defaultCallbackExecutor() {// 返回一个默认的回调方法执行器// 该执行器作用:切换线程(子->>主线程),并在主线程(UI线程)中执行回调方法return new MainThreadExecutor();}static class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());// 获取与Android 主线程绑定的Handler @Override public void execute(Runnable r) {handler.post(r);// 该Handler是上面获取的与Android 主线程绑定的Handler // 在UI线程进行对网络请求返回数据处理等操作。}}// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
//2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程}// 下面继续看步骤5的Builder有参构造方法
<-- 步骤5 -->
// Builder类的构造函数2(有参)public Builder(Platform platform) {// 接收Platform对象(Android平台)this.platform = platform;// 通过传入BuiltInConverters()对象配置数据转换器工厂(converterFactories)// converterFactories是一个存放数据转换器Converter.Factory的数组
// 配置converterFactories即配置里面的数据转换器converterFactories.add(new BuiltInConverters());// BuiltInConverters是一个内置的数据转换器工厂(继承Converter.Factory类)
// new BuiltInConverters()是为了初始化数据转换器}
对Builder类分析完毕,总结:Builder设置了默认的
· 平台类型对象:Android
· 网络请求适配器工厂:CallAdapterFactory
CallAdapter用于对原始Call进行再次封装,如Call到Observable
· 数据转换器工厂: converterFactory
· 回调执行器:callbackExecutor
特别注意,这里只是设置了默认值,但未真正配置到具体的Retrofit类的成员变量当中
步骤3
还是按部就班按步骤来观看
<-- 步骤1 -->
public Builder baseUrl(String baseUrl) {// 把String类型的url参数转化为适合OKhttp的HttpUrl类型HttpUrl httpUrl = HttpUrl.parse(baseUrl); // 最终返回带httpUrl类型参数的baseUrl()// 下面继续看baseUrl(httpUrl) ->> 步骤2return baseUrl(httpUrl);}<-- 步骤2 -->public Builder baseUrl(HttpUrl baseUrl) {//把URL参数分割成几个路径碎片List<String> pathSegments = baseUrl.pathSegments(); // 检测最后一个碎片来检查URL参数是不是以"/"结尾// 不是就抛出异常 if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);} this.baseUrl = baseUrl;return this;}
· 至此,步骤3分析完毕
· 总结:baseUrl()用于配置Retrofit类的网络请求url地址
将传入的String类型url转化为适合OKhttp的HttpUrl类型的url
步骤4
我们从里往外看,即先看GsonConverterFactory.creat()
public final class GsonConverterFactory extends Converter.Factory {<-- 步骤1 -->public static GsonConverterFactory create() {// 创建一个Gson对象return create(new Gson()); ->>步骤2}<-- 步骤2 -->public static GsonConverterFactory create(Gson gson) {// 创建了一个含有Gson对象实例的GsonConverterFactoryreturn new GsonConverterFactory(gson); ->>步骤3}private final Gson gson;<-- 步骤3 -->private GsonConverterFactory(Gson gson) {if (gson == null) throw new NullPointerException("gson == null");this.gson = gson;}
· 所以,GsonConverterFactory.creat()是创建了一个含有Gson对象实例的GsonConverterFactory,并返回给addConverterFactory()
· 接下来继续看:addConverterFactory()
// 将上面创建的GsonConverterFactory放入到 converterFactories数组
// 在第二步放入一个内置的数据转换器工厂BuiltInConverters()后又放入了一个GsonConverterFactorypublic Builder addConverterFactory(Converter.Factory factory) {converterFactories.add(checkNotNull(factory, "factory == null"));return this;}
· 至此,分析完毕
· 总结:步骤4用于创建一个含有Gson对象实例的GsonConverterFactory并放入到数据转换器工厂converterFactories里
-
即Retrofit默认使用Gson进行解析
-
若使用其他解析方式(如Json、XML或Protocobuf),也可通过自定义数据解析器来实现(必须继承 Converter.Factory)
步骤5
终于到了最后一个步骤了。
public Retrofit build() {<-- 配置网络请求执行器(callFactory)-->okhttp3.Call.Factory callFactory = this.callFactory;// 如果没指定,则默认使用okhttp// 所以Retrofit默认使用okhttp进行网络请求if (callFactory == null) {callFactory = new OkHttpClient();}<-- 配置回调方法执行器(callbackExecutor)-->Executor callbackExecutor = this.callbackExecutor;// 如果没指定,则默认使用Platform检测环境时的默认callbackExecutor// 即Android默认的callbackExecutorif (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor();}<-- 配置网络请求适配器工厂(CallAdapterFactory)-->List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);// 向该集合中添加了步骤2中创建的CallAdapter.Factory请求适配器(添加在集合器末尾)adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));// 请求适配器工厂集合存储顺序:自定义1适配器工厂、自定义2适配器工厂...默认适配器工厂(ExecutorCallAdapterFactory)<-- 配置数据转换器工厂:converterFactory -->// 在步骤2中已经添加了内置的数据转换器BuiltInConverters()(添加到集合器的首位)// 在步骤4中又插入了一个Gson的转换器 - GsonConverterFactory(添加到集合器的首二位)List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);// 数据转换器工厂集合存储的是:默认数据转换器工厂( BuiltInConverters)、自定义1数据转换器工厂(GsonConverterFactory)、自定义2数据转换器工厂....// 注:
//1. 获取合适的网络请求适配器和数据转换器都是从adapterFactories和converterFactories集合的首位-末位开始遍历
// 因此集合中的工厂位置越靠前就拥有越高的使用权限// 最终返回一个Retrofit的对象,并传入上述已经配置好的成员变量return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,callbackExecutor, validateEagerly);}
· 至此,步骤5分析完毕
· 总结:在最后一步中,通过前面步骤设置的变量,将Retrofit类的所有成员变量都配置完毕。
· 所以,成功创建了Retrofit的实例
总结
Retrofit 使用建造者模式通过Builder类建立了一个Retrofit实例,具体创建细节是配置了:
· 平台类型对象(Platform - Android)
· 网络请求的url地址(baseUrl)
· 网络请求工厂(callFactory)
默认使用OkHttpCall
· 网络请求适配器工厂的集合(adapterFactories)
本质是配置了网络请求适配器工厂- 默认是ExecutorCallAdapterFactory
· 数据转换器工厂的集合(converterFactories)
本质是配置了数据转换器工厂
· 回调方法执行器(callbackExecutor)
默认回调方法执行器作用是:切换线程(子线程 - 主线程)
由于使用了建造者模式,所以开发者并不需要关心配置细节就可以创建好Retrofit实例,建造者模式get。
在创建Retrofit对象时,你可以通过更多更灵活的方式去处理你的需求,如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能
2. 创建网络请求接口的实例
2.1 使用步骤
<-- 步骤1:定义接收网络数据的类 -->
<-- JavaBean.java -->
public class JavaBean {.. // 这里就不介绍了}<-- 步骤2:定义网络请求的接口类 -->
<-- AccessApi.java -->
public interface AccessApi {// 注解GET:采用Get方法发送网络请求// Retrofit把网络请求的URL分成了2部分:1部分baseurl放在创建Retrofit对象时设置;另一部分在网络请求接口设置(即这里)// 如果接口里的URL是一个完整的网址,那么放在创建Retrofit对象时设置的部分可以不设置@GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")// 接受网络请求数据的方法Call<JavaBean> getCall();// 返回类型为Call<*>,*是解析得到的数据类型,即JavaBean
}<-- 步骤3:在MainActivity创建接口类实例 -->
AccessApi NetService = retrofit.create(AccessApi.class);<-- 步骤4:对发送请求的url进行封装,即生成最终的网络请求对象 --> Call<JavaBean> call = NetService.getCall();
2.2 源码分析
· 结论:Retrofit是通过外观模式 & 代理模式 使用create()方法创建网络请求接口的实例(同时,通过网络请求接口里设置的注解进行了网络请求参数的配置)
-
外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) - 最易懂的设计模式解析
-
代理模式:通过访问代理对象的方式来间接访问目标对象。具体请看:代理模式(Proxy Pattern)- 最易懂的设计模式解析
· 下面主要分析步骤3和步骤4:
<-- 步骤3:在MainActivity创建接口类实例 -->
AccessApi NetService = retrofit.create(NetService.class);<-- 步骤4:对发送请求的url进行封装,即生成最终的网络请求对象 --> Call<JavaBean> call = NetService.getCall();
步骤3讲解:AccessApi NetService = retrofit.create(NetService.class);
public <T> T create(final Class<T> service) {if (validateEagerly) { // 判断是否需要提前验证eagerlyValidateMethods(service); // 具体方法作用:// 1. 给接口中每个方法的注解进行解析并得到一个ServiceMethod对象// 2. 以Method为键将该对象存入LinkedHashMap集合中// 特别注意:如果不是提前验证则进行动态解析对应方法(下面会详细说明),得到一个ServiceMethod对象,最后存入到LinkedHashMap集合中,类似延迟加载(默认)} // 创建了网络请求接口的动态代理对象,即通过动态代理创建网络请求接口的实例 (并最终返回)// 该动态代理是为了拿到网络请求接口实例上所有注解return (T) Proxy.newProxyInstance(service.getClassLoader(), // 动态生成接口的实现类 new Class<?>[] { service }, // 动态创建实例new InvocationHandler() { // 将代理类的实现交给 InvocationHandler类作为具体的实现(下面会解释)private final Platform platform = Platform.get();// 在 InvocationHandler类的invoke()实现中,除了执行真正的逻辑(如再次转发给真正的实现类对象),还可以进行一些有用的操作// 如统计执行时间、进行初始化和清理、对接口调用进行检查等。@Override public Object invoke(Object proxy, Method method, Object... args)throws Throwable {// 下面会详细介绍 invoke()的实现// 即下面三行代码ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);return serviceMethod.callAdapter.adapt(okHttpCall);}});}// 特别注意
// return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)
// 可以解读为:getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 即通过动态生成的代理类,调用interfaces接口的方法实际上是通过调用InvocationHandler对象的invoke()来完成指定的功能
// 先记住结论,在讲解步骤4的时候会再次详细说明<-- 关注点1:eagerlyValidateMethods() -->
private void eagerlyValidateMethods(Class<?> service) { Platform platform = Platform.get(); for (Method method : service.getDeclaredMethods()) { if (!platform.isDefaultMethod(method)) { loadServiceMethod(method); } // 将传入的ServiceMethod对象加入LinkedHashMap<Method, ServiceMethod>集合// 使用LinkedHashMap集合的好处:lruEntries.values().iterator().next()获取到的是集合最不经常用到的元素,提供了一种Lru算法的实现}
}
创建网络接口实例用了外观模式 & 代理模式:
使用外观模式进行访问,里面用了代理模式
1. 外观模式
外观模式:定义一个统一接口,外部与通过该统一的接口对子系统里的其他接口进行访问。具体请看:外观模式(Facade Pattern) - 最易懂的设计模式解析
Retrofit对象的外观(门店) = retrofit.create()
通过这一外观方法就可以在内部调用各个方法创建网络请求接口的实例和配置网络请求参数
大大降低了系统的耦合度
2. 代理模式
· 代理模式:通过访问代理对象的方式来间接访问目标对象
分为静态代理 & 动态代理:
-
静态代理:代理类在程序运行前已经存在的代理方式
-
动态代理:代理类在程序运行前不存在、运行时由程序动态生成的代理方式
具体请看文章代理模式(Proxy Pattern)- 最易懂的设计模式解析
· return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)
通过代理模式中的动态代理模式,动态生成网络请求接口的代理类,并将代理类的实例创建交给InvocationHandler``类
作为具体的实现,并最终返回一个动态代理对象。
生成实例过程中含有生成实现类的缓存机制(单例模式),下面会详细分析
使用动态代理的好处:
· 当NetService
对象调用getCall``()
接口中方法时会进行拦截,调用都会集中转发到 InvocationHandler#invoke (),可集中进行处理
· 获得网络请求接口实例上的所有注解
· 更方便封装ServiceMethod
下面看源码分析
下面将详细分析InvocationHandler``类 # invoke()
里的具体实现
new InvocationHandler() { private final Platform platform = Platform.get();@Override public Object invoke(Object proxy, Method method, Object... args)throws Throwable {// 将详细介绍下面代码// 关注点1// 作用:读取网络请求接口里的方法,并根据前面配置好的属性配置serviceMethod对象ServiceMethod serviceMethod = loadServiceMethod(method); // 关注点2// 作用:根据配置好的serviceMethod对象创建okHttpCall对象 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);// 关注点3// 作用:调用OkHttp,并根据okHttpCall返回rejava的Observe对象或者返回Callreturn serviceMethod.callAdapter.adapt(okHttpCall);}
下面将详细介绍3个关注点的代码。
关注点1: ServiceMethod serviceMethod = loadServiceMethod(method);
<-- loadServiceMethod(method)方法讲解 -->
// 一个 ServiceMethod 对象对应于网络请求接口里的一个方法
// loadServiceMethod(method)负责加载 ServiceMethod:ServiceMethod loadServiceMethod(Method method) {ServiceMethod result;// 设置线程同步锁synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);// ServiceMethod类对象采用了单例模式进行创建// 即创建ServiceMethod对象前,先看serviceMethodCache有没有缓存之前创建过的网络请求实例// 若没缓存,则通过建造者模式创建 serviceMethod 对象if (result == null) {// 下面会详细介绍ServiceMethod生成实例的过程result = new ServiceMethod.Builder(this, method).build();serviceMethodCache.put(method, result);}}return result;}
// 这里就是上面说的创建实例的缓存机制:采用单例模式从而实现一个 ServiceMethod 对象对应于网络请求接口里的一个方法
// 注:由于每次获取接口实例都是传入 class 对象
// 而 class 对象在进程内单例的,所以获取到它的同一个方法 Method 实例也是单例的,所以这里的缓存是有效的。
下面,我将分3个步骤详细分析serviceMethod
实例的创建过程:
步骤1:ServiceMethod``类
构造函数
<-- ServiceMethod 类 -->
public final class ServiceMethod {
final okhttp3.Call.Factory callFactory; // 网络请求工厂
final CallAdapter<?> callAdapter;
// 网络请求适配器工厂
// 具体创建是在new ServiceMethod.Builder(this, method).build()最后的build()中
// 下面会详细说明private final Converter<ResponseBody, T> responseConverter;
// Response内容转换器
// 作用:负责把服务器返回的数据(JSON或者其他格式,由 ResponseBody 封装)转化为 T 类型的对象;private final HttpUrl baseUrl; // 网络请求地址
private final String relativeUrl; // 网络请求的相对地址
private final String httpMethod; // 网络请求的Http方法
private final Headers headers; // 网络请求的http请求头 键值对
private final MediaType contentType; // 网络请求的http报文body的类型 private final ParameterHandler<?>[] parameterHandlers; // 方法参数处理器// 作用:负责解析 API 定义时每个方法的参数,并在构造 HTTP 请求时设置参数;// 下面会详细说明// 说明:从上面的成员变量可以看出,ServiceMethod对象包含了访问网络的所有基本信息<-- ServiceMethod 类的构造函数 -->
// 作用:传入各种网络请求参数
ServiceMethod(Builder<T> builder) {this.callFactory = builder.retrofit.callFactory(); this.callAdapter = builder.callAdapter; this.responseConverter = builder.responseConverter; this.baseUrl = builder.retrofit.baseUrl(); this.relativeUrl = builder.relativeUrl; this.httpMethod = builder.httpMethod; this.headers = builder.headers; this.contentType = builder.contentType; . this.hasBody = builder.hasBody; y this.isFormEncoded = builder.isFormEncoded; this.isMultipart = builder.isMultipart; this.parameterHandlers = builder.parameterHandlers;
}
步骤2:ServiceMethod的Builder()
public Builder(Retrofit retrofit, Method method) {this.retrofit = retrofit;this.method = method;// 获取网络请求接口方法里的注释this.methodAnnotations = method.getAnnotations();// 获取网络请求接口方法里的参数类型 this.parameterTypes = method.getGenericParameterTypes(); //获取网络请求接口方法里的注解内容 this.parameterAnnotationsArray = method.getParameterAnnotations(); }
步骤3:ServiceMethod的build()
// 作用:控制ServiceMethod对象的生成流程public ServiceMethod build() {callAdapter = createCallAdapter(); // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的网络请求适配器 -->关注点1responseType = callAdapter.responseType(); // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取该网络适配器返回的数据类型responseConverter = createResponseConverter(); // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的数据转换器 -->关注点3// 构造 HTTP 请求时,我们传递的参数都是String// Retrofit 类提供 converter把传递的参数都转化为 String // 其余类型的参数都利用 Converter.Factory 的stringConverter 进行转换// @Body 和 @Part 类型的参数利用Converter.Factory 提供的 requestBodyConverter 进行转换// 这三种 converter 都是通过“询问”工厂列表进行提供,而工厂列表我们可以在构造 Retrofit 对象时进行添加。for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation);}// 解析网络请求接口中方法的注解// 主要是解析获取Http请求的方法// 注解包括:DELETE、GET、POST、HEAD、PATCH、PUT、OPTIONS、HTTP、retrofit2.http.Headers、Multipart、FormUrlEncoded// 处理主要是调用方法 parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) ServiceMethod中的httpMethod、hasBody、relativeUrl、relativeUrlParamNames域进行赋值int parameterCount = parameterAnnotationsArray.length;// 获取当前方法的参数数量parameterHandlers = new ParameterHandler<?>[parameterCount];for (int p = 0; p < parameterCount; p++) {Type parameterType = parameterTypes[p];Annotation[] parameterAnnotations = parameterAnnotationsArray[p];// 为方法中的每个参数创建一个ParameterHandler<?>对象并解析每个参数使用的注解类型// 该对象的创建过程就是对方法参数中注解进行解析// 这里的注解包括:Body、PartMap、Part、FieldMap、Field、Header、QueryMap、Query、Path、Url parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);} return new ServiceMethod<>(this);<-- 总结 -->
// 1. 根据返回值类型和方法标注从Retrofit对象的的网络请求适配器工厂集合和内容转换器工厂集合中分别获取到该方法对应的网络请求适配器和Response内容转换器;
// 2. 根据方法的标注对ServiceMethod的域进行赋值
// 3. 最后为每个方法的参数的标注进行解析,获得一个ParameterHandler<?>对象
// 该对象保存有一个Request内容转换器——根据参数的类型从Retrofit的内容转换器工厂集合中获取一个Request内容转换器或者一个String内容转换器。}<-- 关注点1:createCallAdapter() -->private CallAdapter<?> createCallAdapter() {// 获取网络请求接口里方法的返回值类型Type returnType = method.getGenericReturnType(); // 获取网络请求接口接口里的注解// 此处使用的是@GetAnnotation[] annotations = method.getAnnotations(); try {return retrofit.callAdapter(returnType, annotations); // 根据网络请求接口方法的返回值和注解类型,从Retrofit对象中获取对应的网络请求适配器// 下面会详细说明retrofit.callAdapter() -- >关注点2}
...<-- 关注点2:retrofit.callAdapter() -->public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {return nextCallAdapter(null, returnType, annotations);}public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,Annotation[] annotations) {// 创建 CallAdapter 如下// 遍历 CallAdapter.Factory 集合寻找合适的工厂(该工厂集合在第一步构造 Retrofit 对象时进行添加(第一步时已经说明))// 如果最终没有工厂提供需要的 CallAdapter,将抛出异常for (int i = start, count = adapterFactories.size(); i < count; i++) {CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) {return adapter;}}<-- 关注点3:createResponseConverter() -->private Converter<ResponseBody, T> createResponseConverter() {Annotation[] annotations = method.getAnnotations();try {// responseConverter 还是由 Retrofit 类提供 -->关注点4return retrofit.responseBodyConverter(responseType, annotations);} catch (RuntimeException e) { throw methodError(e, "Unable to create converter for %s", responseType);}}<-- 关注点4:responseBodyConverter() -->public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {return nextResponseBodyConverter(null, type, annotations);}public <T> Converter<ResponseBody, T> nextResponseBodyConverter(Converter.Factory skipPast,int start = converterFactories.indexOf(skipPast) + 1;for (int i = start, count = converterFactories.size(); i < count; i++) {// 获取Converter 过程:(和获取 callAdapter 基本一致)Converter<ResponseBody, ?> converter =converterFactories.get(i).responseBodyConverter(type, annotations, this); // 遍历 Converter.Factory 集合并寻找合适的工厂(该工厂集合在构造 Retrofit 对象时进行添加(第一步时已经说明))// 由于构造Retroifit采用的是Gson解析方式,所以取出的是GsonResponseBodyConverter// Retrofit - Converters 还提供了 JSON,XML,ProtoBuf 等类型数据的转换功能。// 继续看responseBodyConverter() -->关注点5 }<-- 关注点5:responseBodyConverter() -->
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));// 根据目标类型,利用 Gson#getAdapter 获取相应的 adapterreturn new GsonResponseBodyConverter<>(gson, adapter);
}// 做数据转换时调用 Gson 的 API 即可。
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {private final Gson gson;private final TypeAdapter<T> adapter;GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {this.gson = gson;this.adapter = adapter;}@Override public T convert(ResponseBody value) throws IOException {JsonReader jsonReader = gson.newJsonReader(value.charStream());try {return adapter.read(jsonReader);} finally {value.close();}}
}
· 当选择了RxjavaCallAdapterFactory后,Rxjava通过策略模式选择对应的adapter
关于策略模式的讲解,请看文章策略模式(Strategy Pattern)- 最易懂的设计模式解析
· 具体过程是:根据网络接口方法的返回值类型来选择具体要用哪种CallAdapterFactory,然后创建具体的CallAdapter实例
采用工厂模式使得各功能模块高度解耦
· 上面提到了两种工厂:CallAdapter.Factory & Converter.Factory分别负责提供不同的功能模块
· 工厂负责如何提供、提供何种功能模块
· Retrofit 只负责提供选择何种工厂的决策信息(如网络接口方法的参数、返回值类型、注解等)
这正是所谓的高内聚低耦合,工厂模式get。
关于工厂模式请看我写的文章:
简单工厂模式(SimpleFactoryPattern)- 最易懂的设计模式解析
工厂方法模式(Factory Method)- 最易懂的设计模式解析
抽象工厂模式(Abstract Factory)- 最易懂的设计模式解析
终于配置完网络请求参数(即配置好ServiceMethod
对象)。接下来将讲解第二行代码:okHttpCall对象
的创建
第二行:OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
根据第一步配置好的ServiceMethod
对象和输入的请求参数创建okHttpCall
对象
<--OkHttpCall类 -->
public class OkHttpCall {private final ServiceMethod<T> serviceMethod; // 含有所有网络请求参数信息的对象 private final Object[] args; // 网络请求接口的参数 private okhttp3.Call rawCall; //实际进行网络访问的类 private Throwable creationFailure; //几个状态标志位 private boolean executed; private volatile boolean canceled; <--OkHttpCall构造函数 -->public OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) { // 传入了配置好的ServiceMethod对象和输入的请求参数this.serviceMethod = serviceMethod; this.args = args;
}
第三行:return serviceMethod.callAdapter.adapt(okHttpCall);
将第二步创建的OkHttpCall
对象传给第一步创建的serviceMethod
对象中对应的网络请求适配器工厂的adapt()
返回对象类型:Android默认的是Call<>
;若设置了RxJavaCallAdapterFactory,返回的则是Observable<>
<-- adapt()详解-->
public <R> Call<R> adapt(Call<R> call) {return new ExecutorCallbackCall<>(callbackExecutor, call); }ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.delegate = delegate; // 把上面创建并配置好参数的OkhttpCall对象交给静态代理delegate// 静态代理和动态代理都属于代理模式// 静态代理作用:代理执行被代理者的方法,且可在要执行的方法前后加入自己的动作,进行对系统功能的拓展this.callbackExecutor = callbackExecutor;// 传入上面定义的回调方法执行器// 用于进行线程切换 }
· 采用了装饰模式:ExecutorCallbackCall = 装饰者,而里面真正去执行网络请求的还是OkHttpCall
· 使用装饰模式的原因:希望在OkHttpCall发送请求时做一些额外操作。这里的额外操作是线程转换,即将子线程切换到主线程
-
OkHttpCall的enqueue()是进行网络异步请求的:当你调用OkHttpCall.enqueue()时,回调的callback是在子线程中,需要通过Handler转换到主线程进行回调。ExecutorCallbackCall就是用于线程回调;
-
当然以上是原生Retrofit使用的切换线程方式。如果你用Rxjava,那就不会用到这个ExecutorCallbackCall而是RxJava的Call,此处不过多展开
步骤4讲解:Call<JavaBean> call = NetService.getCall();
· NetService
对象实际上是动态代理对象Proxy.newProxyInstance()
(步骤3中已说明),并不是真正的网络请求接口创建的对象
· 当NetService
对象调用getCall()
时会被动态代理对象Proxy.newProxyInstance()
拦截,然后调用自身的InvocationHandler # invoke()
· invoke(Object proxy, Method method, Object... args)
会传入3个参数:Object proxy:
(代理对象)、
Method method
(调用的getCall()
)
Object... args
(方法的参数,即getCall(*)
中的*)
· 接下来利用Java反射获取到getCall()
的注解信息,配合args参数创建ServiceMethod对象
。
如上面步骤3描述,此处不再次讲解
最终创建并返回一个OkHttpCall
类型的Call对象
-
OkHttpCall
类是OkHttp
的包装类 -
创建了
OkHttpCall
类型的Call对象还不能发送网络请求,需要创建Request
对象才能发送网络请求
总结
Retrofit采用了 外观模式 统一调用创建网络请求接口实例和网络请求参数配置的方法,具体细节是:
· 动态创建网络请求接口的实例**(代理模式 - 动态代理)**
· 创建 serviceMethod
对象**(建造者模式 & 单例模式(缓存机制))**
· 对 serviceMethod
对象进行网络请求参数配置:通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络请求的url地址、网络请求执行器、网络请求适配器 & 数据转换器。(策略模式)
· 对 serviceMethod
对象加入线程切换的操作,便于接收数据后通过Handler从子线程切换到主线程从而对返回数据结果进行处理**(装饰模式)**
· 最终创建并返回一个OkHttpCall
类型的网络请求对象
3. 执行网络请求
· Retrofit
默认使用OkHttp
,即OkHttpCall``类
(实现了 retrofit2.Call<T>
接口)
但可以自定义选择自己需要的Call类
· OkHttpCall
提供了两种网络请求方式:
. 同步请求:OkHttpCall.execute()
. 异步请求:OkHttpCall.enqueue()
下面将详细介绍这两种网络请求方式。
对于OkHttpCall的enqueue()、execute()此处不往下分析,有兴趣的读者可以看OkHttp的源码
3.1 同步请求OkHttpCall.execute()
3.1.1 发送请求过程
· 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler
进行解析,再根据ServiceMethod
对象创建一个OkHttp
的Request
对象
· 步骤2:使用OkHttp
的Request
发送网络请求;
· 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response<T>
对象
3.1.2 具体使用
Response<JavaBean> response = call.execute();
上面简单的一行代码,其实包含了整个发送网络同步请求的三个步骤。
3.1.3 源码分析
@Override
public Response<T> execute() throws IOException {okhttp3.Call call;// 设置同步锁synchronized (this) {call = rawCall;if (call == null) {try {call = rawCall = createRawCall();// 步骤1:创建一个OkHttp的Request对象请求 -->关注1} catch (IOException | RuntimeException e) {creationFailure = e;throw e;}}}return parseResponse(call.execute());// 步骤2:调用OkHttpCall的execute()发送网络请求(同步)// 步骤3:解析网络请求返回的数据parseResponse() -->关注2
}<-- 关注1:createRawCall() -->
private okhttp3.Call createRawCall() throws IOException {Request request = serviceMethod.toRequest(args);// 从ServiceMethod的toRequest()返回一个Request对象okhttp3.Call call = serviceMethod.callFactory.newCall(request);// 根据serviceMethod和request对象创建 一个okhttp3.Requestif (call == null) {throw new NullPointerException("Call.Factory returned null.");}return call;
}<-- 关注2:parseResponse()-->
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();rawResponse = rawResponse.newBuilder().body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())).build();// 收到返回数据后进行状态码检查// 具体关于状态码说明下面会详细介绍int code = rawResponse.code();if (code < 200 || code >= 300) {}if (code == 204 || code == 205) {return Response.success(null, rawResponse);}ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);try {T body = serviceMethod.toResponse(catchingBody);// 等Http请求返回后 & 通过状态码检查后,将response body传入ServiceMethod中,ServiceMethod通过调用Converter接口(之前设置的GsonConverterFactory)将response body转成一个Java对象,即解析返回的数据// 生成Response类return Response.success(body, rawResponse);} catch (RuntimeException e) {... // 异常处理}
}
特别注意:
· ServiceMethod
几乎保存了一个网络请求所需要的数据
· 发送网络请求时,OkHttpCall
需要从ServiceMethod
中获得一个Request对象
· 解析数据时,还需要通过ServiceMethod
使用Converter
(数据转换器)转换成Java对象进行数据解析
为了提高效率,Retrofit还会对解析过的请求ServiceMethod
进行缓存,存放在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
对象中,即第二步提到的单例模式
· 关于状态码检查时的状态码说明:
以上便是整个以同步的方式发送网络请求的过程。
3.2 异步请求OkHttpCall.enqueue()
3.2.1 发送请求过程
· 步骤1:对网络请求接口的方法中的每个参数利用对应ParameterHandler
进行解析,再根据ServiceMethod
对象创建一个OkHttp
的Request
对象
· 步骤2:使用OkHttp
的Request
发送网络请求;
· 步骤3:对返回的数据使用之前设置的数据转换器(GsonConverterFactory)解析返回的数据,最终得到一个Response<T>
对象
· 步骤4:进行线程切换从而在主线程处理返回的数据结果
若使用了RxJava,则直接回调到主线程
异步请求的过程跟同步请求类似,唯一不同之处在于:异步请求会将回调方法交给回调执行器在指定的线程中执行。
指定的线程此处是指主线程(UI线程)
3.2.2 具体使用
call.enqueue(new Callback<JavaBean>() {@Overridepublic void onResponse(Call<JavaBean> call, Response<JavaBean> response) {System.out.println(response.isSuccessful());if (response.isSuccessful()) {response.body().show();}else {try {System.out.println(response.errorBody().string());} catch (IOException e) {e.printStackTrace();} ;}}
}
· 从上面分析有:call
是一个静态代理
· 使用静态代理的作用是:在okhttpCall发送网络请求的前后进行额外操作
这里的额外操作是:线程切换,即将子线程切换到主线程,从而在主线程对返回的数据结果进行处理
3.2.3 源码分析
<-- call.enqueue()解析 -->
@Override
public void enqueue(final Callback<T> callback) {delegate.enqueue(new Callback<T>() {// 使用静态代理 delegate进行异步请求 ->>分析1// 等下记得回来@Override public void onResponse(Call<T> call, final Response<T> response) {// 步骤4:线程切换,从而在主线程显示结果callbackExecutor.execute(new Runnable() {// 最后Okhttp的异步请求结果返回到callbackExecutor// callbackExecutor.execute()通过Handler异步回调将结果传回到主线程进行处理(如显示在Activity等等),即进行了线程切换// 具体是如何做线程切换 ->>分析2@Override public void run() {if (delegate.isCanceled()) {callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));} else {callback.onResponse(ExecutorCallbackCall.this, response);}}});}@Override public void onFailure(Call<T> call, final Throwable t) {callbackExecutor.execute(new Runnable() {@Override public void run() {callback.onFailure(ExecutorCallbackCall.this, t);}});}});}<-- 分析1:delegate.enqueue()解析 -->
@Override
public void enqueue(final Callback<T> callback) {okhttp3.Call call;Throwable failure;// 步骤1:创建OkHttp的Request对象,再封装成OkHttp.call// delegate代理在网络请求前的动作:创建OkHttp的Request对象,再封装成OkHttp.callsynchronized (this) {if (executed) throw new IllegalStateException("Already executed.");executed = true;call = rawCall;failure = creationFailure;if (call == null && failure == null) {try {call = rawCall = createRawCall(); // 创建OkHttp的Request对象,再封装成OkHttp.call// 方法同发送同步请求,此处不作过多描述 } catch (Throwable t) {failure = creationFailure = t;}}// 步骤2:发送网络请求// delegate是OkHttpcall的静态代理// delegate静态代理最终还是调用Okhttp.enqueue进行网络请求call.enqueue(new okhttp3.Callback() {@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)throws IOException {Response<T> response;try {// 步骤3:解析返回数据response = parseResponse(rawResponse);} catch (Throwable e) {callFailure(e);return;}callSuccess(response);}@Override public void onFailure(okhttp3.Call call, IOException e) {try {callback.onFailure(OkHttpCall.this, e);} catch (Throwable t) {t.printStackTrace();}}private void callFailure(Throwable e) {try {callback.onFailure(OkHttpCall.this, e);} catch (Throwable t) {t.printStackTrace();}}private void callSuccess(Response<T> response) {try {callback.onResponse(OkHttpCall.this, response);} catch (Throwable t) {t.printStackTrace();}}});}// 请回去上面分析1的起点<-- 分析2:异步请求后的线程切换-->
// 线程切换是通过一开始创建Retrofit对象时Platform在检测到运行环境是Android时进行创建的:(之前已分析过)
// 采用适配器模式
static class Android extends Platform {// 创建默认的回调执行器工厂// 如果不将RxJava和Retrofit一起使用,一般都是使用该默认的CallAdapter.Factory// 后面会对RxJava和Retrofit一起使用的情况进行分析@OverrideCallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {return new ExecutorCallAdapterFactory(callbackExecutor);}@Override public Executor defaultCallbackExecutor() {// 返回一个默认的回调方法执行器// 该执行器负责在主线程(UI线程)中执行回调方法return new MainThreadExecutor();}// 获取主线程Handlerstatic class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());@Override public void execute(Runnable r) {// Retrofit获取了主线程的handler// 然后在UI线程执行网络请求回调后的数据显示等操作。handler.post(r);}}// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
// 2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程处理返回结果(如显示在Activity等等)}
以上便是整个以 异步方式发送网络请求的过程。
5. 总结
Retrofit
本质上是一个 RESTful
的HTTP
网络请求框架的封装,即通过 大量的设计模式 封装了 OkHttp
,使得简洁易用。具体过程如下:
-
Retrofit
将Http
请求 抽象 成Java
接口 -
在接口里用 注解 描述和配置 网络请求参数
-
用动态代理 的方式,动态将网络请求接口的注解 解析 成
HTTP
请求 -
最后执行
HTTP
请求
最后贴一张非常详细的Retrofit
源码分析图:
请回去上面分析1的起点
<-- 分析2:异步请求后的线程切换–>
// 线程切换是通过一开始创建Retrofit对象时Platform在检测到运行环境是Android时进行创建的:(之前已分析过)
// 采用适配器模式
static class Android extends Platform {
// 创建默认的回调执行器工厂
// 如果不将RxJava和Retrofit一起使用,一般都是使用该默认的CallAdapter.Factory
// 后面会对RxJava和Retrofit一起使用的情况进行分析
@OverrideCallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {return new ExecutorCallAdapterFactory(callbackExecutor);
}@Override public Executor defaultCallbackExecutor() {// 返回一个默认的回调方法执行器// 该执行器负责在主线程(UI线程)中执行回调方法return new MainThreadExecutor();
}// 获取主线程Handler
static class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());@Override public void execute(Runnable r) {// Retrofit获取了主线程的handler// 然后在UI线程执行网络请求回调后的数据显示等操作。handler.post(r);}
}
// 切换线程的流程:
// 1. 回调ExecutorCallAdapterFactory生成了一个ExecutorCallbackCall对象
// 2. 通过调用ExecutorCallbackCall.enqueue(CallBack)从而调用MainThreadExecutor的execute()通过handler切换到主线程处理返回结果(如显示在Activity等等)
}
以上便是整个以 **异步方式**发送网络请求的过程。#### 5. 总结`Retrofit` 本质上是一个 `RESTful` 的`HTTP` 网络请求框架的封装,即通过 大量的设计模式 封装了 `OkHttp` ,使得简洁易用。具体过程如下:1. `Retrofit` 将 `Http`请求 抽象 成 `Java`接口2. 在接口里用 注解 描述和配置 网络请求参数3. 用动态代理 的方式,动态将网络请求接口的注解 解析 成`HTTP`请求4. 最后执行`HTTP`请求最后贴一张非常详细的`Retrofit`源码分析图:![在这里插入图片描述](https://img-blog.csdnimg.cn/f83b3beadc654a7c8e9a2a20e1b86609.gif#pic_center)
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- R语言自学笔记-----R的系统函数
R基础包中含有的函数种类很多,从计算功能上可以分为数学函数、概率函数、统计函数、矩阵运算函数、字符串函数、数据管理函数、逻辑判断函数、文件管理函数等。现在对上述函数进行简单的介绍,具体的更加复杂的操作可以自行参考R的帮助文档。 一、数学函…...
2024/4/30 16:24:13 - 反编译 java控制流程
java流程控制 用户交互Scanner 顺序结构 选择结构 循环结构 break&continue 练习 我们可以通过Scanner类来获取用户的输入。 基本语法 Scanner snew Scanner(System.in); 通过Scanner类的next()与nextLine()方法去获取输入的字符串…...
2024/4/30 20:24:51 - kubernetes -- k8s安装及配置全流程
### 1. 安装前的准备工作 sh # 关闭防火墙 systemctl stop firewalld systemctl disable firewalld # 查看hostname并修改 hostname # 查看本机hostname hostnamectl set-hostname k8s-master # 把本机名设置成k8s-master hostnamectl status # 查看修改结果 echo "…...
2024/4/30 19:38:49 - Java JUC 之何为 JUC
一、基本概念 1、JUC 的定义 所谓 JUC 是指 Package java.util.concurrent(并发),但实际上包含三个java.util 下面的Java内部包:concurrent、concurrent.atomic和concurrent.locks,分别是并发、并发原子性和并发加锁。 2、进程…...
2024/4/30 21:33:29 - 【推荐】2021年两会专题研究报告政策解读投资策略及行业分析产业发展前景(附件中为网盘地址,报告持续更新)
【推荐】2021年两会专题研究报告政策解读投资策略及行业分析产业发展前景(附件中为网盘地址,报告持续更新),每月至少更新一次。 文档列表内容:...
2024/4/30 19:26:51 - 低功耗存在感应雷达,雷达传感器技术发展,智能安防雷达应用
家庭生活中最重要的是我们的人身财产安全问题,如果有一套智能安防系统,不仅对歹徒起到威慑作用,而且全面的保护了家庭成员的生命财产安全。 当门窗遭到破坏时,感应装置会感应到,并将信号传送到防盗报警主机࿰…...
2024/4/30 19:38:10 - 使用 RxJs 实现一个支持 infinite scroll 的 Angular Component
首先看看我这个支持 infinite scroll 的 Angular 应用的运行时效果: https://jerry-infinite-scroller.stackblitz.io/ 滚动鼠标中键,向下滚动,可以触发 list 不断向后台发起请求,加载新的数据: 下面是具体的开发步骤…...
2024/5/1 0:39:41 - 春招答疑总结 | 细节决定成败,决战2022春招
22届有梦想的同学还会想:春招冲一冲大厂 23届同学也期盼着能在明年的秋招中收获大厂offer 大厂那么有吸引力,该如何准备? 除此之外,对校招敏感度及了解程度不高的22届、23届甚至是24届同学也可以了解学习的。 相信毕业找工作的…...
2024/4/30 17:08:11 - 47 转置卷积 [动手学深度学习v2]
转置卷积 卷积不会增大输入的高宽,通常要么不变、要么减半; 转置卷积则可以用来增大输入高宽。 Y[i:ih,j:jw]X[i,j]⋅KY[i: ih, j: jw]X[i, j] \cdot K Y[i:ih,j:jw]X[i,j]⋅K 如果卷积将输入从(h,w)(h, w)(h,w)变成了(h′,w′)(h, w)(h′,w′)&#x…...
2024/5/1 1:39:16 - 怕考试?先来分析分析
小白托福考试直通车---第一话 文章目录 小白托福考试直通车---第一话前言一、背景介绍 1.为什么要考托福以及为什么想记录这个话题?2.关于托福备考的经历和痛点二、托福考试简要分析 1.托福考试的内容及特点2.托福考试的备考思路3.托福复习计划及时间安排总结前言 …...
2024/4/14 15:55:52 - DCP博客
ETL-WEB监控平台 一、ETL监控平台的安装 1.1、按照ETL正常安装流程将ETL软件安装,此处使用的自定义安装,数据库使用的外置库, 安装完成后进入/dmdata/dmetl/web/tomcat/bin 启动tomcat:./startup.sh 1.2、通过浏览器访问http:…...
2024/5/1 1:04:24 - gethub官网ping不通
Github经常打不开,或者打开特别慢,网上也有很多解决方案,这里记录一下。实际只需要找到官网IP地址、域名IP地址、静态资源地址,然后配置本机hosts文件,刷新DNS缓存即可 github网址IP地址 github域名IP地址 https://fas…...
2024/5/1 1:39:24 - IIS网络环境搭建
1、工具准备:window-server-xxx版本虚拟机一台 2、在系统上安装IIS服务: i: 鼠标右键计算机,点击管理,调出服务器管理器 ii:出现添加角色弹窗,选择web服务器(iis),然后一直点击下一步,直至…...
2024/4/30 22:04:36 - matlab gui各控件使用模板记录及说明
最近重新学习了一下matlab GUI 的程序编写,用的是guide。虽然matlab现在出了app来编写GUI界面,但是以前的guide还是有使用,官方给出的文档给出了各种控件使用的模板例程,给出的例程都非常简单易懂,需要实现其他的功能只…...
2024/4/30 21:28:44 - Python 学习之 --- 语法部分(列表、元组、字典)
文章目录一、列表(List)1. 概述2. 列表操作2.1 列表示例与查看2.2 列表元素的增加2.3 列表元素的删除2.4 列表元素的截取3. 列表脚本操作符4. 列表函数5. 列表方法二、元组(Tuple)1. 概述2. 元组操作2.1 元组示例与查看2.2 元组的…...
2024/4/30 23:54:57 - 分布式与微服务——1-什么是Iaas,Paas和Saas
一 IaaS基础设施服务 IaaS: Infrastructure-as-a-Service(基础设施即服务) 第一层叫做IaaS,有时候也叫做Hardware-as-a-Service,几年前如果你想在办公室或者公司的网站上运行一些企业应用,你需要去买服务器ÿ…...
2024/5/1 0:57:30 - Linux路由缓存的前世今生
https://segmentfault.com/a/1190000020183650https://segmentfault.com/a/1190000020183650 3.6版本一定算得上是Linux网络子系统中一个特别的版本, 这个版本(补丁patch)移除了查找FIB之前的缓存查找。本文就来谈谈路由缓存的前世今生。 几个基本概念 为了让本文的阅读曲线…...
2024/4/20 4:17:32 - 柚缘航海:一文带你了解PayPal提现方式
2021年PayPal全球用户数已经超过3.78亿,覆盖了超过200个国家。对于跨境电商者来说,PayPal可以说是全球最大的在线支付通道,它方便、简单且使用广泛。 无论是做外贸B2B还是做跨境电商B2C,都会接触到PayPal收款。所以本期光子易与大…...
2024/4/19 5:00:48 - dubbo源码分析第十六篇一dubbo负载均衡-BroadcastCluster广播
文章目录前言BroadcastCluster作用BroadcastClusterBroadcastClusterInvoker前言 cluster可选策略 mockorg.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper failoverorg.apache.dubbo.rpc.cluster.support.FailoverCluster failfastorg.apache.dubbo.rpc.clust…...
2024/4/29 1:21:46 - Matlab多字符字符串数组写入和读出(cell)
想要写入多组数据的文件名,利用字符串时: strname [819292623\,819292647\,819292671\,819292687\,819292703\,819292719\]; 输出 strname是 strname 819292623\819292647\819292671\819292687\819292703\819292719\ 提取strname(1)时发现结果是8…...
2024/4/19 19:28:43
最新文章
- Android性能优化面试题汇总
Android的性能优化涉及多个方面,如启动优化、稳定性优化、内存优化、网络优化、电量优化、安全优化等方面。 一、稳定性优化 1.1 你们做了哪些稳定性方面的优化 随着项目的逐渐成熟,用户基数逐渐增多,DAU持续升高,我们遇到了很多稳定性方面的问题,对于我们技术同学遇到…...
2024/5/1 2:26:17 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 【Godot4自学手册】第三十五节摇杆控制开门
本节主要实现,在地宫墙壁上安装一扇门,在核实安装一个开门的摇杆,攻击摇杆,打开这扇门,但是只能攻击一次,效果如下: 一、添加完善节点 切换到underground场景,先将TileMap修改一下…...
2024/4/30 1:43:17 - 前端路径问题总结
1.相对路径 不以/开头 以当前资源的所在路径为出发点去找目标资源 语法: ./表示当前资源的路径 ../表示当前资源的上一层路径 缺点:不同位置,相对路径写法不同2.绝对路径 以固定的路径作为出发点作为目标资源,和当前资源所在路径没关系 语法:以/开头,不同的项目中,固定的路径…...
2024/4/30 5:38:52 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/4/29 23:16:47 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/30 18:14:14 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/30 18:21:48 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/30 22:21:04 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/4/26 23:04:58 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/30 9:43:22 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57