目录

1.Retrofit是什么?

2.Retrofit如何使用?

2.1创建HTTP请求的API接口

2.2请求执行

3.注解详情

3.1请求方法注解

3.2标记请求数据类型

3.3注解参数

4.GSON和Converter

5.RxJava和CallAdapter

6.自定义Converter

7.自定义CallAdapter

8.其他说明

8.1Retrofit.Builder

8.2Retrofit的Url组合规则


retrofit网络请求相关依赖包说明

implementation 'com.google.code.gson:gson:2.8.0'(gson生成和解析库)
implementation 'com.squareup.okhttp3:okhttp:3.9.1'(开源的网络请求库)
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'(支持okhttp跟踪到一个网络请求的所有状态,包括请求头、请求体、响应行、 响应体,方便调试)
implementation 'com.squareup.retrofit2:retrofit:2.3.0'(实现将HTTP请求转换为Java接口)
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'(配合Rxjava 使用)
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'(转换器,请求结果转换成Model)
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.2.1'(一种帮助你做异步的框架. 类似于 AsyncTask. 但其灵活性和扩展性远远强于前者. 从能力上讲, 如果说 AsycnTask 是 DOS 操作系统, RxJava 是 Window 操作系统。)

网络请求的大概分为四部分,retrofit主要负责封装网络请求,okhttp负责完成网络请求,logging-interceptor负责网络请求和响应的输出日志方便调试,rxjava一种帮助你做异步的框架,gson负责json的生成和解析,将请求结果转为相应的Model;

Retrofit官网:https://square.github.io/retrofit/#restadapter-configuration

1.Retrofit是什么?

Retrofit将您的HTTP API转换为Java接口;Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责网络请求接口的封装;

2.Retrofit如何使用?

    public static final ApiService service = new Retrofit.Builder().baseUrl("http://www.baidu.com").build().create(ApiService.class);

2.1创建HTTP请求的API接口

1)通过Retrofit.Builder内部类创建Retrofit实例,需要调用baseUrl(url)指定请求的跟路径和调用build()方法执行Retrofit实例创建,在build()方法内部会创建Retrofit需要的其他默认参数;

注意:下面三种方式设置URL都可以,但是推荐第三种方式设置,当把url传入给Retrofit对URL进行解析,HttpUrl会解析请求的协议(http和https,或者其他协议),域名,端口等信息,实际上HttpUrl会为域名后面加上“/”;

"http://www.baidu.com/?key=value"
"http://www.baidu.com"
"http://www.baidu.com/"  //推荐格式

最终请求的时候跟路径会处理为"http://www.baidu.com/"格式;

http://www.baidu.com/?c=api&a=getList&p=1&model=1&page_id=0&create_time=0&client=android&version=1.3.0&time=1584694243&device_id=000000000000000&show_sdv=1

2)调用Retrofit类create()创建接口ApiService实例

创建接口ApiService实例时借助Java提供Proxy动态代理类实现ApiService代理和实例的创建;

public interface ApiService {@GET("group/{id}/users")Call<ResponseBody> groupList(@Path("id") int groupId);
}

2.2请求执行

//请求回调在UI线程执行
apiService.groupList(23).enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {try {response.body().string();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t) {t.printStackTrace();}});

3.注解详情

3.1请求方法注解

既然涉及HTTP的请求接口,则需要涉及HTTP请求的方法;

* The relative path for a given method is obtained from an annotation on the method describing
* the request type. The built-in methods are {@link retrofit2.http.GET GET},
* {@link retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH
* PATCH}, {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and
* {@link retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with
* {@link HTTP @HTTP}. For a dynamic URL, omit the path on the annotation and annotate the first
* parameter with {@link Url @Url}.
请求方法 备注说明
GET retrofit2.http.GET

HTTP的请求方法,请求路径是baseurl和GET等方法指定相对路径组成一个完整路径;通过@Path方法可以动态设置路径中指定参数{id};@Url可以动态替换相对路径,同时也可以@Path方法可以动态设置路径中指定参数{id};

POST retrofit2.http.POST
PATCH retrofit2.http.PATCH
HEAD retrofit2.http.HEAD
DELETE retrofit2.http.DELETE
OPTIONS retrofit2.http.OPTIONS
HTTP retrofit2.http.HTTP HTTP请求可以指定以上所有请求方法,同时也可以@Path方法可以动态设置路径中指定参数{id};

下面时通过GET方法和HTTP定义的请求接口;

@GET("group/{id}/users")
Observable<String> groupList(@Path("id") int groupId);@HTTP(method = "GET", path = "group/{id}/users",hasBody = false)
Observable<String> groupList(@Path("id") int groupId);//通过Url动态设置相对路径,请求方法不设置相对路径,@Url指定的动态url参数做为第一个参数
@GET()
Observable<String> groupList(@Url String url, @Path("id") int groupId);

3.2标记请求数据类型

* <li>{@link retrofit2.http.FormUrlEncoded @FormUrlEncoded} - Form-encoded data with key-value* pairs specified by the {@link retrofit2.http.Field @Field} parameter annotation.* <li>{@link retrofit2.http.Multipart @Multipart} - RFC 2388-compliant multipart data with* parts specified by the {@link retrofit2.http.Part @Part} parameter annotation.
数据类型 备注说明
FormUrlEncoded retrofit2.http.FormUrlEncoded

标记为表单请求,只能上传键值对,并且键值对都是间隔分开的,只是最后会转化为一条信息;

Content-Type:application/x-www-from-urlencoded

Multipart retrofit2.http.Multipart

既可以上传文件等二进制数据,也可以上传表单键值对;

Content-Type:application/form-data

Streaming retrofit2.http.Streaming 从字面意思得知,只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件;表示响应体是以数据流的返回,如果没有此注解,默认会把数据全部加载到内存中,读取数据时也是从内存中读取,所有返回的数据比较大时,需要此注解;Content-Type:application/octet-stream
@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);@Multipart
@PUT("user/photo")
Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);

3.3注解参数

分类 名称 备注
作用于方法 Headers 你能通过@Headers注解设置静态的Headers(请求头)
作用于方法参数 Header 一个请求的Header能够通过@Header注解来实现动态更新;
HeaderMap 对于复杂的动态header组合,可以通过Map
Field

FormUrlEncoded与Field和FieldMap(Map<String, String>)标示请求表单键值对;Multipart与Part和PartMap(Map<String, RequestBody>)标示请求文件上传表单键值对;

FieldMap
Part
PartMap
Body 非表单请求体,通过@Body注解可以指定一个对象作为HTTP的请求;
Query 表示为查询参数;
QueryMap 表示为查询参数集合;(Map<String,Strinng>)
Path 指定变量值做为请求路径,在请求路径并表示{id};
Url 指定动态请求的相对路径,需要做为第一个参数,请求方法里面不设置路径;

注意:

1)Query和QueryMap与Field和FieldMap,Query会放置在url上请求服务器,Field做为请求体传给服务器,但生成的数据形式一样的;

2)Query、Field和Part这三者都支持数组和实现了Iterable接口的类型,如List,Set等,方便向后台传递数组;

Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//结果:ids[]=0&ids[]=1&ids[]=2

以上注解的具体使用都可以参考相应类的表头有明确的使用说明,例如QueryMap:

示例:
GET("/friends")
Call<ResponseBody> friends(@QueryMap Map<String, String> filters);调用方式foo.friends(ImmutableMap.of("group", "coworker", "age", "42"));
请求路径/friends?group=coworker&age=42默认key和value是进行URL编码的;
通过encoded=true变量可以改变编码行为;
GET("/friends")
Call<ResponseBody> friends(@QueryMap(encoded=true) Map<String, String> filters);
调用方式foo.friends(ImmutableMap.of("group", "coworker+bowling"));
请求路径/search?group=coworker+bowling

其他注解具体使用参考相应类注解说明;

4.GSON和Converter

    //converters 被添加的顺序将是它们被Retrofit尝试的顺序public static final ApiService service = new Retrofit.Builder().baseUrl("http://www.baidu.com").client(HttpUtils.client).addConverterFactory(StringConverterFactory.create()).addConverterFactory(GsonConverterFactory.create(EntityUtils.gson)).addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//            .callbackExecutor()
//            .callFactory()
//            .validateEagerly().build().create(ApiService.class);

默认Retrofit支持ResponseBody响应体,调用API方法返回Call<ResponseBody>;

Call<T>定义的是范型,在实际开发中我们希望ResponseBody直接转换为我们需要类型方便开发;

@GET("group/{id}/users")
Call<Result.Data<List<Item>>> groupList(@Path("id") int groupId);

1)如何实现这种效果,这时就需要借助Gson转换器,现引入gson和转换gson库;

implementation 'com.google.code.gson:gson:2.8.0'(gson生成和解析库)
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'(转换器,请求结果转换成Model)

2)创建Gson转换器

创建Gson对象

public final class EntityUtils {private EntityUtils() {}public static final Gson gson = new GsonBuilder().registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()).create();}

添加Gson转换器

Retrofit.addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))

这样就可以实现ResponseBody和Gson转换;

Call<Result.Data<List<Item>>>data = apiService.groupList(23)

5.RxJava和CallAdapter

引入RxJava类库:


implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'(配合Rxjava 使用)
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.2.1'(一种帮助你做异步的框架. 类似于 AsyncTask. 但其灵活性和扩展性远远强于前者.
从能力上讲, 如果说 AsycnTask 是 DOS 操作系统, RxJava 是 Window 操作系统。)

(4.GSON和Converter)这一章节主要说明Call<T>中T返回的替换,CallAdapter主要实现Call替换;用Observable替换Call,Observable基于订阅模式实现;

1)添加CallAdapter

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())

2)定义API接口:

    /*** <p>分类列表</p>* @param c* @param a* @param page* @param model(0:首页,1:文字,2:声音,3:影像,4:单向历)* @param pageId* @param time* @param deviceId* @param show_sdv* @return*/
//    @GET("/")@HTTP(method = "get", path = "/",hasBody = false)Observable<Result.Data<List<Item>>> getList(@Query("c") String c, @Query("a") String a, @Query("p") int page, @Query("model") int model, @Query("page_id") String pageId, @Query("create_time") String createTime, @Query("client") String client, @Query("version") String version, @Query("time") long time, @Query("device_id") String deviceId, @Query("show_sdv") int show_sdv);

3)调用API接口

apiService.getList("api","getList",page,model,pageId,createTime,"android","1.3.0", TimeUtil.getCurrentSeconds(), deviceId,1).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Result.Data<List<Item>>>() {@Overridepublic void onCompleted() {}@Overridepublic void onError(Throwable e) {view.showOnFailure();}@Overridepublic void onNext(Result.Data<List<Item>> listData) {int size = listData.getDatas().size();if(size>0){view.updateListUI(listData.getDatas());}else {view.showNoMore();}}});

像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable<Response<T>> 代替 Observable<T> ,这里的Response指retrofit2.Response 
2、用Observable<Result<T>> 代替 Observable<T>,这里的Result是指retrofit2.adapter.rxjava.Result,这个Result中包含了Response的实例;

6.自定义Converter

自定义转换器,实现ResponseBody转换为String类型,Call<ResponseBody>转换为Call<String>;

Retrofit提供了转换器Converter接口和内部Factory类;

/*** 转换HTTP的响应头和请求头到指定对象类型,通过Factory方法创建Converter实例,调用Retrofit.Builder#addConverterFactory(Factory)安装转换器给Retrofit实例;*/
public interface Converter<F, T> {//实现F(from)类型转换为T(to)指定数据类型T convert(F value) throws IOException;/** 创建基于目标需要的Converter实例 */abstract class Factory {/*** 返回一个转换HTTP响应体到指定数据类型的转换器,假如无法处理则返回null* 主要处理HTTP响应体*/public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,Annotation[] annotations, Retrofit retrofit) {return null;}/*** 返回一个转换HTTP请求体到指定数据类型的转换器,假如无法处理则返回null* 主要处理HTTP请求体*/public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return null;}/*** 返回一个转换相应类型到指定数据类型的转换器,假如无法处理则返回null主要处理Field,FieldMap,Header,HeaderMap,Path,Query,QueryMap数值*/public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,Retrofit retrofit) {return null;}}
}

1)实现转换器

我们要想从Call<ResponseBody> 转换为 Call<String> 那么对应的F和T则分别对应ResponseBody和String,我们定义一个StringConverter并实现Converter接口。

public class StringConverter implements Converter<ResponseBody, String> {//实现转换器方法,将ResponseBody转换为String类型@Overridepublic String convert(ResponseBody value) throws IOException {return value.string();}
}

2)实现转换器工厂方法

public class StringConverterFactory extends Converter.Factory {public static StringConverterFactory create(){return new StringConverterFactory();}@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {if (type == String.class) {return new StringConverter(); //在转换器工厂创建转换器}//其它类型我们不处理,返回null就行return null;}
}

3)添加转换器

.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(EntityUtils.gson))

注意:converters 被添加的顺序将是它们被Retrofit尝试的顺序,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配;

只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call<String>还是Observable<String>;

如果需要自定义转换器可以按照需要自定义转换器即可;

7.自定义CallAdapter

若需要自定义CallAdapter,需要实现CallAdapter接口和相应的工厂方法;

package retrofit2;//适配Call<R>转换为T类型
public interface CallAdapter<R, T> {/*** Type对应Call<R>中R类型* 返回此适配器(adapter)在将HTTP响应主体转换为Java时使用的值类型对象。* 例如:Call<R>数据类型是R 。* 这个 R 会作为Converter.Factory.responseBodyConverter 的第一个参数可以参照上面的自定义Converter*/Type responseType();/*** 返回代理Call<R>类型T实例* 例如, 给予T类型Async,当调用call运行这个实例将返回Async<R>* Override* public <R> Async<R> adapt(final Call<R> call) {*   return Async.create(new Callable<Response<R>>() {*     Override*     public Response<R> call() throws Exception {*       return call.execute();*     }*   });* }*/T adapt(Call<R> call);//用于向Retrofit提供CallAdapter的工厂类abstract class Factory {// 返回// 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和Observable<Requestbody>// RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型// 不支持时返回nullpublic abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,Retrofit retrofit);// 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbodyprotected static Type getParameterUpperBound(int index, ParameterizedType type) {return Utils.getParameterUpperBound(index, type);}// 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call// 上面的get方法需要使用该方法。protected static Class<?> getRawType(Type type) {return Utils.getRawType(type);}}
}

了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall<String>为例。

1)定义Call的转换类

在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。

public static class CustomCall<R> {public final Call<R> call;public CustomCall(Call<R> call) {this.call = call;}public R get() throws IOException {return call.execute().body();}
}

2)定义CallAdapter的实现类

有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call<T> 到 CustomCall<T>的转换,这里需要注意的是最后的泛型,是我们要返回的类型。

public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {private final Type responseType;// 下面的 responseType 方法需要数据的类型CustomCallAdapter(Type responseType) {this.responseType = responseType;}@Overridepublic Type responseType() {return responseType;}@Overridepublic <R> CustomCall<R> adapt(Call<R> call) {// 由 CustomCall 决定如何使用return new CustomCall<>(call);}
}

3)定义工厂方法

提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:

public static class CustomCallAdapterFactory extends CallAdapter.Factory {public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();@Overridepublic CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {// 获取原始类型Class<?> rawType = getRawType(returnType);// 返回值必须是CustomCall并且带有泛型if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);return new CustomCallAdapter(callReturnType);}return null;}
}

4)注册工厂方法

使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory

addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)

注: addCallAdapterFactory与addConverterFactory同理,也有先后顺序。

8.其他说明

8.1Retrofit.Builder

前面用到了 Retrofit.Builder 中的baseUrl、addCallAdapterFactory、addConverterFactory、build方法,还有callbackExecutor、callFactory、client、validateEagerly这四个方法没有用到,这里简单的介绍一下。

方法 用途
callbackExecutor(Executor) 指定Call.enqueue时使用的Executor,所以该设置只对返回值为Call的方法有效
callFactory(Factory)

设置一个自定义的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就实现了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最终也是调用了该方法,也就是说两者不能共用

client(OkHttpClient)

设置自定义的OkHttpClient,以前的Retrofit版本中不同的Retrofit对象共用同OkHttpClient,在2.0各对象各自持有不同的OkHttpClient实例,所以当你需要共用OkHttpClient或需要自定义时则可以使用该方法,如:处理Cookie、使用stetho 调式等

validateEagerly(boolean) 是否在调用create(Class)时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用

8.2Retrofit的Url组合规则

BaseUrl 和URL有关的注解中提供的值 最后结果
http://localhost:4567/path/to/other/ /post http://localhost:4567/post
http://localhost:4567/path/to/other/ post http://localhost:4567/path/to/other/post
http://localhost:4567/path/to/other/ https://github.com/ikidou https://github.com/ikidou

从上面不能难看出以下规则:

1)如果你在注解中提供的url是完整的url,则url将作为请求的url。
2)如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
3)如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值

参考:

https://www.jianshu.com/p/308f3c54abdd

https://blog.csdn.net/ahou2468/article/details/104972887

Android Retrofit详解(retrofit:2.3.0)相关推荐

  1. 学习Android从0开始之开发工具篇-Android studio详解

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android studio详解 我们古人又云:工欲善其事,必先利其器. 1.android studio的背景 Android Studio 是 ...

  2. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  3. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  4. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  5. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  6. Android SharedFlow详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/121911675 本文出自[赵彦军的博客] 文章目录 系列文章 什么是SharedF ...

  7. Android签名详解(debug和release)

    Android签名详解(debug和release) 1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包不被 ...

  8. Android 安装详解---Mr.Zhang

    Android 安装详解 介于做android项目开发之际,在这里和大家一起学习一下android的安装说明,以帮助更多刚开始接触android开发的同学更快捷的学习. 首先,说下我们在安装andro ...

  9. android fragment 优势,Android Fragment详解

    参考网址:Android Fragment详解 一.什么是Fragment? Fragment:是Android3.0开始新增的概念,意为碎片.Fragment是依赖于Activity的,不能独立存在 ...

最新文章

  1. linux tar压缩排除某个文件夹或者文件
  2. html大学生活主题班会,“大学生活”主题班会记录范文
  3. 性能建议(这里只针对单机版redis持久化做性能建议)
  4. 基于Metronic的Bootstrap开发框架经验总结(5)--Bootstrap文件上传插件File Input的使用...
  5. python内核死亡的原因_Python的内核由于DLL而死亡
  6. 版本控制工具--svn和git的使用(一) -----版本控制的好处以及分类
  7. 小程序mysql+php测试,莲米粒是一个基于PHP+MySQL+微信小程序技术栈
  8. 2019 全国大学生电子设计竞赛题目
  9. python百度ai实现身份证识别_python利用百度AI实现文字识别功能
  10. 旋转矩阵(Rotation matrix):旋转轴与旋转角 ( axis and angle )
  11. 男人一生的四菜一汤(转载)
  12. 在线运行Java代码获取APNIC中国区的ip段,用作绕过国内ip
  13. Scratch滚动的天空(1)
  14. 【2018-11-09】中证500指数的估值详情
  15. 工具篇 之 Mac 安装 JDK 1.8 并配置环境变量
  16. 2020校招笔试之新华三
  17. Bagging (bootstrap aggregating) - 集成方法之一
  18. vulnhub-warzone_1
  19. 荣耀X10 Max发布在即 屏幕优势显著
  20. python json字典模块详解,json.dumps(),json.loads()

热门文章

  1. Shader——漩涡效果
  2. 【20保研】中国科学技术大学2019年第六届计算机科学暑期夏令营通知
  3. Discus论坛System Error界面修改标语
  4. 第8章 对象引用、可变性和垃圾回收
  5. 2011年回顾:改变游戏的20个HTML5网站
  6. onload与ready方法的区别
  7. 用AI培养孩子学习兴趣:讯飞新一代智能学习机发布
  8. 数学建模清风微信公众号的习题答案(挑战篇-蒙特卡罗思想、枚举法和网格搜索法)
  9. 工作-大四实习生面经
  10. 华为云永久修改主机名