Retrofit2.0使用详解

2023-11-19

简介

Retrofit是由Square公司提供的开源产品,为Android平台的应用提供一个类型安全的REST客户端。其实质上是对OkHttp的封装,使用面向接口的方式进行网络请求,利用动态生成的代理类封装了网络接口请求的底层,将REST API返回的数据转化为Java对象方便操作,可以进行GETPOSTPUTDELETE等请求,极大的提高了应用的网络体验。

官方文档(英文)
更新日志
用Retrofit 2简化HTTP请求
给Android开发者的RxJava详解
Android文件存储使用参考

1、REST

REST(REpresentational State Transfer)指的是一组架构约束条件和原则。
RESTful架构都满足以下规则:

(1) 每一个URI代表一种资源;
(2) 客户端和服务器之间,传递这种资源的某种表现层;
(3) 客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。

什么是REST

2、2.0与1.9使用比较

(1) 创建实例:Retrofit 1.9中使用的是RestAdapter,而Retrofit 2.0中使用的是Retrofit
(2) 加载URL:Retrofit 1.9中使用的是setEndpoint,而Retrofit 2.0中使用的是baseUrl
(3) 拦截器:Retrofit 1.9中使用setRequestInterceptor方法设置拦截器对Http请求进行相应处理,而Retrofit 2.0中通过OKHttp的拦截器拦截Http请求进行监控,可以用于重写、重试、日志打印等;
(4) 转换器:Retrofit 1.9中使用的是setConverter,而Retrofit 2.0中使用的是addConverterFactory用于支持Gson转换。

3、Retrofit 1.9体验不好的地方

(1) 不能同时操作response返回数据(比如返回的Header部分或者URL)和序列化后的数据(JavaBean);
(2) 同步和异步方式执行同一个方法需要分别定义接口;
(3) 对正在进行的网络任务无法取消。

常用配置

1、依赖配置

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

2、网络权限

<uses-permission android:name="android.permission.INTERNET" />

3、 混淆配置

-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

4、解析方式

Retrofit 2支持多种解析方式来解析响应数据,有以下解析库可以选择:

Gson:        com.squareup.retrofit:converter-gson:2.0.0-beta2
Jackson:     com.squareup.retrofit:converter-jackson:2.0.0-beta1
Moshi:       com.squareup.retrofit:converter-moshi:2.0.0-beta1
Protobuf:    com.squareup.retrofit:converter-protobuf:2.0.0-beta1
Wire:        com.squareup.retrofit:converter-wire:2.0.0-beta1
Simple XML:  com.squareup.retrofit:converter-simplexml:2.0.0-beta1

基本用法

//定以接口
public interface GitHubApi {
    @GET("repos/{owner}/{repo}/contributors")
    Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}

//获取实例
Retrofit retrofit = new Retrofit.Builder()
        //设置OkHttpClient,如果不设置会提供一个默认的
        .client(new OkHttpClient())
        //设置baseUrl
        .baseUrl("https://api.github.com/")
        //添加Gson转换器
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

GitHubApi repo = retrofit.create(GitHubApi.class);

//请求完整地址:https://api.github.com/repos/square/retrofit/contributors
//同步请求
Call<ResponseBody> call = repo.contributorsBySimpleGetCall(mUserName, mRepo);
try {
    Response<ResponseBody> repos = call.execute();
} catch (IOException e) {
    e.printStackTrace();
}

//Call只能调用一次,否则会抛IllegalStateException异常
Call<ResponseBody> clone = call.clone();

//异步请求
clone.enqueue(new Callback<ResponseBody>() {

    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            // Get result bean from response.body().string()
            String repos = response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // Get header item from response
        String links = response.headers().get("Link");
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {

    }
});

//取消请求
call.cancel();

使用方式

1、创建Retrofit实例

如果要向一个API发送我们的网络请求,我们需要使用Retrofit.Builder()并指定ServicebaseUrl(通常情况下指的是域名)。还需注意我们要指定一个Factory来对响应进行反序列化,可以添加多种序列化Factory,其被添加的顺序将是它们被Retrofit尝试解析的顺序。但是GsonConverterFactory必须放在最后,否则会抛出异常。如果我们希望传入一个自定义的Gson解析实例,也是可以自定义的。一般地baseUrl是在实例化Retrofit的时候定义的,我们也可以在API接口中定义完整的Url

注意:Retrofit 2.0后建议baseUrl中要以“/”结尾,在API中不要以“/”开头和结尾。

Gson gson = new GsonBuilder()
    .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
    .create();

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

2、创建API接口

Retrofit 2中通过一个Java接口作为HTTP请求的API接口,使用特殊的Retrofit注解来映射参数以及请求方法这些细节。另外返回值始终是一个参数化了的Call<T>对象,比如Call<User>。如果你不需要任何类型安全的响应,你可以把返回值指定为Call<ResponseBody>

public interface GitHubApi {
    @GET("repos/{owner}/{repo}/contributors")
    Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);

    @POST("/users/new")
    Call<User> createUser(@Body User user);
}

每个API接口都指定了一个关于HTTP(GETPOST等)方法的注解以及用于分发网络调用的方法。

3、调用API接口

GitHubApi repo = retrofit.create(GitHubApi.class);
Call<ResponseBody> call = repo.contributorsBySimpleGetCall(mUserName, mRepo);

call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            Gson gson = new Gson();
            ArrayList<Contributor> contributors = gson.fromJson(response.body().string(),
                    new TypeToken<List<Contributor>>() {
                    }.getType());
            LogUtil.e("contributors-->" + contributors.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        LogUtil.e("onFailure");
    }
});

完整Url地址为:https://api.github.com/repos/square/retrofit/contributors

4、取消请求

call.cancel();

我们可以终止一个请求。终止操作是对底层的HttpClient执行cancel操作,即使是正在执行的请求,也能够立即终止。

5、clone

无论是同步操作还是异步操作每一个call对象实例只能被执行一次,多次执行会抛出IllegalStateException异常。通过clone方法可以创建一个一模一样的实例,并且它的开销也是很小的。

Call<List<Contributor>> cloneCall = call.clone();

转换器

在上面的例子中通过获取ResponseBody自己使用Gson来解析接收到的Json格式数据。在Retrofit中当创建一个Retrofit实例的时候可以为其添加一个Json转换器,这样就会自动将Json格式的响应体转换为所需要的Java对象。下面看一下如何根据已有的Json格式数据生成Java对象,我们可以根据已知的数据手动创建Java对象,也可以通过工具或插件将Json格式的数据为我们自动生成Java对象。

自动生成Java对象

在这里介绍两种根据已有Json数据自动生成Java对象的工具。

1、jsonschema2pojo

可以通过访问jsonschema2pojo网站来自动生成,先来看一下它的使用方法。

jsonschema2pojo

上面配置中所选注解也可以选择Gson,对于@Generated注解若是需要保留的话可以添加以下依赖方式,也可以直接删除@Generated注解,没有任何影响。

compile 'org.glassfish:javax.annotation:10.0-b28'

2、GsonFormat

GsonFormatAndroid Studio中的一个插件,在Android Studio的插件选项中直接搜索安装这个插件即可。在这里我们看一下是如何使用这个插件的。

GsonFormat

3、添加转换器

在这里我们需要为Retrofit添加Gson转换器的依赖,如果已经添加过converter-gson那么就不用再添加Gson库,因为在converter-gson中已经包含了Gson

compile 'com.squareup.retrofit2:converter-gson:2.0.2'

在这里先创建一个JavaContributor来保存接收到的数据。

public class Contributor {

    private String login;
    private Integer contributions;

    public String getLogin() {
        return login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public Integer getContributions() {
        return contributions;
    }

    public void setContributions(Integer contributions) {
        this.contributions = contributions;
    }

    @Override
    public String toString() {
        return "Contributor{" +
                "login='" + login + '\'' +
                ", contributions=" + contributions +
                '}';
    }
}

然后修改API接口。

public interface GitHubApi {
    @GET("repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}

创建Retrofit实例,通过addConverterFactory指定一个Factory来对响应进行反序列化,在这里converters被添加的顺序将是它们被Retrofit尝试解析的顺序。

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

调用上面所修改的API接口。

GitHubApi repo = retrofit.create(GitHubApi.class);
Call<List<Contributor>> call = repo.contributorsByAddConverterGetCall(mUserName, mRepo);

call.enqueue(new Callback<List<Contributor>>() {

    @Override
    public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
        List<Contributor> contributors = response.body();
        LogUtil.e("contributors-->" + contributors.toString());
    }

    @Override
    public void onFailure(Call<List<Contributor>> call, Throwable t) {
        LogUtil.e("onFailure");
    }
});

在这里我们也可以通过call.execute()执行一个同步请求,由于不允许在主线程中进行网络请求操作,所以我们需要在子线程中进行执行。

GitHubApi repo = retrofit.create(GitHubApi.class);
final Call<List<Contributor>> call = repo.contributorsByAddConverterGetCall(mUserName, mRepo);

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Response<List<Contributor>> response = call.execute();
            List<Contributor> contributors = response.body();
            LogUtil.e("contributors-->" + contributors.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

添加日志信息

Retrofit 2.0中是没有日志功能的,但是Retrofit 2.0依赖OkHttp,所以也就能够通过OkHttp中的addInterceptor来实现实际的底层请求和响应日志。

HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(httpLoggingInterceptor)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

需要添加如下依赖:

compile 'com.squareup.okhttp3:logging-interceptor:3.1.2'

参数注解

@HTTP:可以代替其他方法的任意一种。

/**
 * method  表示请求方法,不区分大小写
 * path    表示路径
 * hasBody 表示是否有请求体
 */
@HTTP(method = "get", path = "user/{userName}", hasBody = false)
Call<User> getUser(@Path("userName") String userName);

@Header:添加请求头,不能被互相覆盖,用于修饰参数。

//动态设置Header值
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization);

等同于:

//静态设置Header值
@Headers("Authorization: authorization")//这里的authorization相当于上面方法中传进来的变量值
@GET("user")
Call<User> getUser();

@Headers:设置多个Header值,用于修饰方法。

@Headers({
        "Accept: application/vnd.github.v3.full+json",
        "User-Agent: Retrofit-Sample-App"
})
@GET("user")
Call<User> getUser();

@Url:使用全路径复写baseUrl,适用于非统一baseUrl的场景。

@GET
Call<User> getUser(@Url String url);

@Path:URL占位符,用于替换和动态更新,相应的参数必须使用相同的字符串被@Path进行注释。

@GET("user/{userName}")
Call<User> getUser(@Path("userName") String userName);
//--> http://baseUrl/user/userName

等同于:

@GET
Call<User> getUser(@Url String url);

@Body:用于POST请求体,将实例对象根据转换方式转换为对应的json字符串参数,这个转化方式是GsonConverterFactory定义的。

@POST("add")
Call<List<User>> addUser(@Body User user);

这个参数对象会被Retrofit实例中的Converter进行转化,如果没有给Retrofit实例添加任何Converter的话则只有ResponseBody可以作为参数使用。

@Query:URL指定查询参数。

@GET("user")
Call<User> getUser(@Query("userName") String userName);
//--> http://baseUrl/user?userName=userName

传数组:

@GET("v1/enterprise/find")
Call<ResponseBody> getData(@Query("id") String id, @Query("linked[]") String... linked);
String id = "retrofit";
String[] str = new String[]{"retrofit"};
GitHubApi repo = retrofit.create(GitHubApi.class);

Call<ResponseBody> call = repo.getData(id, str);

@QueryMap:当参数过多的时候使用它来指定每个表单项的keyvalue值。

@GET("user")
Call<User> getUser(@QueryMap(encoded = true) Map<String, String> map);

可以约定是否需要encode

@Field:使用@Field注解和参数来指定每个表单项的keyvalue为参数的值,以表单的方式传递简单的键值对;使用@FormUrlEncoded注解来发送表单数据,表示表单提交Content-Type:application/x-www-form-urlencoded

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

@FieldMap:当我们有很多个表单参数时可以通过@FieldMap注解和Map对象参数来指定每个表单项的keyvalue值。

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@FieldMap Map<String, String> fieldMap);

@Part:用于单文件上传,以Post表单的方式上传文件可以携带参数,其中@Part MultipartBody.Part代表文件,@Part("key") RequestBody代表参数;需要添加@Multipart表示支持文件上传的表单Content-Type: multipart/form-data

@Multipart
@POST("upload")
Call<ResponseBody> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file);
File file = new File(Environment.getExternalStorageDirectory(), "image.png");

// create RequestBody instance from file
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);

// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body = MultipartBody.Part.createFormData("picture", file.getName(), requestFile);

// add another part within the multipart request
String descriptionString = "hello, this is description speaking";
RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);

GitHubApi repo = retrofit.create(GitHubApi.class);

// finally, execute the request
Call<ResponseBody> call = repo.upload(description, body);
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        LogUtil.e("success");
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        LogUtil.e("error:" + t.getMessage());
    }
});

@PartMap:用于多文件上传。

@Multipart
@POST("upload")
Call<ResponseBody> upload(@PartMap Map<String, RequestBody> params);
File file = new File(Environment.getExternalStorageDirectory(), "image.png");
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
Map<String, RequestBody> params = new HashMap<>();
params.put("picture\"; filename=\"" + file.getName() + "", requestFile);

@Streaming:用于下载大文件。

@Streaming
@GET
Call<ResponseBody> downLoadFile(@Url String fileUrl);

与RxJava结合

添加如下依赖:

compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'
compile 'io.reactivex:rxandroid:1.1.0'

创建Retrofit对象实例时,通过addCallAdapterFactory来添加对RxJava的支持。

Retrofit retrofit = new Retrofit.Builder()
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl("https://api.github.com/")
    .build();

使用Observable创建一个API接口。

@GET("repos/{owner}/{repo}/contributors")
Observable<List<Contributor>> contributorsByRxJava(@Path("owner") String owner, @Path("repo") String repo);

下面来调用这个API接口。

private CompositeSubscription mSubscriptions = new CompositeSubscription();

mSubscriptions.add(
        mGitHubService.contributorsByRxJava(mUserName, mRepo)
                //设置事件触发在非主线程
                .subscribeOn(Schedulers.io())
                //设置事件接受在UI线程以达到UI显示的目的
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<Contributor>>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(List<Contributor> contributors) {
                        LogUtil.e("contributors-->" + contributors.toString());
                    }
                }));

如果我们想要查看所有Contributor的信息,首先我们需要向GitHub请求获取到所有Contributor,然后再通过获得的Contributor进行依次向GitHub请求获取Contributor的信息,在这时候我们使用RxJava也就非常方便了。

 mSubscriptions.add(mGitHubService.contributorsByRxJava(mUserName, mRepo)
        .flatMap(new Func1<List<Contributor>, Observable<Contributor>>() {
            @Override
            public Observable<Contributor> call(List<Contributor> contributors) {
                return Observable.from(contributors);
            }
        })
        .flatMap(new Func1<Contributor, Observable<Pair<User, Contributor>>>() {
            @Override
            public Observable<Pair<User, Contributor>> call(Contributor contributor) {
                Observable<User> userObservable = mGitHubService.userByRxJava(contributor.getLogin())
                        .filter(new Func1<User, Boolean>() {
                            @Override
                            public Boolean call(User user) {
                                return !isEmpty(user.getName()) && !isEmpty(user.getEmail());
                            }
                        });

                return Observable.zip(userObservable,
                        Observable.just(contributor),
                        new Func2<User, Contributor, Pair<User, Contributor>>() {
                            @Override
                            public Pair<User, Contributor> call(User user, Contributor contributor) {
                                return new Pair<>(user, contributor);
                            }
                        });
            }
        })
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<Pair<User, Contributor>>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Pair<User, Contributor> pair) {
                User user = pair.first;
                Contributor contributor = pair.second;
                LogUtil.e("name:" + user.getName());
                LogUtil.e("contributions:" + contributor.getContributions());
                LogUtil.e("email:" + user.getEmail());
            }
        }));

设置缓存

1、由于Retrofit是对OkHttp的封装,所以可以直接为OkHttp设置缓存,以下我们设置了离线读取本地缓存,在线获取最新数据。

OkHttpClient builder = new OkHttpClient.Builder()
        //设置Cache目录
        .cache(cache())
        //设置缓存
        .addInterceptor(cacheInterceptor)
        .addNetworkInterceptor(cacheInterceptor)
        //设置超时
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        //错误重连
        .retryOnConnectionFailure(true)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        //设置baseUrl
        .baseUrl(Constant.baseUrl)
        //设置OkHttpClient,如果不设置会提供一个默认的
        .client(builder)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        //添加Gson转换器
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

private static Cache cache() {
    //设置缓存路径
    File cacheDir = new File(MyApplication.getContext().getExternalCacheDir(), "HttpResponseCache");
    //设置缓存大小为10M
    return new Cache(cacheDir, 10 * 1024 * 1024);
}

private static Interceptor cacheInterceptor = new Interceptor() {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //在每个请求发出前,判断一下网络状况,如果没问题继续访问,如果有问题,则设置从本地缓存中读取
        if (!NetworkUtils.isNetworkAvailable()) {
            LogUtil.i("no network");
            request = request.newBuilder()
                    //强制使用缓存
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }

        Response response = chain.proceed(request);
        //先判断网络,网络好的时候,移除header后添加cache失效时间为0小时,网络未连接的情况下设置缓存时间为4周
        if (NetworkUtils.isNetworkAvailable()) {
            LogUtil.i("has network");
            // 有网络时 设置缓存超时时间0个小时
            int maxAge = 0;// 在线缓存0个小时
            response.newBuilder()
                    .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .build();
        } else {
            LogUtil.i("network error");
            // 无网络时,设置超时为4周
            int maxStale = 60 * 60 * 24 * 4 * 7;// 离线缓存4周
            response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }
        return response;
    }
};

2、有网和没网都先读缓存,统一缓存策略,降低服务器压力。

private static Interceptor cacheInterceptor = new Interceptor() {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);

        String cacheControl = request.cacheControl().toString();
        if (TextUtils.isEmpty(cacheControl)) {
            cacheControl = "public, max-age=60";
        }
        return response.newBuilder()
                .header("Cache-Control", cacheControl)
                .removeHeader("Pragma")
                .build();
    }
};

3、配置单个请求的@Headers,设置此请求的缓存策略,不影响其他请求的缓存策略,不设置则没有缓存。

//设置单个请求的缓存时间
@Headers("Cache-Control: max-age=640000")
@GET("user")
Call<User> getUser();

使用证书锁定

Retrofit中的证书锁定是借助OkHttpClient实现的,通过为OkHttpClient添加certificatePinner即可。CertificatePinner对象以构建器的方式创建,可以通过其add()方法来锁定多个证书。

OkHttpClient client = new OkHttpClient.Builder()
        .certificatePinner(new CertificatePinner.Builder()
                .add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
                .add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
                .build())
        .build();

自签名证书

由于我们使用的是自签名的证书,因此客户端不信任服务器,会抛出异常javax.NET.ssl.SSLHandshakeException。为此,我们需要自定义信任处理器(TrustManager)来替代系统默认的信任处理器,这样我们才能正常的使用自定义的证书或者非Android认可的证书颁发机构颁发的证书。

针对使用场景又分为以下两种情况:一种是安全性要求不高的情况下,客户端无需内置证书;另外一种则是客户端内置证书。

客户端不内置证书

public static SSLSocketFactory getSSLSocketFactory() throws Exception {

    // Create a trust manager that does not validate certificate chains
    final TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {

                //证书中的公钥
                public static final String PUB_KEY = "------";

                @Override
                public void checkClientTrusted(
                        java.security.cert.X509Certificate[] chain,
                        String authType) throws CertificateException {
                }

                //客户端并为对ssl证书的有效性进行校验
                @Override
                public void checkServerTrusted(
                        java.security.cert.X509Certificate[] chain,
                        String authType) throws CertificateException {

                    if (chain == null) {
                        throw new IllegalArgumentException("checkServerTrusted:x509Certificate array isnull");
                    }

                    if (!(chain.length > 0)) {
                        throw new IllegalArgumentException("checkServerTrusted: X509Certificate is empty");
                    }

                    if (!(null != authType && authType.equalsIgnoreCase("RSA"))) {
                        throw new CertificateException("checkServerTrusted: AuthType is not RSA");
                    }

                    // Perform customary SSL/TLS checks
                    try {
                        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                        tmf.init((KeyStore) null);
                        for (TrustManager trustManager : tmf.getTrustManagers()) {
                            ((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
                        }
                    } catch (Exception e) {
                        throw new CertificateException(e);
                    }

                    // Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins
                    // with 0×30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0×00 to drop.
                    RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey();

                    String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16);
                    // Pin it!
                    final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);

                    if (!expected) {
                        throw new CertificateException("checkServerTrusted: Expected public key: "
                                + PUB_KEY + ", got public key:" + encoded);
                    }
                }

                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return new java.security.cert.X509Certificate[0];
                }
            }};

    // Install the all-trusting trust manager
    final SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

    // Create an ssl socket factory with our all-trusting manager
    return sslContext.getSocketFactory();
}

其中PUB_KEY是我们证书中的公钥,你可以自行从自己的证书中提取。我们看到,在checkServerTrusted()方法中,我们通过证书的公钥信息来确认证书的真伪,如果验证失败,则中断请求。

客户端内置证书

Retrofit中使用自签名证书大致要经过以下几步:

  1. 将证书添加到工程中;
  2. 自定义信任管理器TrustManager
  3. 用自定义TrustManager代替系统默认的信任管理器;

1、添加证书到工程

比如现在我们有个证书myssl.cer,首先需要将其放在res/raw目录下,当然你也可以放在assets目录下。

证书

2、自定义TrustManager

public static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {

    if (context == null) {
        throw new NullPointerException("context == null");
    }

    //CertificateFactory用来证书生成
    CertificateFactory certificateFactory;
    try {
        certificateFactory = CertificateFactory.getInstance("X.509");
        //Create a KeyStore containing our trusted CAs
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);

        for (int i = 0; i < certificates.length; i++) {
            //读取本地证书
            InputStream is = context.getResources().openRawResource(certificates[i]);
            keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(is));

            if (is != null) {
                is.close();
            }
        }

        //Create a TrustManager that trusts the CAs in our keyStore
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        //Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

3、用自定义TrustManager代替系统默认的信任管理器

int[] certificates = new int[]{R.raw.myssl};
OkHttpClient build = new OkHttpClient.Builder()
        .socketFactory(HttpsFactroy.getSSLSocketFactory(context, certificates))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .client(build)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

这样我们的客户端就可以使用自签名的证书了。

公共参数

Interceptor addQueryParameterInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request request;
        String method = originalRequest.method();
        Headers headers = originalRequest.headers();
        HttpUrl modifiedUrl = originalRequest.url().newBuilder()
                // Provide your custom parameter here
                .addQueryParameter("platform", "android")
                .addQueryParameter("version", "1.0.0")
                .build();
        request = originalRequest.newBuilder().url(modifiedUrl).build();
        return chain.proceed(request);
    }
};

OkHttpClient builder = new OkHttpClient.Builder()
        .addInterceptor(addQueryParameterInterceptor)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(builder)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

设置通用头

Interceptor headerInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request.Builder requestBuilder = originalRequest.newBuilder()
                .header("AppType", "TPOS")
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .method(originalRequest.method(), originalRequest.body());
        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
};

OkHttpClient builder = new OkHttpClient.Builder()
        .addInterceptor(headerInterceptor)
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(builder)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

设置Cookie

服务端可能需要保持请求是同一个Cookie

1、添加依赖

compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0'

2、设置Cookie

CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);

OkHttpClient builder = new OkHttpClient.Builder()
        .cookieJar(new JavaNetCookieJar(cookieManager))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(builder)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

文件上传下载进度显示

Retrofit中我们可以通过ResponseBody对文件进行下载。但是在Retrofit中并没有为我们提供显示下载进度的接口。在项目开发中,如果用户下载一个文件,无法实时给用户显示下载进度,那么这样的用户体验是非常差的。下面我们简单介绍一下在Retrofit中是如何对文件的上传下载进度进行实时的更新显示。

首先定义一个用于监听上传下载进度的接口,其中包含上传或下载进度、文件总大小以及是否操作完成。

public interface ProgressListener {
    /**
     * @param progress 已经下载或上传字节数
     * @param total    总字节数
     * @param done     是否完成
     */
    void onProgress(long progress, long total, boolean done);
}

对于文件的下载我们需要重写ResponseBody类中的一些方法,用于下载进度监听。

public class ProgressResponseBody extends ResponseBody {

    //实际的待包装响应体
    private final ResponseBody responseBody;
    //进度回调接口
    private final ProgressListener progressListener;
    //包装完成的BufferedSource
    private BufferedSource bufferedSource;

    /**
     * 构造函数赋值
     *
     * @param responseBody     待包装的响应体
     * @param progressListener 回调接口
     */
    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }

    /**
     * 重写实际响应体的contentType
     *
     * @return MediaType
     */
    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    /**
     * 重写实际响应体的contentLength
     *
     * @return contentLength
     * @throws IOException 异常
     */
    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    /**
     * 重写包装source
     *
     * @return BufferedSource
     */
    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            //包装
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    /**
     * 读取操作回调进度接口
     *
     * @param source Source
     * @return Source
     */
    private Source source(Source source) {
        return new ForwardingSource(source) {

            //当前读取字节数
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                //增加当前读取的字节数,如果读取完成了bytesRead会返回-1
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                //回调:如果contentLength()不知道长度则返回-1
                progressListener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                return bytesRead;
            }
        };
    }
}

对于文件的上传我们需要重写RequestBody类中的一些方法,用于上传进度监听。

public class ProgressRequestBody extends RequestBody {

    //实际的待包装请求体
    private final RequestBody requestBody;
    //进度回调接口
    private final ProgressListener progressListener;
    //包装完成的BufferedSink
    private BufferedSink bufferedSink;

    /**
     * 构造函数赋值
     *
     * @param requestBody      待包装的请求体
     * @param progressListener 回调接口
     */
    public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) {
        this.requestBody = requestBody;
        this.progressListener = progressListener;
    }

    /**
     * 重写实际响应体的contentType
     *
     * @return MediaType
     */
    @Override
    public MediaType contentType() {
        return requestBody.contentType();
    }

    /**
     * 重写实际响应体的contentLength
     *
     * @return contentLength
     * @throws IOException 异常
     */
    @Override
    public long contentLength() throws IOException {
        return requestBody.contentLength();
    }

    /**
     * 重写写入操作
     *
     * @param sink BufferedSink
     * @throws IOException 异常
     */
    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (bufferedSink == null) {
            //包装
            bufferedSink = Okio.buffer(sink(sink));
        }

        //写入
        requestBody.writeTo(bufferedSink);
        //必须调用flush,否则最后一部分数据可能不会被写入
        bufferedSink.flush();
    }

    /**
     * 写入操作回调进度接口
     *
     * @param sink Sink
     * @return Sink
     */
    private Sink sink(Sink sink) {
        return new ForwardingSink(sink) {

            //当前写入字节数
            long bytesWritten = 0L;
            //总字节长度,避免多次调用contentLength()方法
            long contentLength = 0L;

            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                if (contentLength == 0) {
                    //获得contentLength的值,后续不再调用
                    contentLength = contentLength();
                }

                //增加当前写入的字节数
                bytesWritten += byteCount;
                //回调
                progressListener.onProgress(bytesWritten, contentLength, bytesWritten == contentLength);
            }
        };
    }
}

上面类中计算已经读取文件的字节数,并且调用了ProgressListener接口,因此这个接口是在子线程中运行的。下面我们创建ProgressHelper帮助类,用于对OkHttpClient添加拦截事件,将RequestBodyResponseBody替换成我们自己实现的ProgressRequestBodyProgressResponseBody

public class ProgressHelper {

    private static ProgressBean progressBean = new ProgressBean();
    private static ProgressHandler mProgressHandler;

    public static void setProgressHandler(ProgressHandler progressHandler) {
        mProgressHandler = progressHandler;
    }

    /**
     * 包装OkHttpClient,用于下载文件的回调
     *
     * @return 包装后的OkHttpClient
     */
    public static OkHttpClient addProgressDownLoadBuilder(OkHttpClient.Builder builder) {
        if (builder == null) {
            builder = new OkHttpClient.Builder();
        }

        //进度回调接口
        final ProgressListener progressListener = new ProgressListener() {
            // 该方法在子线程中运行
            @Override
            public void onProgress(long progress, long total, boolean done) {
                LogUtil.e("progress: " + String.format("%d%% \n", (100 * progress) / total));
                if (mProgressHandler == null) {
                    return;
                }

                progressBean.setBytesRead(progress);
                progressBean.setContentLength(total);
                progressBean.setDone(done);
                mProgressHandler.sendMessage(progressBean);
            }
        };

        //增加拦截器
        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //拦截
                Response originalResponse = chain.proceed(chain.request());
                //包装响应体并返回
                return originalResponse.newBuilder()
                        .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                        .build();
            }
        });
        return builder.build();
    }

    /**
     * 包装OkHttpClient,用于上传文件的回调
     *
     * @return 包装后的OkHttpClient
     */
    public static OkHttpClient addProgressUpLoadBuilder(OkHttpClient.Builder builder) {
        if (builder == null) {
            builder = new OkHttpClient.Builder();
        }

        //进度回调接口
        final ProgressListener progressListener = new ProgressListener() {
            // 该方法在子线程中运行
            @Override
            public void onProgress(long progress, long total, boolean done) {
                LogUtil.e("progress: " + String.format("%d%% \n", (100 * progress) / total));
                if (mProgressHandler == null) {
                    return;
                }

                progressBean.setBytesRead(progress);
                progressBean.setContentLength(total);
                progressBean.setDone(done);
                mProgressHandler.sendMessage(progressBean);
            }
        };

        //增加拦截器
        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request original = chain.request();
                Request request = original.newBuilder()
                        .method(original.method(), new ProgressRequestBody(original.body(), progressListener))
                        .build();
                return chain.proceed(request);
            }
        });
        return builder.build();
    }
}

通过实现ProgressListener接口来获取下载进度,但是由于ProgressListener接口运行在子线程中,无法在其中进行UI操作,因此需要通过Handler将子线程中ProgressListener的数据发送到UI线程中进行处理。

创建一个用于存放ProgressListener中参数的对象。

public class ProgressBean {

    private long bytesRead;
    private long contentLength;
    private boolean done;

    public long getBytesRead() {
        return bytesRead;
    }

    public void setBytesRead(long bytesRead) {
        this.bytesRead = bytesRead;
    }

    public long getContentLength() {
        return contentLength;
    }

    public void setContentLength(long contentLength) {
        this.contentLength = contentLength;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    @Override
    public String toString() {
        return "ProgressBean{" +
                "bytesRead=" + bytesRead +
                ", contentLength=" + contentLength +
                ", done=" + done +
                '}';
    }
}

然后在创建一个ProgressHandler类。

public abstract class ProgressHandler {

    protected abstract void sendMessage(ProgressBean progressBean);

    protected abstract void handleMessage(Message message);

    protected abstract void onProgress(long progress, long total, boolean done);

    protected static class ResponseHandler extends Handler {

        private ProgressHandler mProgressHandler;

        public ResponseHandler(ProgressHandler mProgressHandler, Looper looper) {
            super(looper);
            this.mProgressHandler = mProgressHandler;
        }

        @Override
        public void handleMessage(Message msg) {
            mProgressHandler.handleMessage(msg);
        }
    }
}

上面的ProgressHandler是一个抽象类,通过Handler对象进行发送和处理消息。于是定义了两个抽象方法sendMessagehandleMessage,之后又定义了一个抽象方法onProgress来处理下载进度的显示,而这个onProgress则是需要我们在UI线程内进行调用。最后创建了一个继承自HandlerResponseHandler内部类,为了避免内存泄露我们使用static关键字。

下面来创建一个DownloadProgressHandler类,继承于ProgressHandler,用来发送和处理下载消息。

public abstract class DownloadProgressHandler extends ProgressHandler {

    private static final int DOWNLOAD_PROGRESS = 1;
    protected ResponseHandler mHandler = new ResponseHandler(this, Looper.getMainLooper());

    @Override
    protected void sendMessage(ProgressBean progressBean) {
        mHandler.obtainMessage(DOWNLOAD_PROGRESS, progressBean).sendToTarget();
    }

    @Override
    protected void handleMessage(Message message) {
        switch (message.what) {
            case DOWNLOAD_PROGRESS:
                ProgressBean progressBean = (ProgressBean) message.obj;
                onProgress(progressBean.getBytesRead(), progressBean.getContentLength(), progressBean.isDone());
        }
    }
}

在创建一个UploadProgressHandler类,继承于ProgressHandler,用来发送和处理上传消息。

public abstract class UploadProgressHandler extends ProgressHandler {

    private static final int UPLOAD_PROGRESS = 0;
    protected ResponseHandler mHandler = new ResponseHandler(this, Looper.getMainLooper());

    @Override
    protected void sendMessage(ProgressBean progressBean) {
        mHandler.obtainMessage(UPLOAD_PROGRESS, progressBean).sendToTarget();
    }

    @Override
    protected void handleMessage(Message message) {
        switch (message.what) {
            case UPLOAD_PROGRESS:
                ProgressBean progressBean = (ProgressBean) message.obj;
                onProgress(progressBean.getBytesRead(), progressBean.getContentLength(), progressBean.isDone());
        }
    }
}

定义上传下载回调Service

/**
* 创建带响应进度(下载进度)回调的Service
 */
public static <T> T getDownLoadService(Class<T> tClass) {
    return builder
            .client(ProgressHelper.addProgressDownLoadBuilder(OkHttpClientBuilder()))
            .build()
            .create(tClass);
}

/**
 * 创建带请求进度(上传进度)回调的Service
 */
public static <T> T getUpLoadService(Class<T> tClass) {
    return builder
            .client(ProgressHelper.addProgressUpLoadBuilder(OkHttpClientBuilder()))
            .build()
            .create(tClass);
}

定义上传下载API接口

@GET
Call<ResponseBody> requestByDownload(@Url String url);

@Multipart
@POST("{url}")
Call<ResponseBody> requestByUpload(@Path("url") String url, @PartMap Map<String, RequestBody> params);

使用方式

/**
 * 下载文件
 */
private void requestByDownload() {
    final ProgressDialog dialog = new ProgressDialog(this);
    dialog.setProgressNumberFormat("%1d KB/%2d KB");
    dialog.setTitle("下载");
    dialog.setMessage("正在下载,请稍后...");
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    dialog.setCancelable(false);
    dialog.show();

    ProgressHelper.setProgressHandler(new DownloadProgressHandler() {
        @Override
        protected void onProgress(long bytesRead, long contentLength, boolean done) {
            LogUtil.e("是否在主线程中运行: " + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
            LogUtil.e("onProgress: " + String.format("%d%% \n", (100 * bytesRead) / contentLength));
            LogUtil.e("done: " + String.valueOf(done));
            dialog.setMax((int) (contentLength / 1024));
            dialog.setProgress((int) (bytesRead / 1024));

            if (done) {
                dialog.dismiss();
            }
        }
    });

    HttpApi mDownLoadHttpApi = HttpUtil.getDownLoadService(HttpApi.class);
    Call<ResponseBody> call = mDownLoadHttpApi.requestByDownload(Constant.mobileSafe);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                InputStream is = null;
                FileOutputStream fos = null;
                BufferedInputStream bis = null;
                try {
                    //获取存储文件夹
                    String dirName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/RetrofitDownload/";
                    File file = new File(dirName);
                    //如果目录不存在则创建
                    if (!file.exists()) {
                        file.mkdir();
                    }

                    File fileName = new File(dirName + "mobileSafe.apk");
                    if (fileName.exists()) {
                        fileName.delete();
                        fileName.createNewFile();
                    } else {
                        fileName.createNewFile();
                    }

                    is = response.body().byteStream();
                    fos = new FileOutputStream(fileName);
                    bis = new BufferedInputStream(is);

                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = bis.read(buffer)) != -1) {
                        fos.write(buffer, 0, len);
                        fos.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (is != null) {
                        try {
                            is.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (bis != null) {
                        try {
                            bis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            ToastUtil.showText(t.getMessage());
        }
    });
}

/**
 * 上传文件
 */
private void requestByUpload() {
    final ProgressDialog dialog = new ProgressDialog(this);
    dialog.setProgressNumberFormat("%1d KB/%2d KB");
    dialog.setTitle("上传");
    dialog.setMessage("正在上传,请稍后...");
    dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    dialog.setCancelable(false);
    dialog.show();

    ProgressHelper.setProgressHandler(new UploadProgressHandler() {
        @Override
        protected void onProgress(long bytesRead, long contentLength, boolean done) {
            LogUtil.e("是否在主线程中运行: " + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
            LogUtil.e("onProgress: " + String.format("%d%% \n", (100 * bytesRead) / contentLength));
            LogUtil.e("done: " + String.valueOf(done));
            dialog.setMax((int) (contentLength / 1024));
            dialog.setProgress((int) (bytesRead / 1024));

            if (done) {
                dialog.dismiss();
            }
        }
    });

    HttpApi mUpLoadHttpApi = HttpUtil.getUpLoadService(HttpApi.class);
    Map<String, RequestBody> params = new HashMap<>();
    Call<ResponseBody> call = mUpLoadHttpApi.requestByUpload("", params);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            ToastUtil.showText(t.getMessage());
        }
    });
}

项目地址 ☞ 传送门

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Retrofit2.0使用详解 的相关文章

  • 国考省考申论:归纳概括多个主体身上的优秀品质,透过动词现象(怎么做的),找到名词(精神品质)本质

    国考省考申论 归纳概括多个主体身上的优秀品质 透过动词现象 怎么做的 找到名词 精神品质 本质 2022找工作是学历 能力和运气的超强结合体 公务员特招重点就是专业技能 附带行测和申论 而常规国考省考最重要的还是申论和行测 所以大家认真准备
  • dede:list分页与控制文章标题显示字数

    关于dedecms分页 百度上也有许多教程 本人记性不好所以写个博客保存下来 pagesize控制每页显示条数 在 dede list 结束标签 后边写上 dede pagelist 标签即可 如何控制文章显示字 让溢出部分用 代替呢 其实
  • IPS与防火墙旁路部署

    一 防火墙旁路部署 实现防护功能的同时 可以完全不需改变用户的网络环境 并且可以避免设备对用户网络造成中断的风险 用于把设备接在交换机的镜像口或者接在 HUB 上 保证外网用户访问服务器的数据经过此交换机 并且设置镜像口的时候需要同时镜像上
  • iview+page封装+强制刷新

    前言 iview的page封装 缺点无法固定页码按钮数量 而且current的页面恢复选中第一个实现不了 这里动态写了强制刷新的方法 下面是组件cpage vue
  • 【Spring

    上篇 Spring 事件监听概述 对 Spring 事件监听的机制有了个基本的了解 本篇来详细的解读下Spring 的 事件监听机制 事件监听详解 ApplicationEvent ApplicationListener 基于注释 异步 排
  • 多态的实现

    多态 之前介绍过多态的概念就是基类引用派生类对象且和派生类有相同的同名覆盖函数 那么现在我们就具体讲讲怎么实现多态 类方法实现多态性有两种方法 1 方法重载 可以声明多个同名但参数个数 类型 和顺序不同的方法 编译时根据参数 个数 类型和顺
  • win环境下SSH key 配置

    从Gitlab上拉取代码报错 Warning Permanently added gitlab wang cn 47 94 8 13 ECDSA to the list of known hosts Connection closed by
  • windows下使用FFmpeg生成YUV视频文件并播放(通过命令的方式)

    一 YUV的定义 YUV是一种颜色编码方法 它跟我们常见的RGB格式区分开来 常使用在各个视频处理组件中 其中 Y 代表明亮度 U 和 V 代表其色度 视频播放器把市面上流行的MP4等格式文件的视频部分解码出来 得到的一般会是YUV格式的数
  • Java方法重写注意事项

    系原创 只为需要它的人 Java方法重写的几个要求 重写的方法与父类方法签名 方法名称和参数列表 相同 子类重写的方法访问修饰符范围不能低于父类 父类的私有方法不能被重写 static修饰的方法不能被重写 返回值类型 如果父类中方法返回值类
  • 解决mybatis一对多只能获取部分数据的问题

    需求 building表和position表 Building类中含有List positionList mybatis查询方法需要查询到所有的building和building中含有所有的position 问题 sql语句和一对多方法写的
  • SQL如何进行优化

    SQL优化 前言 对于初级程序开发工程师而言 SQL是很多人的弱项 为此我给大家来做一下总结 希望能够帮到你们 课程说明 1 对MySQL SQL优化方案做讲解 学习如何排查慢查询 SQL优化 分页查询优化 一页一页的往下面翻这种查询方式
  • 针对读写操作频繁的应用系统的LINUX调优设置

    在线签约系统调优 项目类型 限制型应用 需要频繁调用 进行签章 调用的 保存在 磁盘中 项目业务设计实现 这里简要说明一下业务流程 前端业务系统过来的请求通过Nignx进行分流 通过网关DSS 将各自的请求转发到相应的老 新签章系统进行处理
  • 小白学Redis系列:Redis持久化

    Redis作为缓存数据库 区别于常规数据库的地方就在于Redis将数据存储在内存中 而不是硬盘中 因此数据的IO就十分快速 非常适合一些电商网站等数据IO频繁的场景 当然 内存中的数据在掉电之后就会被清空 而Redis的持久化功能使得内存中
  • linux后台运行之screen和nohup

    linux后台运行之screen和nohup 3 1 nohup命令 如果你正在运行一个进程 而且你觉得在退出帐户时该进程还不会结束 那么可以使用nohup命令 该命令可以在你退出帐户 关闭终端之后继续运行相应的进程 nohup就是不挂起的
  • 进制及进制转换详解。原码、反码、移码,补码区别介绍。(通俗易懂)

    目录 前言 一 十进制 n进制 进制转换详解 1 先说说什么是进制 2 二进制介绍 3 十进制 n进制 进制转换详解 重点 十进制 gt n进制 2 8 16 n进制 2 8 16 gt 十进制 非十进制间的互相转化 二 原码 反码 移码
  • Python数据可视化 - 如何自定义Matplotlib图例?

    Python数据可视化 如何自定义Matplotlib图例 Matplotlib 是一个最常用的Python数据可视化库 它允许我们创建各种类型的图形 包括直方图 折线图 散点图 饼状图等 当我们绘制多个子图或多个曲线时 我们可能需要图例来
  • SpringBoot 整合 ElasticSearch

    整合前先理解几个概念 与关键字 开始前给大家推荐一款很火的刷题 面试求职网站 https www nowcoder com link pc csdncpt xiaoying java 索引
  • Java编程练习题:Demo96 - Demo105(多维数组)

    目录 Demo96 代数方面 两个矩阵相乘 编写两个矩阵相乘的方法 Demo97 距离最近的两个点 程序清单8 3给出找到二维空间中距离最近的两个点的程序 修改该程序 让程序能够找出在三维空间上距离最近的两个点 Demo98 最大的行和列

随机推荐

  • flink-addSource和addSink分别是kafka、自定义数据、mysql、hbase的java实现

    flink主程序 public class FinkTest public static void main String args throws Exception StreamExecutionEnvironment env Strea
  • Python 和 A-frame实现从图像创建 3D 模型--附完整示例代码

    介绍 虚拟现实是指由计算机生成的模拟 允许用户使用特殊耳机进行交互 简而言之 它是由计算机创建的另类现实 而耳机可以让人们沉浸在该现实中 根据 Allied Market Research 的数据 到 2026 年 VR 内容创作市场将达到
  • 基于若依框架的微信小程序登录

    一 用户表结构 CREATE TABLE bus user user id varchar 32 COLLATE utf8mb4 bin NOT NULL COMMENT 用户id parent id varchar 32 CHARACTE
  • 秋招提前批已来,万字长文教你如何增加面试大厂的成功率

    本文是笔者在春季在 前端早早聊 手动笔芯 的面试专场分享的文字稿 主要针对前端社招 校招和实习的同学仅供参考 感兴趣的同学可以点击链接查看PPT和录屏 前端如何提高面试大厂的通过率 字节跳动秋季招聘提前批已经启动 欢迎投递幸福里业务线 内推
  • 嵌入式 ARM 汇编编程例题

    文章目录 用汇编语言实现 128 位数的减法 已知 32 位变量 X Y 存放在存储器的地址 0x90010 0x90014 中 要求实现 Z X Y 其中 Z 的值存放在 0x90018 中 已知 32 位有符号数 X 存放在存储器的地址
  • python request第三方库介绍

    python request第三方库介绍 快速上手 迫不及待了吗 本页内容为如何入门Requests提供了很好的指引 其假设你已经安装了Requests 如果还没有 去 安装 一节看看吧 首先 确认一下 Requests 已安装 Reque
  • mybatis查询mysql时间格式化 DATE_FORMAT

    在数据库中对应的是DateTime 查询参数为String类型 缺少时分秒的情况下使用 select from order where isDelete 0
  • 笔记 —— ByteArrayOutputStream

    内存输出流 ByteArrayOutputStream 此类实现了一个输出流 其中的数据被写入一个 byte 数组 缓冲区会随着数据的不断写入而自动增长 可使用 toByteArray 和 toString 获取数据 两个构造函数 1 By
  • Linux系统编程makefile制作动态库和静态库

    目录 制作动态库 制作静态库 首先准备简单的add c sub c main c head h 具体代码如下 head h文件 int Add int a int b int Sub int a int b add c文件 include
  • 山洪灾害监测预警系统解决方案

    一 方案概述 山洪灾害是指山丘地区由降雨引起的洪水 泥石流和滑坡灾害 近年来 我国突发性 局部性极端强降雨引发的山洪灾害导致大量人员伤亡 占洪涝灾害死亡总人数的比例趋上升趋势 群死群伤事件时有发生 山洪灾害严重制约山区和丘陵地区经济发展 人
  • SVM支持向量机学习——使用MATLAB实现基于SVM的数据二分类

    SVM支持向量机学习 使用MATLAB实现基于SVM的数据二分类 支持向量机 Support Vector Machine SVM 是一种广泛应用于分类 回归和异常检测等领域的算法 它的优点在于具有较高的准确性 鲁棒性和可扩展性 在本文中
  • Hyper-v 虚拟机挂载物理硬盘的方法-Windows Server 2022/2025

    起因 之前我写过一篇介绍在KVM虚拟机体系下 如何直接挂载物理硬盘和物理分区的方法 KVM虚拟机直接挂栽物理硬盘分区的方法 给libvirt虚机挂载磁盘 lggirls的博客 CSDN博客 近期帮助一个朋友搭建局域网 其中有OA系统要用到w
  • Get to know yosys & yosys-abc

    In this blog I m going to give some instructions about yosys yosys abc in Linux Environment yosys 0 7 gcc 5 4 0 ubuntu 1
  • verilog 基本语法 {}大括号的使用

    的基本使用是两个 一个是拼接 一个是复制 下面列举了几种常见用法 基本用法 表示拼接 第一位 第二位 表示复制 4 a 等同于 a a a a 所以 13 1 b1 就表示将13个1拼接起来 即13 b1111111111111 拼接语法详
  • 学习总结——按下按键灯亮,再次按下按键,灯灭

    按键控制灯的亮灭 1 主要实现按键控制灯的亮灭 按键按下 灯亮 再次按下 灯灭 主要对实现的逻辑进行控制 逻辑清晰 很简单 实现的方法有两种 方法1 将按键按下的值赋值给一个变量 变量除以2的值的是基数或者偶数来确定灯亮还是灯灭 程序中设置
  • 堆栈 对比

    https www cnblogs com guoxiaoyan p 8664150 html
  • STL — Set/Multiset容器

    1 1 Set容器基本概念 Set的特性是 所有元素都会根据元素的键值自动被排序 Set的元素不像map那样可以同时拥有实值和键值 set的元素即是键值又是实值 set不允许两个元素有相同的键值 我们可以通过set的迭代器改变set元素的值
  • POI解析word\pdf中表格

  • Windows10下SlowFast环境安装和运行

    SlowFast安装详解 第一步 下载官方源码 第二步 我搭建的环境配置 第二步 安装其他包以及出现的问题 第三步 构建SlowFast 第四部 下载权重和标签 第五步 更改参数 第六步 当然是运行啦 第一步 下载官方源码 github代码
  • Retrofit2.0使用详解

    简介 Retrofit是由Square公司提供的开源产品 为Android平台的应用提供一个类型安全的REST客户端 其实质上是对OkHttp的封装 使用面向接口的方式进行网络请求 利用动态生成的代理类封装了网络接口请求的底层 将REST