Retrofit的封装

Retrofit是Square公司基于restful风格推出的网络框架封装,截止目前github已经有了37.2kstart,可见他的受欢迎程度非常高,Retrofit基于Okhttp封装,具有非常强大的解耦特点,高度的灵活解耦导致使用起来不够简洁,下面对Retrofit进行一次二次的封装,在使用上更加简洁。封装之后具有一下特点:

  • 支持reftofit的单例模式配置,一次配置多处使用。
  • 支持动态切换baseUrl不影响原有的baseUrl。
  • 支持通用格式的网络请求,返回对象或者字符串。
  • 支持快捷的请求方式返回字符串格式的数据。
  • 支持大文件下载和进度回调以及动态取消。
  • 支持单文件和多文件上传。

一、导入依赖库

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'

二、reftofit的创建

Retrofit的创建方式如下:

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

1、addConverterFactory

用于添加Retrofit返回数据的支持格式,通常有两个返回字符串格式或者返回一个对象,这块需要添加对应的依赖:

  • 返回字符串:.addConverterFactory(GsonConverterFactory.create())
  • 返回对象:.addConverterFactory(ScalarsConverterFactory.create())

二者不能同时添加,同时添加只有后添加的生效,需要依据需求或者项目配置。

2、baseUrl

用于设置请求的协议IP和端口,在配置时候需要确定,但是有些时候有些接口baseUrl会变化,这时候需要单独配置了,原始的retrofit不支持动态配置,目前市面上的解决方案是两种,一种是重新创建一个Retrofit设置baseUrl,另一种是直接在拦截器里面处理,通过Api接口动态配置header,获取对应的baseUrl,使其生效,如果没有配置就使用默认的。

3、Retrofit对象的封装

基于上面两点对retrofit进行封装,命名为RetrofitManager,该管理类中有一个统一的baseUrl配置,需要提前配置好,在请求中直接使用该默认的配置,如果有需要切换的baseUrl,额外创建一个Retrofit,为了避免每次调用创建,把他放在一个Map集合中,每次获取Retrofit根据baseUrl从集合中读取,如果没有则创建,这样可以避免频繁的创建不同baseUrl的Retrofit。另外换需要对于返回数据是String或者为对象的Retrofit的支持。

    /**
     * 返回全局对象
     *
     * 解析为对象
     */
    public Retrofit getRetrofit() {
        checkBaseUrl();
        Retrofit retrofit = retrofitMap.get(baseUrl);
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            retrofitMap.put(baseUrl, retrofit);
        }
        return retrofit;
    }

    /**
     * 返回局部对象
     *
     * 解析为对象
     */
    public Retrofit getRetrofit(String baseUrl) {
        checkBaseUrl(baseUrl);
        Retrofit retrofit = retrofitMap.get(baseUrl);
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            retrofitMap.put(baseUrl, retrofit);
        }
        return retrofit;
    }

    /**
     *  返回全局对象
     *
     *  解析为字符串
     */
    public Retrofit getStringRetrofit() {
        checkBaseUrl();
        Retrofit retrofit = retrofitMap.get(baseUrl);
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .build();
            retrofitMap.put(baseUrl, retrofit);
        }
        return retrofit;
    }

    /**
     * 返回局部对象
     *
     * 解析为字符串
     */
    public Retrofit getStringRetrofit(String baseUrl) {
        checkBaseUrl();
        Retrofit retrofit = retrofitMap.get(baseUrl);
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .build();
            retrofitMap.put(baseUrl, retrofit);
        }
        return retrofit;
    }

    /**
     * 设置全局的url
     * @param baseUrl
     * @return
     */
    public  RetrofitManager setBaseUrl(String baseUrl) {
        RetrofitManager.baseUrl = baseUrl;
        return this;
    }

这样在使用中如果有全局统一的baseUrl,则设置:

  • Retrofit retrofit = RetrofitManager.getInstance().setBaseUrl("http://127.0.0.1:3000/").getStringRetrofit();
  • Retrofit retrofit = RetrofitManager.getInstance().setBaseUrl("http://127.0.0.1:3000/").getRetrofit();

如果是需要设置其他的baseUrl,则设置:

  • Retrofit retrofit = RetrofitManager.getInstance().getRetrofit("http://www.weather.com.cn/");
  • Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit("http://www.weather.com.cn/");

三、Retrofit的通用的请求

    LoginApi loginApi = retrofit.create(LoginApi.class);
    Call<ResponseBody> call = loginApi.getName();
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            
        }

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

        }
    });

Retrofit的请求如上,支持get/post/delete/patch/options等方式的请求,配置方式灵活多变,高度解耦,为了兼容各个请求和内容回调,和请求参数配置的解耦,既支持返回为String有支持返回为对象Call这个对象不能直接处理,需要用户配置,否则就是去了通用性,同时能够兼顾请求的简洁性,仅对回调做了处理,回调如下:

	public interface ResponseCallback<T> {

	    void onSuccess(T t);

	    void onFailure(Throwable t);
	}

封装为如下,T表示用户希望返回的数据类型和Call中的泛型保持一致:

	public static <T> void executeAsync(Call<T> call, ResponseCallback<T> callback) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                if (response.isSuccessful() && callback != null) {
                    callback.onSuccess(response.body());
                }
            }

            @Override
            public void onFailure(Call<T> call, Throwable t) {
                if (callback != null) {
                    callback.onFailure(t);
                }
            }
        });
    }

使用封装后的内容如下:

     LoginApi loginApi = retrofit.create(LoginApi.class);
     Call<ResponseBody> call = loginApi.getName();
     HttpUtils.executeAsync(call, new ResponseCallback<ResponseBody>() {
        @Override
        public void onSuccess(ResponseBody responseBody) {
            
        }

        @Override
        public void onFailure(Throwable t) {

        }
    });

四、get和post请求

除了通用的网络请求外,很多时候是get或者post请求,需要返回的格式是字符串的数据,这时候需要对通用的请求进一步封装,返回String格式:

	public interface StringCallback extends ResponseCallback<String> {

	}

对于get和post请求,分为两大类,没有参数的请求和有参数的请求,进一步缩小了请求范围。统一一个StringApi:

	public interface StringApi {

	    //带参数的通用get请求
	    @GET()
	    Call<String> executeGet(@Url String url, @QueryMap Map<String, String> maps);

	    //不带参数的通用get请求
	    @GET()
	    Call<String> executeGet(@Url String url);

	    //不带参数的通用post请求
	    @POST()
	    Call<String> executePost( @Url String url);

	    //带参数的通用post请求
	    @POST()
	    @FormUrlEncoded
	    Call<String> executePost( @Url String url, @FieldMap Map<String, String> maps);

	}

对于call.enqueue的结果统一出来回调

	public class RetrofitStringCallback implements Callback<String> {

	    private StringCallback callback;

	    public RetrofitStringCallback(StringCallback callback) {
	        this.callback = callback;
	    }

	    @Override
	    public void onResponse(Call<String> call, Response<String> response) {
	        if (response.isSuccessful()) {
	            callback.onSuccess(response.body());
	        } else {
	            try {
	                Throwable throwable = new Throwable(response.errorBody().string());
	                callback.onFailure(throwable);
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	    }

	    @Override
	    public void onFailure(Call<String> call, Throwable t) {
	        callback.onFailure(t);
	    }
	}

具体请求封装如下:

	/**
     * get:无参数
     */
    public static void get(String url, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        get(retrofit, url, callback);
    }

    public static void get(Retrofit retrofit, String url, StringCallback callback) {
        StringApi baseApi = retrofit.create(StringApi.class);
        Call<String> call = baseApi.executeGet(url);
        call.enqueue(new RetrofitStringCallback(callback));
    }

    /**
     * get:有参数
     */
    public static void get(String url, Map<String, String> map, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        get(retrofit, url, map, callback);
    }

    public static void get(Retrofit retrofit, String url, Map<String, String> map, StringCallback callback) {
        StringApi baseApi = retrofit.create(StringApi.class);
        Call<String> call = baseApi.executeGet(url, map);
        call.enqueue(new RetrofitStringCallback(callback));
    }


    /**
     * post:无参数
     */
    public static void post(String url, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        post(retrofit, url, callback);
    }

    public static void post(Retrofit retrofit, String url, StringCallback callback) {
        StringApi baseApi = retrofit.create(StringApi.class);
        Call<String> call = baseApi.executePost(url);
        call.enqueue(new RetrofitStringCallback(callback));
    }

    /**
     * post有参数
     */
    public static void post(String url, Map<String, String> map, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        post(retrofit, url, map, callback);
    }

    public static void post(Retrofit retrofit, String url, Map<String, String> map, StringCallback callback) {
        StringApi baseApi = retrofit.create(StringApi.class);
        Call<String> call = baseApi.executePost(url, map);
        call.enqueue(new RetrofitStringCallback(callback));
    }

这样,如果是get或者post请求就变的很简单:

    get("aoi/weather", new StringCallback() {

        @Override
        public void onSuccess(String s) {
            Log.d(TAG, "onSuccess: " + s);
        }

        @Override
        public void onFailure(Throwable t) {
            Log.d(TAG, "onSuccess: " + t);
        }

    });

四、文件下载

1、回调定义

public interface DownLoadListener {

     void onStart();

     void progress(int progress, float currentSize, float totalSize);

     void onFinish(String path);

     void onFailure(String msg);

     void onCancel(String tag);

}

实现类:

public abstract class DownLoadCallback implements DownLoadListener {

    private String folder;
    private String fileName;
    private String tag;

    public DownLoadCallback(String folder, String fileName) {
        this.folder = folder;
        this.fileName = fileName;
    }

    public DownLoadCallback(String folder, String fileName, String tag) {
        this.folder = folder;
        this.fileName = fileName;
        this.tag = tag;
    }


    public void onStart() {

    }

    public void progress(int progress, float currentSize, float totalSize) {

    }

    @Override
    public void onCancel(String tag) {

    }

    public String getFolder() {
        return folder;
    }

    public String getFileName() {
        return fileName;
    }

    public String getTag() {
        return tag;
    }
}

tag用于标记那是那个请求,用于请求的取消和请求的回调标记,如果没有这个需求,则可以不用。

2、DownLoadApi

和get/post的请求一样,文件下载也定义两个接口,一个是有参数的下载,另一个是无参数的下载,返回格式必须是ResponseBody,用于处理下载结果,如果是大文件的话,需要加上@Streaming,因为Retrofit默认下载中回一次性把内容下载到内存中才回调结果,加上@Streaming之后会以流的方式读取写入,否则进度回调不到。

	public interface DownLoadApi {

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

	    @Streaming
	    @GET()
	    Call<ResponseBody> downLoadApi( @Url String url, @QueryMap Map<String, String> maps);

	}

3、封装下载

文件下载是比较耗时间的任务,需要放在子线程中执行,最好放在个线程池executor中,下面直接看下载入口:

    /**
     * 文件下载
     */
    public void executeDownLoadAsync(Retrofit retrofit, String downloadUrl, DownLoadCallback callback) {
        DownLoadApi baseApi = retrofit.create(DownLoadApi.class);
        Call<ResponseBody> call = baseApi.downLoadApi(downloadUrl);
        setDownLoadTag(callback.getTag(), call);
        executor.execute(new DownloadRunnable(call, callback, executor, callMap));
    }

    public void executeDownLoadAsync(Retrofit retrofit, String downloadUrl, Map<String, String> map, DownLoadCallback callback) {
        DownLoadApi baseApi = retrofit.create(DownLoadApi.class);
        Call<ResponseBody> call = baseApi.downLoadApi(downloadUrl, map);
        setDownLoadTag(callback.getTag(), call);
        executor.execute(new DownloadRunnable(call, callback, executor, callMap));
    }

    /**
     * 设置取消下载回调
     */
    private void setDownLoadTag(String tag, Call<ResponseBody> call) {
        if (TextUtils.isEmpty(tag)) {
            return;
        }
        Call put = callMap.get(tag);
        if (put == null) {
            callMap.put(tag, call);
        }
    }

4、下载任务

具体的下载任务放在了DownloadRunnable里面,实现了Runnable接口,run方法中直接发起请求:
 

 call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (callback == null) {
                return;
            }
            if (response.isSuccessful()) {
                callback.onStart();
                executor.execute(() -> writeResponseBodyToDisk(response.body()));
            } else {
                callback.onFailure(response.message());
            }
        }

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

因为回调是在主线程的,所以需要开辟一块子线程进行磁盘的写入,调用executor.execute只写入:
 

     private void writeResponseBodyToDisk(ResponseBody body) {
        try {
            File file = new File(callback.getFolder() + File.separator + callback.getFileName());
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdir();
            }
            if (file.exists()) {
                file.delete();
            }
            if (!file.createNewFile()) {
                handler.post(() -> callback.onFailure("file path is not exit"));
                return;
            }
            InputStream inputStream = null;
            OutputStream outputStream = null;
            ProgerssRun progerssRun = new ProgerssRun(callback, file.getAbsolutePath());
            try {
                byte[] fileReader = new byte[1024 * 4];
                long fileSize = body.contentLength();
                long downLoadSize = 0;
                inputStream = body.byteStream();
                outputStream = new FileOutputStream(file);
                handler.postDelayed(progerssRun, 300);
                while (true) {
                    int read = inputStream.read(fileReader);
                    if (read == -1) {
                        break;
                    }
                    outputStream.write(fileReader, 0, read);
                    downLoadSize += read;
                    //回调结果保留几位小数
                    DecimalFormat df = new DecimalFormat("0.000");
                    df.setRoundingMode(RoundingMode.HALF_UP);
                    //将K转为M,保留3位小数返回
                    float currentSize = Float.parseFloat(df.format(downLoadSize / (1024 * 1024f)));
                    float totalSize = Float.parseFloat(df.format(fileSize / (1024 * 1024f)));
                    int progress = (int) (downLoadSize * 100 / fileSize);
                    progerssRun.setProgress(progress, currentSize, totalSize);
                }
                outputStream.flush();

            } catch (IOException e) {
                handler.post(() -> {
                    handler.removeCallbacks(progerssRun);
                    callback.onCancel(callback.getTag());
                });
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (!TextUtils.isEmpty(callback.getTag()) && callMap.get(callback.getTag()) != null) {
                    callMap.remove(callback.getTag());
                }
            }
        } catch (IOException e) {
            handler.post(() -> {
                callback.onFailure(e.getMessage());
            });
        }
    }

上面的handler是主线程的handler,将结果回调给主线程,便于数据的设置,最后关于进度回调的类:

   public static class ProgerssRun implements Runnable {

        private int progress;
        private float currentSize;
        private float totalSize;
        private DownLoadCallback callback;
        private String filePath;

        public ProgerssRun(DownLoadCallback callback, String filePath) {
            this.callback = callback;
            this.filePath = filePath;
        }

        public void setProgress(int progress, float currentSize, float totalSize) {
            this.progress = progress;
            this.currentSize = currentSize;
            this.totalSize = totalSize;
        }

        @Override
        public void run() {
            if (currentSize == totalSize) {
                callback.progress(progress, currentSize, totalSize);
                handler.removeCallbacks(this);
                handler.post(() -> callback.onFinish(filePath));
            } else {
                callback.progress(progress, currentSize, totalSize);
                //延迟300毫秒回调
                handler.postDelayed(this, 300);
            }
        }
    }

这样就完成了文件的下载,用法如下:

    downLoadFile(retrofit, url, new DownLoadCallback(folder, fileFile, tag) {

            @Override
            public void onFinish(String path) {
                
            }

            @Override
            public void onFailure(String msg) {
                
            }

            @Override
            public void progress(int progress, float currentSize, float totalSize) {
               
            }

            @Override
            public void onCancel(String tag) {
                
            }
    });

5、取消下载

在请求的时候设置有一个tag,如果需要取消下载的需求,根据tag进行取消,如果不需要请求可以不用设置:

	/**
     * 取消指定的请求
     *
     * @param tag
     */
    public void cancel(String tag) {
        if (TextUtils.isEmpty(tag)) {
            return;
        }
        Call call = callMap.get(tag);
        if (call != null) {
            callMap.remove(tag);
            if (!call.isCanceled()) {
                call.cancel();
            }
        }
    }

    /**
     * 取消所有的请求
     */
    public void cancelAll() {
        Iterator it = callMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            Call<ResponseBody> call = (Call<ResponseBody>) entry.getValue();
            call.cancel();
        }
    }

六、文件上传

总结了一下常用的文件上传的Api格式,具体需要那个,直接调用:

	public interface UpLoadApi {

	    /**
	     * 上传字符串
	     */
	    @FormUrlEncoded
	    @POST()
	    Call<String> uploadStringApi(@Url String url, @FieldMap Map<String, String> map);

	    @POST()
	    Call<String> uploadStringApi(@Url String url, @Body RequestBody body);


	    /**
	     * 上传单个文件
	     */
	    @Multipart
	    @POST()
	    Call<String> uploadFileApi(@Url String url, @Part MultipartBody.Part body);

	    /**
	     * 上传多个文件
	     */
	    @Multipart
	    @POST()
	    Call<String> uploadFilesApi(@Url String url, @PartMap Map<String, RequestBody> map);
	    
	    @Multipart
	    @POST()
	    Call<String> uploadFilesApi(@Url String url, @Part List<MultipartBody.Part> parts);

	    /**
	     * 文件字符串混合传递
	     */
	    @Multipart
	    @POST()
	    Call<String> uploadFileStringApi(@Url String url, @Part("body") RequestBody body, @Part MultipartBody.Part file);

	    /**
	     * 通用的上传:这种方式上传的时候,不能再接口上加上@Multipart的注解,否者会报错
	     */
	    @POST()
	    Call<String> uploadApi(@Url String url,@Body RequestBody body);

	}

1、单个文件上传

    /**
     * 上传文件单个
     * 
     * path:文件路径
     * action:服务器接收的字段,和服务器对应
     */
    public static void upLoadFile(String url, String path, String action, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        upLoadFile(retrofit, url, path, action, callback);
    }

    public static void upLoadFile(Retrofit retrofit, String url, String path, String action, StringCallback callback) {
        UpLoadApi upLoadApi = retrofit.create(UpLoadApi.class);
        File file = new File(path);
        RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part part = MultipartBody.Part.createFormData(action, file.getName(), body);
        Call<String> call = upLoadApi.uploadFileApi(url, part);
        call.enqueue(new RetrofitStringCallback(callback));
    }

使用:

	upLoadFile("upload", "/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_122945.png", "logo", new StringCallback() {

        @Override
        public void onSuccess(String s) {
            Log.d(TAG, "onSuccess: " + s);
        }

        @Override
        public void onFailure(Throwable t) {
            Log.d(TAG, "onFailure: " + t.getMessage());
        }
    });

2、多个文件上传

    /**
     * 上传多个文件
     * listPath:文件路径集合
     * action:服务器接收的字段,和服务器对应
     */
    public static void upLoadFiles(String url, List<String> listPath, String action, StringCallback callback) {
        Retrofit retrofit = RetrofitManager.getInstance().getStringRetrofit();
        upLoadFiles(retrofit, url, listPath, action, callback);
    }

    public static void upLoadFiles(Retrofit retrofit, String url, List<String> listPath, String action, StringCallback callback) {
        Map<String, RequestBody> map = new HashMap<>();
        for (int i = 0; i < listPath.size(); i++) {
            File file = new File(listPath.get(i));
            RequestBody body = RequestBody.create(MediaType.parse("image/*"), file);
            map.put("" + action + "\"; filename=\"" + file.getName(), body);
        }
        UpLoadApi upLoadApi = retrofit.create(UpLoadApi.class);
        Call<String> call = upLoadApi.uploadFilesApi(url, map);
        call.enqueue(new RetrofitStringCallback(callback));
    }

使用:

 	ArrayList<String> pathList = new ArrayList<>();
	pathList.add("/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_122945.png");
	pathList.add("/storage/emulated/0/DCIM/Screenshots/Screenshot_2020_1225_181054.png");
	HttpUtils.upLoadFiles("form", pathList, "logo", new StringCallback() {
	    @Override
	    public void onSuccess(String s) {
	        Log.d(TAG, "onSuccess: " + s);
	    }

	    @Override
	    public void onFailure(Throwable t) {
	        Log.d(TAG, "onFailure: " + t.getMessage());
	    }
	});

有什么改进方案,欢迎提出

上一篇:java-使用Spring Security 3.1.3记住我-不建议使用的默认构造函数


下一篇:取消Apache日志的PHP mysql_ *不推荐使用的错误