文章目录

  • 使⽤示例
  • 实现原理
    • 接口的合规验证
    • 自定义api的动态代理
    • 如何创建的OKHttp
    • 如何解析返回结果
    • 返回的UI线程切换原理
  • 参考

使⽤示例

以下Retrofit解析版本库均为写作时的最新版本:2.9.0。

先在安卓的构建文件中添加依赖:

 implementation 'com.squareup.retrofit2:retrofit:2.9.0'

使用步骤:

  1. 创建⼀个 interface 作为 Web Service 的请求集合,在⾥⾯⽤注解(Annotation)写⼊需要配置的请求⽅法
public interface GitHubService {@GET("/notifications")Call<ResponseBody> getNotici();@GET("/users/LucasXu01")Call<ResponseBody> getUser();}
  1. 在正式代码⾥⽤ Retrofit 创建出 interface 的实例
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").build();GitHubService service = retrofit.create(GitHubService.class);
  1. 调⽤创建出的 Service 实例的对应⽅法,创建出相应的可以⽤来发起⽹络请求的Call 对象
 Call<ResponseBody> repos = service.getNotici();Call<ResponseBody> user = service.getUser();
  1. 使⽤ Call.execute() 或者 Call.enqueue() 来发起请求

    repos.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)            {}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t) {}});
    

实现原理

Retrofit基于OkHttp进行功能细化以更好使用。

以下是Retrofit的整体实现图,对照着图看接下来的源码解析,会不至于嵌套太深而忘记起点,从而达到整体掌握的全局观视角。总而言之:对照着图,往下看源码解析;看下面的源码解析,多返回来看看图,知道自己走到了哪一步。

通过Retrofit.create(Class) ⽅法创建出 Service interface 的实例,从⽽使得 Service 中配置的⽅法变得可⽤,下面这段create()代码是 Retrofit 结构的核⼼;

 public <T> T create(final Class<T> service) {//验证接口validateServiceInterface(service);return (T)// 动态代理Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] {service},new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});} 

create()代码主要做两件事:验证接口合法性、动态代理接口。

接口的合规验证

验证服务接口validateServiceInterface(service):

 private void validateServiceInterface(Class<?> service) {//service必须是接口if (!service.isInterface()) {throw new IllegalArgumentException("API declarations must be interfaces.");}
// service不能继承泛型接口Deque<Class<?>> check = new ArrayDeque<>(1);check.add(service);while (!check.isEmpty()) {Class<?> candidate = check.removeFirst();if (candidate.getTypeParameters().length != 0) {StringBuilder message =new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());if (candidate != service) {message.append(" which is an interface of ").append(service.getName());}throw new IllegalArgumentException(message.toString());}Collections.addAll(check, candidate.getInterfaces());}// 对每个service方法进行初始化加载试错if (validateEagerly) {Platform platform = Platform.get();for (Method method : service.getDeclaredMethods()) {if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {loadServiceMethod(method);}}}}

可以看到,validateServiceInterface(service):验证了需要代理的service必须是接口,且不能继承泛型接口。该方法最后会对每个service方法进行初始化加载试错。

if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {}java接口默认不许有默认实现,但是java8开始可以给接口的一些方法写默认实现了;java接口不允许写静态方法,但java8开始允许接口里写静态方法了。然而Retrofit是不支持的这些的,Retrofit不接纳这两种方式为service接口里的方法。

自定义api的动态代理

有关动态代理的知识可以看这篇文章:Java内功修炼系列:代理模式及动态代理,这里默认读者已有相关知识。

代理:创造一个类并实现了某个接口,这个具体new出来的类就实际上代理这个接口的具体实现,这个类就是代理类。

动态代理:这个代理类在运行时(匿名类都是运行时创建,非编译时)生成。

Retrofit.create() ⽅法内部,使⽤的是Proxy.newProxyInstance() ⽅法来创建 Service 实例。这个⽅法会为参数中的多个 interface (具体到 Retrofifit 来说,是固定传⼊⼀个 interface)创建⼀个对象,这个对象实现了所有 interface 的每个⽅法,并且每个⽅法的实现都是雷同的:调⽤对象实例内部的⼀个 InvocationHandler 成员变量的invoke() ⽅法,并把⾃⼰的⽅法信息传递进去。这样就在实质上实现了代理逻辑:interface 中的⽅法全部由⼀个另外设定的 InvocationHandler 对象来进⾏代理操作。并且,这些⽅法的具体实现是在运⾏时⽣成 interface 实例时才确定的,⽽不是在编译时(虽然在编译时就已经可以通过代码逻辑推断出来)。这就是⽹上所说的「动态代理机制」的具体含义。

Proxy.newProxyInstance()有三个参数:

1 提供一个类加载器

2 提供代理的接口对象

3 真正代理:实际做操作的对象

由动态代理的知识我们可知,无论哪个service,最终都会调用到invoke方法

public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果方法是Object中的方法,不代理,自己用自己的方法。// 确保代理的方法不是Object中的方法if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;// 不代理java8的默认方法return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);
}

platform.isDefaultMethod(method) 判断有没有java8的types, 主要是对不同版本的java进行了处理,使得最终都能得到正确结果。

  boolean isDefaultMethod(Method method) {return hasJava8Types && method.isDefault();}

loadServiceMethod(method).invoke(args);拆开来看,首先通过loadServiceMethod获得一个HttpServiceMethod的实现类,然后调用它的invoke方法。接下来就看这两个方法。

loadServiceMethod:一个带缓存的加载,核心: result = ServiceMethod.parseAnnotations(this, method);

  ServiceMethod<?> loadServiceMethod(Method method) {ServiceMethod<?> result = serviceMethodCache.get(method);if (result != null) return result;synchronized (serviceMethodCache) {result = serviceMethodCache.get(method);if (result == null) {result = ServiceMethod.parseAnnotations(this, method);serviceMethodCache.put(method, result);}}return result;}

看看result = ServiceMethod.parseAnnotations(this, method)方法:

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);Type returnType = method.getGenericReturnType();if (Utils.hasUnresolvableType(returnType)) {throw methodError(method,"Method return type must not include a type variable or wildcard: %s",returnType);}if (returnType == void.class) {throw methodError(method, "Service methods cannot return void.");}return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

点进HttpServiceMethod.parseAnnotations()中一探究竟:

 static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {...if (!isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);} else if (continuationWantsResponse) {//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.return (HttpServiceMethod<ResponseT, ReturnT>)new SuspendForResponse<>(requestFactory,callFactory,responseConverter,(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);} else {//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.return (HttpServiceMethod<ResponseT, ReturnT>)new SuspendForBody<>(requestFactory,callFactory,responseConverter,(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,continuationBodyNullable);}}

可以看到,一般在java下开发的非协程挂起函数,直接new了一个HttpServiceMethod的子类CallAdapted进行返回。

CallAdapted的细致分析后面再深入,走到这步回过头去,再看下invoke方法。

HttpServiceMethod中找到了invoke()方法:

  @Overridefinal @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);}

可以看到,因为上面返回的是一个HttpServiceMethod的子类CallAdapted,所以这里的adapt(call, args)执行的是类CallAdapted中的adapt()方法:

  static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {private final CallAdapter<ResponseT, ReturnT> callAdapter;CallAdapted(RequestFactory requestFactory,okhttp3.Call.Factory callFactory,Converter<ResponseBody, ResponseT> responseConverter,CallAdapter<ResponseT, ReturnT> callAdapter) {super(requestFactory, callFactory, responseConverter);this.callAdapter = callAdapter;}@Overrideprotected ReturnT adapt(Call<ResponseT> call, Object[] args) {return callAdapter.adapt(call);}}

可以看到adapter的实现就一行return callAdapter.adapt(call)。这个⽅法会使⽤⼀个 CallAdapter 对象来把 OkHttpCall 对象进⾏转换,⽣成⼀个新的对象,默认情况下,该方法返回的是⼀个 ExecutorCallbackCall对象 ,它的主要作⽤是把操作切回主线程后再交给 Callback 。

另外,如果有⾃定义的 CallAdapter,这⾥也可以⽣成别的类型的对象,例如RxJava 的 Observable ,来让 Retrofit 可以和 RxJava 结合使⽤。

如何创建的OKHttp

上面说到,在HttpServiceMethod中的invoke()方法创建了OkHttpCallnew OkHttpCall<>(requestFactory, args, callFactory,responseConverter)

OkHttpCall 是 retrofit.Call 的⼦类。这⾏代码负责将ServiceMethod 解读到的信息( RequestFactory 、OkHttpClient 和ResponseConverter )封装进 OkHttpCall ;⽽这个对象可以在需要的时候(例如它的 enqueue() ⽅法被调⽤的时候),利⽤ RequestFactory 和 OkHttpClient 来创建⼀个 okhttp3.Call对象,并调⽤这个 okhttp3.Call 对象来进⾏⽹络请求的发起,然后利⽤ResponseConverter 对结果进⾏预处理之后,交回给 Retrofifit 的Callback 。

如下代码所示,在repos.enqueue(new Callback<ResponseBody>() {}时,执行的是OkHttpCallenqueue方法:

  @Overridepublic void enqueue(final Callback<T> callback) {...okhttp3.Call call;synchronized (this) {...call = rawCall = createRawCall();}...call.enqueue(new okhttp3.Callback() {@Overridepublic void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {Response<T> response;// 注释1response = parseResponse(rawResponse);callback.onResponse(OkHttpCall.this, response);}@Overridepublic void onFailure(okhttp3.Call call, IOException e) {callFailure(e);}private void callFailure(Throwable e) {callback.onFailure(OkHttpCall.this, e);}});}

可以看到,真正执行call.enqueue的是okhttp3的call,它在OkHttpCall内部生成,主要通过call = rawCall = createRawCall();:

private okhttp3.Call createRawCall() throws IOException {okhttp3.Call call = callFactory.newCall(requestFactory.create(args));if (call == null) {throw new NullPointerException("Call.Factory returned null.");}return call;}

可以看到OkHttpCall中通过callFactory创建,我们知道OkHttp3的的call的创建如下代码所示: client.newCall(request).enqueue(new Callback()),它需要一个OkHttp3中的Request对象,它在Retrofit中通过RequestFactory进行构建,并传给callFactory创造出OkHttp3的Call对象。

对照着前文的结构图看,否则容易迷失。callFactory对象在HttpServiceMethod中的invoke()方法中创建OkHttpCall时被赋值,它的创建在HttpServiceMethodparseAnnotations()方法中:

 okhttp3.Call.Factory callFactory = retrofit.callFactory;

可以看到,这个用来创建OkHttp3的Call的工厂来自于Retrofit,在Retrofit中的构建者模式中可以找到:

   public Retrofit build() {okhttp3.Call.Factory callFactory = this.callFactory;if (callFactory == null) {callFactory = new OkHttpClient();}..return new Retrofit(callFactory,baseUrl,unmodifiableList(converterFactories),unmodifiableList(callAdapterFactories),callbackExecutor,validateEagerly);}}

真相大白了!原来Retrofit用来创建OkHttp3的Call的工厂就是OkHttp3的OkHttpClient:callFactory = new OkHttpClient();

如何解析返回结果

回看前结构图,可以知道请求结果的返回,在前一章节:如何创建的OKHttp 的注释1处:

response = parseResponse(rawResponse);

查看OkHttpCall的parseResponse方法:

 Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();...    ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);try {T body = responseConverter.convert(catchingBody);return Response.success(body, rawResponse);} catch (RuntimeException e) {}}

可以看到,网络请求的结果最终会被一个返回结果转换器进行转换之后再返回,不难猜测,这个responseConverter转换器的功能,就是将网络返回的不易看懂的数据转换为我们需要的自定义的Object接收对象,如UserBean、XXListBean等。现在我们已经有了原始的返回对象,接下只需要知道这个responseConverter对象是什么以及它的convert()方法即可。

还是在HttpServiceMethodinvoke()方法中寻找OkHttpCall的创建过程,responseConverter作为构建参数被传递进来。而它又是在HttpServiceMethodparseAnnotations()方法中被create:

Converter<ResponseBody, ResponseT> responseConverter =createResponseConverter(retrofit, method, responseType);

createResponseConverter()方法

  private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter(Retrofit retrofit, Method method, Type responseType) {Annotation[] annotations = method.getAnnotations();try {return retrofit.responseBodyConverter(responseType, annotations);} catch (RuntimeException e) { // Wide exception range because factories are user code.throw methodError(method, e, "Unable to create converter for %s", responseType);}}

很容易又继续追到retrofit.responseBodyConverter(responseType, annotations)方法:

  public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {return nextResponseBodyConverter(null, type, annotations);}

继续追到Retrofit里的nextResponseBodyConverter(null, type, annotations);

  public <T> Converter<ResponseBody, T> nextResponseBodyConverter(...) {...for (int i = start, count = converterFactories.size(); i < count; i++) {Converter<ResponseBody, ?> converter =converterFactories.get(i).responseBodyConverter(type, annotations, this);if (converter != null) {//noinspection uncheckedreturn (Converter<ResponseBody, T>) converter;}}...}

converterFactories.get(i).responseBodyConverter(type, annotations, this);可以看到,这个返回结果转换器responseConverter来自于converterFactories,在Retrofit中寻找converterFactories,在Retrofit的建造者Builder的build()方法中我们找到:

 public Retrofit build() {...// ensures correct behavior when using converters that consume all types.converterFactories.add(new BuiltInConverters());converterFactories.addAll(this.converterFactories);converterFactories.addAll(platform.defaultConverterFactories());...}

第一行和第三行被add的converter不是我们自定义的,暂不去看,去寻找第二行addAll(this.converterFactories)中的converterFactories,看它是哪里被赋值构建的,我们于是又在Retrofit中找到了addConverterFactory()方法:

    public Builder addConverterFactory(Converter.Factory factory) {converterFactories.add(Objects.requireNonNull(factory, "factory == null"));return this;}

这个方法是不是很眼熟,回忆一下我们平时是怎么使用Retrofit的:

        Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").addConverterFactory(gsonConvertFactory).build();

这不就是我们在一开始在创建Retrofit时常用的添加Gson转换器的地方嘛!原来这个Gson转化器在Retrofit里是最终被用到OkHttpCall的parseResponse()中的responseConverter.convert(catchingBody);啊!原来Retrofit内部的网络结果转换器是这样工作的!

返回的UI线程切换原理

相比OkHttp3,Retrofit在使用时一个很明显的方便之处就是在 Call.execute() 或者 Call.enqueue() 来发起请求后的返回结果事件中,不需要再切换线程,因为此刻,它已经在安卓的UI主线程当中了。

repos.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response)            {}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t) {}});

在在执行retrofit2.Callenqueue()方法时,我们需要注意,这个Call对象我们刚刚是在HttpServiceMethod中的invoke()方法创建的OkHttpCallnew OkHttpCall<>(requestFactory, args, callFactory,responseConverter),但是这个OkHttpCall会经过adapt转为真正进行请求的另一个类ExecutorCallbackCall,它里面会持有OkHttpCall并在执行enqueue方法时去执行OkHttpCallenqueue方法。

我们其实可以顺着HttpServiceMethod中的CallAdapted子类找到callAdapter.adapt(call),顺着这个adapt(call)方法不断溯源,可以找到这个callAdapter的赋值过程实际上是在Retrofit中的build函数中,再在Retrofit中的build()方法中不断溯源,最终可以找到它在Retrofit中赋值如下所示:

 public CallAdapter<?, ?> nextCallAdapter(...) {...for (int i = start, count = callAdapterFactories.size(); i < count; i++) {CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);if (adapter != null) {return adapter;}}...}

可以看到,callAdapter最终取自 callAdapterFactories,它是定义在Retrofit中的callAdapter的工厂List,具体每一个callAdapter由这个List中的某一个工厂的get()方法进行创建。

那么问题来了, callAdapterFactories这个List中的工厂又在哪里来的呢?再去寻找 callAdapterFactories,如下所示, callAdapterFactories中添加的是platform.defaultCallAdapterFactories(callbackExecutor)对象:

 public Retrofit build() {...Executor callbackExecutor = this.callbackExecutor;if (callbackExecutor == null) {//注释2callbackExecutor = platform.defaultCallbackExecutor();}List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));...}

那么问题来了:

  1. platform.defaultCallAdapterFactories()这个方法干嘛的?

  2. callbackExecutor哪里来的,有什么用?

首先看platform.defaultCallAdapterFactories()方法:

List<? extends CallAdapter.Factory> defaultCallAdapterFactories(@Nullable Executor callbackExecutor) {DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);return hasJava8Types? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory): singletonList(executorFactory);}

该方法主要创建了DefaultCallAdapterFactory类,这里需要中断注意下,刚刚说了,具体每一个callAdaptercallAdapterFactories这个List中的某一个工厂的get()方法进行创建的,所以,我们此刻应该去DefaultCallAdapterFactory类中看看它的get()方法做了什么:

public @Nullable CallAdapter<?, ?> get(...) {...return new CallAdapter<Object, Call<?>>() {@Overridepublic Type responseType() {return responseType;}@Overridepublic Call<Object> adapt(Call<Object> call) {return executor == null ? call : new ExecutorCallbackCall<>(executor, call);}};}

来了!get()方法它创建了一个CallAdapter进行返回,这不就是在HttpServiceMethodinvoke()方法返回的那个adapt(),我们再看一下:

  @Overridefinal @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);}

对了!是它了,HttpServiceMethodinvoke()方法里执行的adapt()就是return executor == null ? call : new ExecutorCallbackCall<>(executor, call);这个了!它把OkHttpCall传给了ExecutorCallbackCall,通过前面的分析不难想象,最终的请求ExecutorCallbackCall还是通过这个OkHttpCall来进行的,那么,第二个问题还没解决,这个executor是什么?干嘛用的?

回到callAdapterFactories添加platform.defaultCallAdapterFactories(callbackExecutor)对象那里,可以看到注释2:

 callbackExecutor = platform.defaultCallbackExecutor();

这个就是哪个executor!看它的实现:

    public Executor defaultCallbackExecutor() {return new MainThreadExecutor();}

MainThreadExecutor():

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

这不就是在切线程!而且拿的是Looper.getMainLooper(),难不成这就是Retrofit返回直接在UI主线程的原因嘛?汇过去看这个executor的使用地方,也就是ExecutorCallbackCall去执行enqueue()进行网络请求时的使用情况:

 static final class ExecutorCallbackCall<T> implements Call<T> {final Executor callbackExecutor;final Call<T> delegate;ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {this.callbackExecutor = callbackExecutor;this.delegate = delegate;}public void enqueue(final Callback<T> callback) {this.delegate.enqueue(new Callback<T>() {public void onResponse(Call<T> call, Response<T> response) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {...callback.onResponse(ExecutorCallbackCall.this, response);});}public void onFailure(Call<T> call, Throwable t) {ExecutorCallbackCall.this.callbackExecutor.execute(() -> {callback.onFailure(ExecutorCallbackCall.this, t);});}});}}

由上面的分析可知,ExecutorCallbackCall中的delegate就是 adapt()传过去的OkHttpCallcallbackExecutor就是我们刚刚看到的MainThreadExecutor,ExecutorCallbackCall在处理返回结果时,使用的是MainThreadExecutor,而MainThreadExecutor又将这个线程切换到了UI主线程中,至此,完成了网络请求过程子线程个UI主线程的切换。

参考

https://square.github.io/retrofit/

真懂?Retrofit完整剖析相关推荐

  1. 深入理解JavaScript系列:《你真懂JavaScript吗?》答案详解

    介绍 昨天发的<大叔手记(19):你真懂JavaScript吗?>里面的5个题目,有很多回答,发现强人还是很多的,很多人都全部答对了. 今天我们来对这5个题目详细分析一下,希望对大家有所帮 ...

  2. 华为OD机试真题大全完整目录

    华为OD机试真题大全完整目录 专栏说明如下 专栏目录 专栏说明如下 内容:华为OD机试真题大全 数量:406篇博文(2023年5月16日截止) 更新时间至:2023年5月16日(后续加上去的博文,会每 ...

  3. 直击美团“远程面试”现场,面试官竟反问:你真懂数据库事务吗?

    为什么要记录这次面试经历? 疫情形势仍然十分严峻,很多企业在招聘时会采取网络面试或是远程面试的方式来保证面试流程的顺利进行.马上就要迎来金三银四,希望你们同我一样,面试顺利,顺利拿下自己心仪的offe ...

  4. 阿里巴巴王坚:不理解在线,就没有真懂互联网

    阿里巴巴王坚:不理解在线,就没有真懂互联网 王坚现任阿里巴巴技术委员会主席,他是2008年9月加入阿里的.乌镇互联网大会,在台下听他演讲,觉得很精彩,就给马云发短信,说"王坚是阿里的财富啊& ...

  5. 利用上下文常识,让AI读懂不完整人类指令

    https://www.toutiao.com/a6687850036513997324/ 大数据文摘专栏作品 作者:Christopher Dossman 编译:笪洁琼.conrad.云舟 呜啦啦啦 ...

  6. 一文看懂物联网完整产业链条

    随着未来中国物联网产业规模的不断壮大,以及应用领域的不断拓展,我国正处于物联网快速发展时期,产业规模将突破万亿,产业链基本完善.在芯片制造.读写器制造.标签成品制造.系统集成.网络提供与运营服务和应用 ...

  7. 实现粗糙表面_什么是表面粗糙度,你真懂吗?

    前言 表面粗糙度对大部分参与滑动接触的表面而言是非常重要的.因为磨损的原始速率及持续的性质等因素高度依赖这一特性.这些表面一般是承重面,而且需标识粗糙度以确保预计用途的适用性. 许多零部件需要具有特定 ...

  8. 秦朔-王坚:不理解在线,就没有真懂互联网

    王坚现任阿里巴巴技术委员会主席,他是2008年9月加入阿里的.乌镇互联网大会,在台下听他演讲,觉得很精彩,就给马云发短信,说"王坚是阿里的财富啊",马云回了一句--"他是 ...

  9. 你真懂吗?C++ 四种 cast 转换

    目录 C++11 四种 cast 转换 1.const_cast 2.static_cast 3.dynamic_cast 4.reinterpret_cast typeid C++11 四种 cas ...

  10. 【博客497】k8s cgroup原理完整剖析

    k8s cgroup原理 k8s cgroup设计层级: k8s完整的cgroup hierarchy root| +- kube-reserved| || +- kubelet (kubelet p ...

最新文章

  1. 2021年大数据Spark(四十四):Structured Streaming概述
  2. java 三个参数的运算符,java – 三个参数运算符:局部变量可能尚未初始化
  3. RecyclerView悬浮标题
  4. 如何用eclispe远程调试tomcat--转载
  5. Nginx的Gzip模块配置指令(三)
  6. 主流java框架理解
  7. 怎样找回由于IO设备错误移动硬盘的文件
  8. linux修改文件夹及其下面所有文件的权限(文件夹权限)
  9. VC++的学习(基于VS2008)——windows程序内部运行机制
  10. 嵌入式电路设计(入门)
  11. Centos7.9源码编译安装Apache
  12. SQL Server Management Studio格式化SQL工具(可免费)
  13. linux 浏览器缓存目录在哪里找,火狐浏览器缓存文件在什么位置?缓存文件位置分享...
  14. iTOP4412 uboot移植教程
  15. 编程英文单词的标准缩写
  16. 通过Windows的bat方式一键给计算机网卡替换IP地址
  17. Debian 10 使用 rz sz 命令
  18. win7计算机u盘不显示盘符,win7系统识别U盘但不显示盘符该如何解决?
  19. 在Blender中使用代码控制人物模型的头部姿态 - 前置知识
  20. java 中 IO 的流的种类及BIO、NIO、AIO 有什么区别?

热门文章

  1. React学习笔记之四---抽离组件
  2. 回眸 2020,展望 2021
  3. NOI 十连测 Zbox loves stack
  4. 如何有效开展小组教学_如何有效地开展小组教学研究初探
  5. 例行检查软件列表,突然发现不明sangforvnc应用
  6. Nofollow标签的写法以及运用
  7. 博士申请 | 蒙纳士大学(苏州)陈存建老师招收人工智能方向全奖博士生
  8. cf_Gym 101572 K.Kayaking Trip
  9. 宁静以致远,淡泊以明志
  10. 虚拟机ping通主机步骤_6在购买虚拟主机服务之前,请按照操作步骤进行操作