【面试 反思】Retrofit源码与设计 7 连问
前言
在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格,本质上只是对OkHttp进行封装,今天我们根据几个问题来进一步学习一下Retrofit的源码与设计思想。
1. 使用方法
直接看一下官方介绍的使用方法。
public final class SimpleService {public static final String API_URL = "https://api.github.com";public static class Contributor {public final String login;public final int contributions;public Contributor(String login, int contributions) {this.login = login;this.contributions = contributions;}}public interface GitHub {@GET("/repos/{owner}/{repo}/contributors")Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);}public static void main(String... args) throws IOException {// Create a very simple REST adapter which points the GitHub API.Retrofit retrofit =new Retrofit.Builder().baseUrl(API_URL).client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build()).addConverterFactory(GsonConverterFactory.create()).build();// Create an instance of our GitHub API interface.GitHub github = retrofit.create(GitHub.class);// Create a call instance for looking up Retrofit contributors.Call<List<Contributor>> call = github.contributors("square", "retrofit");// Fetch and print a list of the contributors to the library.List<Contributor> contributors = call.execute().body();for (Contributor contributor : contributors) {System.out.println(contributor.login + " (" + contributor.contributions + ")");}}
}
可以简单的概括成三步:
- 构建
retrofit实例。 - 构建
API接口实例。 - 执行请求,解析响应。
2. 流程解析
我们按照它的使用方法来分析一下它的流程。
2.1 构建 Retrofit 实例
从使用方法可以看出是使用建造者模式来构建实例。
Retrofit retrofit =new Retrofit.Builder().baseUrl(API_URL).client(new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS).build()).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();
这一步就不具体展开了,看几个参数。
public static final class Builder {//实际的请求调用,如 okhttp3.OkHttpClientprivate @Nullable okhttp3.Call.Factory callFactory;//基础URL,如:域名private @Nullable HttpUrl baseUrl;//数据转换器列表private final List<Converter.Factory> converterFactories = new ArrayList<>();//请求适配器列表private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
2.2 构建 API 接口实例
按照官方的使用方法介绍,我们会将我们的API方法放在一个接口中,然后通过注解来设置请求参数。在使用时,通过retrofit.create(Class<T>)方法将这个接口实例化,然后调用其方法。 如:
public interface GitHub {@GET("/repos/{owner}/{repo}/contributors")Call<List<Contributor>> contributors(@Path("owner") String owner, @Path("repo") String repo);
}//实例化API接口
GitHub github = retrofit.create(GitHub.class);
//调用接口中某条API
Call<List<Contributor>> call = github.contributors("square", "retrofit");
看一下源码
public <T> T create(final Class<T> service) {//验证 api servicevalidateServiceInterface(service);return (T)//这里采用了动态代理模式, service 就是被代理类//todo 为什么要采用动态代理,有什么好处吗?用别的行不行?Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] {service},new InvocationHandler() {private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// If the method is a method from Object then defer to normal invocation.if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;Platform platform = Platform.get();//如果不是系统默认方法,通过loadServiceMethod()方法返回一个ServiceMethod,并调用invoke方法return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}
做了两件事:
- 验证我们的API接口类。
- 利用动态代理在运行期间实例化API接口。
private void validateServiceInterface(Class<?> service) {//service 必须是 interface,否则抛出异常if (!service.isInterface()) {throw new IllegalArgumentException("API declarations must be interfaces.");}...省略代码...//是否立即验证API接口中的所有方法,由用户设置,默认为falseif (validateEagerly) {Platform platform = Platform.get();//遍历 service 中定义的所有方法for (Method method : service.getDeclaredMethods()) {//如果该方法不是系统默认方法且方法修饰符不是静态方法就执行loadServiceMethod方法if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {//加载请求方法。loadServiceMethod(method);}}}}
从这我们也可以看出,我们的API方法必须方法接口中。如果开始验证接口,会遍历其声明的所有方法,过滤掉系统默认方法与静态方法,然后执行loadServiceMethod(method)。
扩充一下:
getMethods(): 返回由类或接口声明的以及从超类和超接口继承的所有公共方法。getDeclaredMethods(): 返回类声明的方法,包括 public, protected, default (package),但不包括继承的方法。所以,相对比于 getMethods 方法,getDeclaredMethods速度更快,尤其是在复杂的类中,如在Activity类中。
最终都是通过loadServiceMethod(method) 方法来加载一个ServiceMethod。
看一下HttpServiceMethod.parseAnnotations()方法,我将其简化了一下,如下:
HttpServiceMethod.javastatic <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {//获取方法的注解信息Annotation[] annotations = method.getAnnotations();//适配器类型,就是Retrofit.addCallAdapterFactory()添加的类型。Type adapterType;//方法的返回类型adapterType = method.getGenericReturnType();//实例化一个 CallAdapter 对象CallAdapter<ResponseT, ReturnT> callAdapter =createCallAdapter(retrofit, method, adapterType, annotations);//检查 responseType,如果不合格则抛出异常Type responseType = callAdapter.responseType();//实例化一个Converter对象,将 okhttp3.ResponseBody 转换成 ResponseT 类型Converter<ResponseBody, ResponseT> responseConverter =createResponseConverter(retrofit, method, responseType);okhttp3.Call.Factory callFactory = retrofit.callFactory;//不是kotlin挂起方法,返回 CallAdapted,其实也就是调用 callAdapter.adapter 方法return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);}
实例化了 ServiceMethod 后,调用invoke方法。
HttpServiceMethod.java@Overridefinal @Nullable ReturnT invoke(Object[] args) {//新建一个 OkHttpCall 请求Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);//然后调用 adapt 方法,CallAdapted 有重写 adapt 方法,然后调用 callAdapter.adapt(call) 方法return adapt(call, args);}protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
从上述代码中可以看出,invoke方法就是实例化一个Call请求,然后调用adapter方法,在这里adapter是一个抽象方法,所以具体实现方法就需要看它的具体实现类CallAdapter。 这里的 CallAdapter 就是通过.addCallAdapterFactory()方法所添加的CallAdapter,以及根据平台默认提供的DefaultCallAdapterFactory中的CallAdapter,执行其adapter方法,最终返回Call<Object>。
2.3 执行请求,解析响应
在上一步中,我们对API接口进行了实例化,通过CallAdapter对请求进行适配,最终得到一个Call<Object>对象。
接着下一步,就是执行这个Call<Object>请求,最终得到我们想要的Object对象。
例如一开始使用方法中所介绍的:
//已经得到了Call<List<Contributor>>对象,执行call,得到List<Contributor>
List<Contributor> contributors = call.execute().body();
调用 execute 执行同步请求获取到Response,然后获取其请求体。
OkHttpCall.java@Overridepublic Response<T> execute() throws IOException {okhttp3.Call call;synchronized (this) {//判断请求是否已经被执行,如果已被执行则抛出异常if (executed) throw new IllegalStateException("Already executed.");executed = true;//获取最原始的请求,通过createRawCall()创建okhttp3.Callcall = getRawCall();}if (canceled) {call.cancel();}//执行请求,并且解析响应,将okhttp3.response 转换成 retrofit2.responsereturn parseResponse(call.execute());}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;}/*** 解析响应,就是就okhttp3.response 转换成 retrofit2.response*/Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {...省略代码...try {//利用converter转换成我们期望的类型T body = responseConverter.convert(catchingBody);return Response.success(body, rawResponse);} catch (RuntimeException e) {...省略代码...}
从源码中也可以看出,请求的实际工作还是通过okhttp来完成的,这边Retrofit就是负责请求与响应转换工作,将retrofit2.Call转换成okhttp3.Call,将okhttp3.response转换成retrofit2.response。
3. 为什么要引入CallAdapter与Converter?
如果你熟悉okHttp的话,你应该知道,当我们请求的时候,要先通过OkHttpClient.newCall(request)方法将request转换成Call对象,然后再执行这个Call对象拿到response。
但是Retrofit不光光只支持Call,他还可以将请求适配成Observable类型,方便与RxJava2结合起来一起使用。这就是通过CallAdapter来进行适配工作的,例如通过默认的DefaultCallAdapterFactory将请求转换成Call<Object>,通过RxJava2CallAdapter将请求转换成Observable<Object>。
回到okHttp,大部分业务情况下,我们在拿到响应体后都会将其进行反序列化成对象,方便调用。显然,Retrofit就考虑到了这一点,所以他默认提供了GsonConverterFactory,来帮助我们做这一步反序列化工作。这就是通过Converter来完成的,同时它也支持用户进行自定义。
4. CallAdapter 是如何工作的?
作为请求适配器,我们将CallAdapter工作流程分为三步:添加、匹配、工作。
添加
可以通过addCallAdapterFactory(CallAdapter.Factory)方法来添加请求适配器工厂类,添加成功后会被保存在callAdapterFactories列表中。另外,Retrofit会根据Platform来添加默认的请求适配器,例如:DefaultCallAdapterFactory等等,同样也加入到callAdapterFactories列表中。
匹配
思考一下:所有添加的请求适配器都会被保存在callAdapterFactories列表中,那在实际请求中是如何匹配出相对应的适配器的呢?
在HttpServiceMethod.parseAnnotations()方法中,我们有实例化一个CallAdapter对象。(具体流程就不再次展开了,请回头看 2.2 构建 API 接口实例 中所介绍内容。)
HttpServiceMethod.javastatic <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {//实例化一个 CallAdapter 对象CallAdapter<ResponseT, ReturnT> callAdapter =createCallAdapter(retrofit, method, adapterType, annotations);···省略代码···//不是kotlin挂起方法,返回 CallAdapted,其实也就是调用 callAdapter.adapter 方法return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);}
匹配工作其实就在createCallAdapter()方法中,一步步走下来,最终到Retrofit.nextCallAdapter()方法中:
Retrofit.javapublic CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {int start = callAdapterFactories.indexOf(skipPast) + 1;for (int i = start, count = callAdapterFactories.size(); i < count; i++) {//通过方法的返回值类型与注解信息来找到匹配的CallAdapterCallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);if (adapter != null) {return adapter;}}···省略代码···//如果找不到匹配的CallAdapter,则抛出异常throw new IllegalArgumentException(builder.toString());}
简单概括一下,就是通过方法的返回值类型与注解信息,遍历callAdapterFactories列表,找到匹配的CallAdapter,如果找不到则抛出IllegalArgumentException异常。
工作
找到匹配的CallAdapter后,剩下就是看看他是如何工作的。
如上一步匹配过程所介绍,在找到匹配的callAdapter后,会通过它来实例化一个CallAdapted对象。
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) {//将responseConverter传给父类。super(requestFactory, callFactory, responseConverter);this.callAdapter = callAdapter;}@Overrideprotected ReturnT adapt(Call<ResponseT> call, Object[] args) {return callAdapter.adapt(call);}}
CallAdapted很简单,就是继承了HttpServiceMethod,然后复写了adapt方法。也就是说,最终执行的,其实就是我们上一步匹配到的CallAdapter对象的adapt方法。
比如匹配到的是DefaultCallAdapterFactory中的CallAdapter,最终执行的就是其adapt方法,具体代码细节这边就不展示了,有兴趣同学请自行查阅。
另外,我这边展示的是不支持kotlin挂起函数的情况,当然即使是kotlin挂起函数,过程也是一样的,也是执行其子类的adapt方法。

5. Converter 是如何工作的?
作为数据转换器,我们同样将Converter工作流程分为三步:添加、匹配、工作。
添加
可以通过addConverterFactory(Converter.Factory)方法来添加数据装换器工厂类,添加成功后会被保存在converterFactories列表中。另外,Retrofit会根据Platform来添加默认的数据转换器,例如OptionalConverterFactory,同样也加入到converterFactories列表中。
匹配
跟上述所介绍的 4. CallAdapter 是如何工作的 一样,同样在HttpServiceMethod.parseAnnotations()方法中,会实例化一个Converter对象。
匹配工作其实就在createResponseConverter()方法中,一步步走下来,最终到Retrofit.nextResponseBodyConverter()方法中:
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {int start = converterFactories.indexOf(skipPast) + 1;for (int i = start, count = converterFactories.size(); i < count; i++) {//通过转换类型与注解信息来找到匹配的ConverterConverter<ResponseBody, ?> converter =converterFactories.get(i).responseBodyConverter(type, annotations, this);if (converter != null) {//noinspection uncheckedreturn (Converter<ResponseBody, T>) converter;}}···省略代码···//如果找不到匹配的Converter,则抛出异常throw new IllegalArgumentException(builder.toString());}
简单概括一下,就是通过转换类型与注解信息,遍历converterFactories列表,找到匹配的Converter,如果找不到则抛出IllegalArgumentException异常。
工作
跟上述所介绍的 4. CallAdapter 是如何工作的 一样,我们找到匹配的Converter后,通过它来实例化一个CallAdapted。但不同的是,我们会将responseConverter传给父类,也就是HttpServiceMethod,然后当其调用invoke方法时,我们通过responseConverter来实例化一个OkHttpCall对象,最终将这个OkHttpCall对象传给adapter方法执行。
最终当执行请求时,OkHttpCall执行parseResponse来解析响应,调用responseConverter.convert()方法,将ResponseBody数据转换我们想要的类型。
6. 说说使用到了哪些设计模式
动态代理模式
Retrofit内部通过动态代理+反射来拿到用户定义在接口中的请求参数,从而来构建实际请求。具体细节这边就不再次展开了,可以回头查看 2.2 构建 API 接口实例 这一部分内容。
为什么要使用动态代理来获取API方法?
不知道你们有没有这个疑问,为什么我们的API方法需要定义在interface中呢?又为什么要通过动态代理+反射的形式来拿到请求参数呢?
Retrofit按照RESTful风格设计并通过注解来定义API方法的请求参数,并将这些API方法放到interface中。因为interface是不能被实例化的,所以这里采用动态代理在运行期间实例化API接口,获取到方法的请求参数。
再进一步:
解耦,将实际业务与Retrofit隔离开来。用户只需通过注解方法来定义请求参数,而实际请求的构建则通过Retrofit内部来实现。
此时再反过来看为何放在interface中?相信你心中已有答案了吧。
策略模式
当针对同一类型问题有多种处理方式,仅仅是具体行为有差别时,就可以使用策略模式。
例如:Retrofit中的请求适配器
- 抽象策略:
CallAdapter。 - 策略具体实现:
DefaultCallAdapterFactory.get()、RxJava2CallAdapter。
即提供默认的请求适配器,也支持用户自定义,符合开闭原则,达到很好的可扩展性。
适配器模式
Retrofit会帮我们构建实际请求,内部通过默认的DefaultCallAdapterFactory来将请求转换成Call<Object>,同时Retrofit也支持其它平台,比如为了适配RxJava特性,将请求转换成Observable<Object>。
- Target(目标角色):
Call<Object>,Observable<Object>。 - adaptee(需要适配的对象):
OkHttpCall。 - adapter(适配器):
DefaultCallAdapterFactory.get()、RxJava2CallAdapter。
工厂方法模式
我们以Converter来举例。
- 抽象工厂:
Converter.Factory。 - 具体工厂:
GsonConverterFactory、BuiltInConverters等等。 - 抽象产品:
Converter。 - 具体产品:
GsonResponseBodyConverter、GsonRequestBodyConverter、ToStringConverter等等。
这边就不具体展开分析各个类了,有兴趣的同学可自行查阅。
建造者模式
在构建Retrofit实例的时候,就用到了建造者模式。建造者模式在开源库中的出现的次数真的很频繁,为了适配不同的用户的各种需求,需提供各种各样的参数与方法来供用户自行选择,所以使用建造者模式,之所以很常见,是因为这样很合理。
7. 使用过程中踩过什么坑?
关于BaseUrl的使用曾经踩过坑,某天我将baseUrl改了一下,然后发现请求接口时服务器一直返回404,但是当我尝试用Postman去调试接口的时候,发现接口是好的,也就推测出来是我的代码出问题了。
最终发现,问题出在:拼接成完整的URL时api被删除了。
Base URL: http://example.com/api/
Endpoint: /foo/bar/
Result: http://example.com/foo/bar/
正确的使用方式为:Endpoint不以斜杠开头。
Base URL: http://example.com/api/
Endpoint: foo/bar/
Result: http://example.com/api/foo/bar/
总结
本文,我们以几个问题的形式展开来对Retrofit源码及设计思想进行解析,相信你对源码有了进一步的了解。Retrofit本质只是对okHttp进行封装,出发点肯定是让网络请求变得更加容易,考虑适配各种用户需求,Jake Wharton大神用了很多设计模式,真的太让人膜拜了。
到此,关于Retrofit的源码解析就结束啦。
推荐更多Android学习笔记参考
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
相关文章:
【面试 反思】Retrofit源码与设计 7 连问
前言 在实际项目中往往是使用Retrofit来做网络请求工作。Retrofit采用RESTful风格,本质上只是对OkHttp进行封装,今天我们根据几个问题来进一步学习一下Retrofit的源码与设计思想。 1. 使用方法 直接看一下官方介绍的使用方法。 public final class S…...
flutter 雷达图
通过CustomPainter自定义雷达图 效果如下 主要代码 import package:flutter/material.dart; import dart:math; import dash_painter.dart; import model/charts_model.dart;class RadarChart extends StatelessWidget {final List<ChartModel> list;final double maxV…...
机器学习之损失函数(Loss Function)
损失函数(Loss Function)是机器学习和深度学习中的关键概念,它用于衡量模型的预测与实际目标之间的差异或误差。损失函数的选择对于模型的训练和性能评估至关重要,不同的任务和问题通常需要不同的损失函数。 以下是一些常见的损失…...
创邻科技张晨:图数据库,激活数据要素的新基建
“数据经济时代,数据要素产业链的各细分领域均蕴含机遇,图技术作为网络协同和数据智能的底层发动机,将深度掘金数字中国价值潜能”。 8月22日,在2023中国(南京)国际软件产品和信息服务交易博览会的信息技术…...
使用端口映射实现Spring Boot服务端接口的公网远程调试:详细配置与步骤解析
文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...
stm32之点亮LED
今天,记录一下stm32如何点亮一个LED,程序本身十分简单,但主要是学习编程的格式。 led.h #ifndef _led_H #define _led_H#include "system.h"/* LED时钟端口、引脚定义 */ #define LED1_PORT GPIOB #define LED1_PIN GPIO_Pin_5 #d…...
SA8000认证的难点及注意事项
SA8000认证是什么? SA8000即“社会责任标准”,是Social Accountability 8000的英文简称,由社会责任国际组织(SAI)制定与执行,是全球首个道德规范国际标准。自1997年问世以来,它创建了一个衡量社会责任的共同语言&#…...
Java可视化物联网智慧工地SaaS平台源码:人脸识别考勤
基于微服务JavaSpring Cloud Vue UniApp MySql实现的智慧工地云平台源码 智慧工地是指利用云计算、大数据、物联网、移动互联网、人工智能等技术手段,为建筑施工现场提供智能硬件及物联网平台的解决方案,以实现建筑工地的实时化、可视化、多元化、智慧化…...
告别数字化系统“物理叠加”,华为云推动智慧门店价值跃迁
文|智能相对论 作者|叶远风 有大屏幕滚动播放广告; 有人脸识别系统让消费者自助结账; 有订单管理系统综合分析一段时间内总体经营情况; 有全门店监控直连总部机房; …… 以搭载数字化系统的硬件设备为表面特征的智慧门店&a…...
k8s 常用命令(四)
12、删除pod中的nginx服务及service [rootmaster ~]# kubectl delete deployment nginx -n kube-public [rootmaster ~]# kubectl delete svc -n kube-public nginx-service 13、查看endpoint的信息 [rootmaster ~]# kubectl get endpoints 14、修改/更新(镜像、…...
大语言模型的分布式训练
什么是大语言模型 大语言模型(Large Language Model,缩写LLM),也称大型语言模型,是一种人工智能模型,旨在理解和生成人类语言。它们在大量的文本数据上进行训练,可以执行广泛的任务,包括文本总结、翻译、情感分析等等。LLM的特点是规模庞大,包含数十亿的参数,帮助它们学习语言…...
【JavaEE】Spring全家桶实现AOP-统一处理
【JavaEE】AOP(2) 文章目录 【JavaEE】AOP(2)1. 统一登录校验处理1.1 自定义拦截器1.2 将自定义拦截器加入到系统配置1.3 测试1.4 对于静态资源的处理1.5 小练习:统一登录拦截处理1.6 拦截器原理1.6.1 执行流程1.6.2 源…...
HQL解决连续三天登陆问题
1.背景 统计连续登录天数超过3天的用户,输出信息包括:用户id,登录天数,起始时间,结束时间; 2.准备数据 -- 建表 create table if not exists user_login_3days(user_id STRING,login_date date );--插入…...
(一)Docker简介(一篇足以)
一、简介 一个项目环境配置相当麻烦,如果换一台机器跑起来,所有配置就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环…...
RK3568 安卓源码编译
一.repo安卓编译工具 项目模块化/组件化之后各模块也作为独立的 Git 仓库从主项目里剥离了出去,各模块各自管理自己的版本。Android源码引用了很多开源项目,每一个子项目都是一个Git仓库,每个Git仓库都有很多分支版本,为了方便统…...
第4篇:vscode+platformio搭建esp32 arduino开发环境
第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 1.配置默认安装路径,安装到D盘。 打开环境变量,点击新建 输入变量名PLATFORMIO_CORE_DIR与路径:D:\PLATF…...
2023前端面试笔记 —— CSS3
系列文章目录 内容链接2023前端面试笔记HTML52023前端面试笔记CSS3 文章目录 系列文章目录前言一、CSS选择器的优先级二、通过 CSS 的哪些方式可以实现隐藏页面上的元素三、px、em、rem之间有什么区别?四、让元素水平居中的方法有哪些五、在 CSS 中有哪些定位方式六…...
iOS 如何对整张图分别局部磨砂,并完全贴合
官方磨砂方式 - (UIVisualEffectView *)effectView{if(!_effectView){UIBlurEffect *blur [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];_effectView [[UIVisualEffectView alloc] initWithEffect:blur];}return _effectView; }使用这种方式对一张图的上半部分和…...
Packet_Tracer的使用
一、实验目的: 通过该实验了解Packet Tracer的使用方法,能够用Packet Tracer建立和模拟网络模型。 二、主要任务: 1.熟悉PT的界面,了解按键用途。 2.尝试自己建立一个小型网络,并测试连通性。 3.学习P…...
WPF如果未定义绑定的属性,程序如何处理
问题:wpf中,<Button IsEnabled"{Binding IsValid1}"></Button>,如果没定义绑定的属性IsValid1,可以正常用吗 解答:在 WPF 中,如果没有定义绑定的属性 IsValid1,会导致绑…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
