Android网络框架Retrofit2使用封装:Get/Post/文件上传/下载

背景

Android开发中的网络框架经过多年的发展,目前比较主流的就是Retrofit了,Retrofit2版本出现也有几年了,为了方便使用,特封装了一些关于Retrofit2的代码,分享给大家。

框架主要包括:

  • Get请求
  • Post请求
  • 文件上传
  • 文件下载

使用效果预览:

演示

Retrofit对象

Retrofit框架内部使用的还是OkHttp框架,在实例化的时候可以自定义OkHttpClient来实现一些个性化的设置,如超时时长、HTTPS协议支持等。

1、OkHttpClient实例

private static int TIME_OUT = 30; // 30秒超时断开连接

public static OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(sslSocketFactory, trustAllCert)
            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
            .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
            .build();

其中,自定义了sslSocketFactory和trustAllCert证书管理器:

private static X509TrustManager trustAllCert = new X509TrustManager() {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
    }

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

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

private static SSLSocketFactory sslSocketFactory = new SSLSocketFactoryCompat(trustAllCert);

SSLSocketFactoryCompat类是从网络上找的,这里就不多说了,详见源码部分。

2、Retrofit对象

/**
 * 网络框架单例
 * <p>因为示例中用到不同的接口地址,URL_BASE随便写了一个,如果是实际项目中,则设置为根路径即可</p>
 */
private static Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(Constant.URL_BASE)
        .client(client)
        .build();

定义好OkHttpClient后,Retrofit对象的构造就很简单,需要注意的是,一定要为retrofit对象指定好baseUrl,如果是后台地址有多个,写其中一个就可以了。

网络上有不少涉及到动态注册baseUrl的文章,写demo的时候并没有遇到此类问题,如果有遇到的话再寻找对应方案即可。

请求框架

定义回调

封装的框架让使用者传递一个回调的对象,在网络请求开始、结束、异常等阶段根据需要将调用相应的回调接口。

该回调接口还能够自动处理一些网络异常,并给予相应的提示(按需修改)。

/**
 * 网络请求结果处理类
 * @param <T> 请求结果封装对象
 */
public static abstract class ResultHandler<T> {
    Context context;

    public ResultHandler(Context context) {
        this.context = context;
    }

    /**
     * 判断网络是否未连接
     *
     * @return
     */
    public boolean isNetDisconnected() {
        return NetworkUtil.isNetDisconnected(context);
    }

    /**
     * 请求成功之前
     */
    public abstract void onBeforeResult();

    /**
     * 请求成功时
     *
     * @param t 结果数据
     */
    public abstract void onResult(T t);

    /**
     * 服务器出错
     */
    public void onServerError() {
        // 服务器处理出错
        Toast.makeText(context, R.string.net_server_error, Toast.LENGTH_SHORT).show();
    }

    /**
     * 请求失败后的处理
     */
    public abstract void onAfterFailure();

    /**
     * 请求失败时的处理
     *
     * @param t
     */
    public void onFailure(Throwable t) {
        if (t instanceof SocketTimeoutException || t instanceof ConnectException) {
            // 连接异常
            if (NetworkUtil.isNetworkConnected(context)) {
                // 服务器连接出错
                Toast.makeText(context, R.string.net_server_connected_error, Toast.LENGTH_SHORT).show();
            } else {
                // 手机网络不通
                Toast.makeText(context, R.string.net_not_connected, Toast.LENGTH_SHORT).show();
            }
        } else if (t instanceof Exception) {
            // 功能异常
            Toast.makeText(context, R.string.net_unknown_error, Toast.LENGTH_SHORT).show();
        }
    }
}

其中的string定义:

<string name="net_not_connected">发送请求失败,请检查网络连接</string>
<string name="net_server_connected_error">连接服务器失败,请稍后重试</string>
<string name="net_server_error">服务器处理请求失败,请稍后重试</string>
<string name="net_server_param_error">应用数据出错,请刷新重试</string>
<string name="net_unknown_error">未知错误,请稍后重试</string>

Get请求

1、定义GetRequest

Retrofit通过定义service来发起网络请求服务,Get请求服务可以定义为:

package com.dommy.retrofitframe.network;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Url;

/**
 * Get请求封装
 */
public interface GetRequest {

    /**
     * 发送Get请求请求
     * @param url URL路径
     * @return
     */
    @GET
    Call<ResponseBody> getUrl(@Url String url);
}

说明:

  • 需要加@GET注解;
  • url对应的参数需要加@Url注解。

2、封装get请求方法

定义static方法:

/**
 * 发送GET网络请求
 * @param url 请求地址
 * @param clazz 返回的数据类型
 * @param resultHandler 回调
 * @param <T> 泛型
 */
public static <T extends BaseResult> void sendGetRequest(String url, final Class<T> clazz, final ResultHandler<T> resultHandler) {
    // 判断网络连接状况
    if (resultHandler.isNetDisconnected()) {
        resultHandler.onAfterFailure();
        return;
    }

    GetRequest getRequest = retrofit.create(GetRequest.class);

    // 构建请求
    Call<ResponseBody> call = getRequest.getUrl(url);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            resultHandler.onBeforeResult();
            try {
                ResponseBody body = response.body();
                if (body == null) {
                    resultHandler.onServerError();
                    return;
                }
                String string = body.string();
                T t = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().fromJson(string, clazz);

                resultHandler.onResult(t);
            } catch (IOException e) {
                e.printStackTrace();
                resultHandler.onFailure(e);
            }
        }

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

其中,返回值的Gson转换过程,可以根据需要选择去掉;如果返回值都是json格式,可以保留,这样在使用处就可以直接拿到Bean对象,比较方便。

3、发起Get请求

RetrofitRequest.sendGetRequest(url, WeatherResult.class, new RetrofitRequest.ResultHandler<WeatherResult>(this) {
    @Override
    public void onBeforeResult() {
        // 这里可以放关闭loading
    }

    @Override
    public void onResult(WeatherResult weatherResult) {
        String weather = new Gson().toJson(weatherResult);
        tvContent.setText(weather);
    }

    @Override
    public void onAfterFailure() {
        // 这里可以放关闭loading
    }
});

其中,WeatherResult类:

package com.dommy.retrofitframe.network.result;

import com.google.gson.JsonObject;

public class WeatherResult extends BaseResult {
    private int code;
    private String msg;
    private JsonObject data; // 数据部分也是一个bean,用JsonObject代替了

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public JsonObject getData() {
        return data;
    }

    public void setData(JsonObject data) {
        this.data = data;
    }
}

Post请求

1、定义PostRequest

Post请求服务可以定义为:

package com.dommy.retrofitframe.network;

import java.util.Map;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Url;

/**
 * Post请求封装
 */
public interface PostRequest{

    /**
     * 发送Post请求
     * @param url URL路径
     * @param requestMap 请求参数
     * @return
     */
    @FormUrlEncoded
    @POST
    Call<ResponseBody> postMap(@Url String url, @FieldMap Map<String, String> requestMap);
}

说明:

  • 需要加@FormUrlEncoded、@POST注解;
  • url对应的参数需要加@Url注解。
  • post提交的参数需要加@FieldMap注解。

2、封装post请求方法

定义static方法:

/**
 * 发送post网络请求
 * @param url 请求地址
 * @param paramMap 参数列表
 * @param clazz 返回的数据类型
 * @param resultHandler 回调
 * @param <T> 泛型
 */
public static <T extends BaseResult> void sendPostRequest(String url, Map<String, String> paramMap, final Class<T> clazz, final ResultHandler<T> resultHandler) {
    // 判断网络连接状况
    if (resultHandler.isNetDisconnected()) {
        resultHandler.onAfterFailure();
        return;
    }
    PostRequest postRequest = retrofit.create(PostRequest.class);

    // 构建请求
    Call<ResponseBody> call = postRequest.postMap(url, paramMap);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            resultHandler.onBeforeResult();
            try {
                ResponseBody body = response.body();
                if (body == null) {
                    resultHandler.onServerError();
                    return;
                }
                String string = body.string();
                T t = new Gson().fromJson(string, clazz);

                resultHandler.onResult(t);
            } catch (IOException e) {
                e.printStackTrace();
                resultHandler.onFailure(e);
            }
        }

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

3、发起Post请求

RetrofitRequest.sendPostRequest(url, paramMap, WeatherResult.class, new RetrofitRequest.ResultHandler<WeatherResult>(this) {
    @Override
    public void onBeforeResult() {
        // 这里可以放关闭loading
    }

    @Override
    public void onResult(WeatherResult weatherResult) {
        String weather = new Gson().toJson(weatherResult);
        tvContent.setText(weather);
    }

    @Override
    public void onAfterFailure() {
        // 这里可以放关闭loading
    }
});

文件上传

1、定义FileRequest

package com.dommy.retrofitframe.network;

import java.util.Map;

import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PartMap;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

/**
 * 文件上传请求封装
 */
public interface FileRequest {

    /**
     * 上传文件请求
     * @param url      URL路径
     * @param paramMap 请求参数
     * @return
     */
    @Multipart
    @POST
    Call<ResponseBody> postFile(@Url String url, @PartMap Map<String, RequestBody> paramMap);

    /**
     * 下载文件get请求
     * @param url 链接地址
     * @return
     */
    @Streaming
    @GET
    Call<ResponseBody> download(@Url String url);
}

说明:

  • 文件上传需要额外添加@Multipart、@PartMap注解;
  • 文件下载需要额外添加@Streaming注解。

2、封装文件上传方法

定义static方法:

/**
 * 发送上传文件网络请求
 * @param url 请求地址
 * @param file 文件
 * @param clazz 返回的数据类型
 * @param resultHandler 回调
 * @param <T> 泛型
 */
public static <T extends BaseResult> void fileUpload(String url, File file, final Class<T> clazz, final ResultHandler<T> resultHandler) {
    // 判断网络连接状况
    if (resultHandler.isNetDisconnected()) {
        resultHandler.onAfterFailure();
        return;
    }
    FileRequest fileRequest = retrofit.create(FileRequest.class);

    Map<String, RequestBody> paramMap = new HashMap<>();
    addMultiPart(paramMap, "file", file);

    // 构建请求
    Call<ResponseBody> call = fileRequest.postFile(url, paramMap);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            resultHandler.onBeforeResult();
            try {
                ResponseBody body = response.body();
                if (body == null) {
                    resultHandler.onServerError();
                    return;
                }
                String string = body.string();
                T t = new Gson().fromJson(string, clazz);

                resultHandler.onResult(t);
            } catch (IOException e) {
                e.printStackTrace();
                resultHandler.onFailure(e);
            }
        }

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

3、发起文件上传请求

因为没有找到现成的上传文件接口,所以这里就随便弄了个接口做了个例子。自己写的时候在后台做一个接口文件的接口就行了。

服务端写法如果有不明白的,可以参见:《HttpClient4.5.2由Client客户端上传File文件流至Server服务端》一文中的server端代码。

File file = null;
try {
    // 通过新建文件替代文件寻址
    file = File.createTempFile("abc", "txt");
} catch (IOException e) {
}
String url = Constant.URL_LOGIN;
RetrofitRequest.fileUpload(url, file, BaseResult.class, new RetrofitRequest.ResultHandler<BaseResult>(this) {
    @Override
    public void onBeforeResult() {
        // 这里可以放关闭loading
    }

    @Override
    public void onResult(BaseResult baseResult) {
        tvContent.setText("上传成功");
    }

    @Override
    public void onAfterFailure() {
        // 这里可以放关闭loading
    }
});

文件下载

基于FileRequest的定义,封装fileDownload方法即可。

1、定义DownloadHandler

因为文件下载过程中涉及到进度的计算、显示,所以这里需要定义一个DownloadHandler回调:

/**
 * 文件下载回调
 */
public interface DownloadHandler {
    /**
     * 接收到数据体
     * @param body 响应体
     */
    public void onBody(ResponseBody body);

    /**
     * 文件下载出错
     */
    public void onError();
}

2、定义Retrofit对象

为了防止回调方法内部涉及到界面操作,出现NetworkOnMainThreadException问题,特别自定义一个回调方法执行器executorService。

ExecutorService executorService = Executors.newFixedThreadPool(1);
// 网络框架
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(Constant.URL_BASE)
        .callbackExecutor(executorService)
        .build();

3、封装文件下载方法

/**
 * 文件下载
 * @param url 请求地址
 * @param downloadHandler 回调接口
 */
public static void fileDownload(String url, final DownloadHandler downloadHandler) {
    // 回调方法执行器,定义回调在子线程中执行,避免Callback返回到MainThread,导致文件下载出现NetworkOnMainThreadException
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    // 网络框架
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constant.URL_BASE)
            .callbackExecutor(executorService)
            .build();

    FileRequest fileRequest = retrofit.create(FileRequest.class);
    // 构建请求
    Call<ResponseBody> call = fileRequest.download(url);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                // 写入文件
                downloadHandler.onBody(response.body());
            } else {
                downloadHandler.onError();
            }
        }

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

4、文件下载示例

RetrofitRequest.fileDownload(Constant.URL_DOWNLOAD, new RetrofitRequest.DownloadHandler() {
    @Override
    public void onBody(ResponseBody body) {
        if (!writeResponseBodyToDisk(body)) {
            mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
        }
    }

    @Override
    public void onError() {
        mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
    }
});

其中涉及到的对象和方法:

private static final int DOWNLOAD_ING = 1;// 下载中
private static final int DOWNLOAD_FINISH = 2;// 下载结束
private static final int DOWNLOAD_ERROR = -1;// 下载出错

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case DOWNLOAD_ING:
                // 正在下载
                showProgress();
                break;
            case DOWNLOAD_FINISH:
                // 下载完成
                onDownloadFinish();
                break;
            case DOWNLOAD_ERROR:
                // 出错
                onDownloadError();
                break;
        }
    }
};
    
/**
 * 写文件入磁盘
 *
 * @param body 请求结果
 * @return boolean 是否下载写入成功
 */
private boolean writeResponseBodyToDisk(ResponseBody body) {
    savePath = StorageUtil.getDownloadPath();
    File apkFile = new File(savePath, fileName);
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        byte[] fileReader = new byte[4096];
        // 获取文件大小
        long fileSize = body.contentLength();
        long fileSizeDownloaded = 0;
        inputStream = body.byteStream();
        outputStream = new FileOutputStream(apkFile);

        // byte转Kbyte
        BigDecimal bd1024 = new BigDecimal(1024);
        totalByte = new BigDecimal(fileSize).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();

        // 只要没有取消就一直下载数据
        while (!cancelUpdate) {
            int read = inputStream.read(fileReader);
            if (read == -1) {
                // 下载完成
                mHandler.sendEmptyMessage(DOWNLOAD_FINISH);
                break;
            }
            outputStream.write(fileReader, 0, read);
            fileSizeDownloaded += read;
            // 计算进度
            progress = (int) (((float) (fileSizeDownloaded * 100.0 / fileSize)));
            downByte = new BigDecimal(fileSizeDownloaded).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();
            // 子线程中,借助handler更新界面
            mHandler.sendEmptyMessage(DOWNLOAD_ING);
        }
        outputStream.flush();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 显示下载进度
 */
private void showProgress() {
    String text = progress + "%  |  " + downByte + "Kb / " + totalByte + "Kb";
    tvContent.setText(text);
}

/**
 * 下载出错处理
 */
private void onDownloadError() {
    tvContent.setText("下载出错");
}

/**
 * 下载完成
 */
private void onDownloadFinish() {
    tvContent.setText("下载已完成");
}

总结

Retrofit框架在APP中具有出现的性能,鉴于API的调用相对复杂(容易冗余),特封装了一套框架来支持日常开发使用。开发者可根据自身需要定义回调接口包含的方法,裁切部分功能。

特别感谢:

https://blog.csdn.net/c__chao/article/details/78573737 提供的API接口
https://blog.csdn.net/webber888/article/details/80993471 提供的https协议适配方法

源码

https://github.com/ahuyangdong/RetrofitFrame

CSDN下载:
https://download.csdn.net/download/ahuyangdong/10722905

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
对于Android网络框架封装,有许多不同的选择。以下是一些常见的网络框架封装方法: 1. Volley:Volley是Google推出的用于Android应用程序的网络通信库。它使用简单且易于集成,具有高效的网络请求队列和缓存机制。你可以封装Volley以满足你的特定需求,例如添加身份验证、错误处理或数据解析等功能。 2. OkHttp:OkHttp是一个开源的HTTP客户端库,可用于Android应用程序中的网络通信。它提供了简单易用的API,支持同步和异步请求,并具有连接池和缓存等功能。你可以使用OkHttp封装网络请求,自定义拦截器和事件监听器,以及处理各类错误和异常情况。 3. Retrofit:Retrofit是Square公司开发的一个类型安全的RESTful网络请求库。它基于OkHttp,并提供了简洁的注解接口来定义API请求。你可以封装Retrofit以定义你的API接口和请求参数,并处理响应结果、错误情况和数据解析等。 4. 自定义封装:除了使用现有的网络框架,你还可以根据你的需求自定义封装一个网络库。这种方式可以灵活地满足特定的业务需求。你可以使用Java的URLConnection或Apache HttpClient等原生库进行底层网络通信,并自定义请求、解析和错误处理等逻辑。 在封装网络框架时,你可以考虑以下几点: - 封装请求方法(GET、POST等)和参数(URL、请求头、请求体等)的接口。 - 统一处理错误和异常情况,例如网络连接失败、超时、服务器错误等。 - 可选地处理数据解析和转换,例如使用Gson库将JSON数据转换为Java对象。 - 支持并发请求和线程管理,以避免阻塞主线程。 - 可选地添加缓存机制,以减少重复请求和提高性能。 - 可选地添加身份验证和安全机制,以保护数据的传输和存储安全。 以上是一些常见的Android网络框架封装方法,你可以根据具体需求选择适合你项目的框架或自定义封装
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值