美文网首页Android开发Android-Rxjava&retrofit&dagger
Retrofit 最简洁易用的封装,摆脱RxJava

Retrofit 最简洁易用的封装,摆脱RxJava

作者: xcheng_ | 来源:发表于2018-11-16 10:49 被阅读153次

前言

Retrofit相信很多android开发者都在使用!很多时候我们根据需要为其在封装一层实现。能够更好更简洁的实现我们的业务代码,我们先列一下retrofit使用过程中的一些痛点


1、取消请求不方便,必须持有发起请求时的Call对象
2、不能动态修改baseUrl
3、不能监听下载进度
4、回调函数 public void onResponse(Call<T> call, final Response<T> response) 我们还需要再次解析Response<T> ,更具是否成功在做业务处理,但是这时候很多代码都是重复判断代码,显得冗余。
5、没有独立的回调接口监听 请求发起 、请求结束、请求取消

我们关心的是请求的结果,比如登录 我们只想要拿到登录信息LoginInfo,或者加载列表的时候我们只需要拿到列表对象List<Item>,请求失败的时候我们很多时候只需要拿到错误信息,弹出给用户同事即可

针对以上问题 ,故做了二次封装。github EasyOkHttp 基于Retrofit的二次封装。解决以上问题

最新的依赖为 implementation 'com.xcheng:easyokhttp:2.5.0'


如何使用?

1、初始化 全局的Retrofit

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://wanandroid.com/")
                .callFactory(new OkHttpClient.Builder()
                        .addNetworkInterceptor(httpLoggingInterceptor)
                        .build())
                .addCallAdapterFactory(ExecutorCallAdapterFactory.INSTANCE)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        RetrofitManager.init(retrofit);

2、构建解析器GsonResponseBodyConverter

​ 一般正常的http返回的合适应该是这样的格式

package com.simple.entity;

/**
 * 普通的结果提示 ,code=0代表成功
 * Created by chengxin on 2017/9/26.
 */
public class BaseResult<T> {
    private int code;
    private String msg;
    private T data;

    public T getData() {
        return data;
    }

    public int getCode() {
        return code;
    }


    public String getMsg() {
        return msg;
    }

    public boolean isSuccess() {
        return code == 0;
    }
}
请求成功时: 我们仅需要的是data对象。
请求失败时 :我们需要的是msg,如果出错时有其他的业务需要处理可以携带对应的信息?如根据code码 处理未登录、 没权限,或者还需要原始的json信息做进一步处理等!基于此需求,我们构建了如下实体类,只要返回的结果不是我们预期的我们就将结果包装成此异常抛出
package com.xcheng.retrofit;

import android.support.annotation.Nullable;

/**
 * 通用的错误信息,一般请求是失败只需要弹出一些错误信息即可,like{@link retrofit2.HttpException}
 * Created by chengxin on 2017/6/22.
 */
public final class HttpError extends RuntimeException {
    private static final long serialVersionUID = -134024482758434333L;
    /**
     * 展示在前端的错误描述信息
     */
    public String msg;

    /**
     * <p>
     * 请求失败保存失败信息,for example:
     * <li>BusiModel: {code:xxx,msg:xxx} 业务错误信息</li>
     * <li>original json:  原始的json</li>
     * <li>{@link retrofit2.Response}:错误响应体->Response<?></li>
     * <li>Throwable: 抛出的异常信息</li>
     * </p>
     */
    @Nullable
    public final transient Object body;

    public HttpError(String msg) {
        this(msg, null);
    }

    public HttpError(String msg, @Nullable Object body) {
        super(msg);
        if (body instanceof Throwable) {
            initCause((Throwable) body);
        }
        //FastPrintWriter#print(String str)
        this.msg = msg != null ? msg : "null";
        this.body = body;
    }

    /**
     * 保证和msg一致
     */
    @Override
    public String getMessage() {
        return msg;
    }

    @Override
    public String toString() {
        return "HttpError {msg="
                + msg
                + ", body="
                + body
                + '}';
    }
}

json解析器的代码如下

package com.simple.converter;

import android.support.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.internal.$Gson$Types;
import com.simple.okhttp.Tip;
import com.xcheng.retrofit.HttpError;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.lang.reflect.Type;

import okhttp3.ResponseBody;
import retrofit2.Converter;

/**
 * 创建时间:2018/4/3
 * 编写人: chengxin
 * 功能描述:json解析相关
 */
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final Type type;

    GsonResponseBodyConverter(Gson gson, Type type) {
        this.type = type;
        this.gson = gson;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T convert(@NonNull ResponseBody value) throws IOException {
        String cacheStr = value.string();
        try {
            JSONObject jsonObject = new JSONObject(cacheStr);
            final int code = jsonObject.getInt("errorCode");
            final String msg = jsonObject.getString("errorMsg");
            Tip tip = new Tip(code, msg);
            if (code != 0) {
                throw new HttpError(msg, tip);
            }
            Class<?> rawType = $Gson$Types.getRawType(type);
            if (Tip.class == rawType) {
                return (T) tip;
            }
            Object data = jsonObject.get("data");
            if (data == JSONObject.NULL) {
                //in case
                throw new HttpError("暂无数据", tip);
            }
            //如果是String 直接返回
            if (String.class == rawType) {
                return (T) data.toString();
            }
            //data 为Boolean 如{"msg": "手机号格式错误","code": 0,"data": false}
            if (Boolean.class == rawType && data instanceof Boolean) {
                return (T) data;
            }
            //data 为Integer  如{"msg": "手机号格式错误","code": 0,"data": 12}
            if (Integer.class == rawType && data instanceof Integer) {
                return (T) data;
            }
            T t = gson.fromJson(data.toString(), type);
            if (t != null) {
                //防止线上接口修改导致反序列化失败奔溃
                return t;
            }
            throw new HttpError("数据异常", tip);
        } catch (JSONException e) {
            throw new HttpError("解析异常", cacheStr);
        }
    }
}

3、回调函数

回调函数中我们,我们期望能监听到

请求开始onStart(Call2<T> call2) 开启loading 动画

请求成功onSuccess(Call2<T> call2, T response) 处理成功

请求失败onError(Call2<T> call2, HttpError error); 处理失败

请求结束onCompleted(Call2<T> call2) 关闭 loading动画

请求取消onCancel(Call2<T> call2) 处理取消等

基类代码如下:

package com.xcheng.retrofit;

import android.support.annotation.NonNull;
import android.support.annotation.UiThread;

import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import retrofit2.Call;
import retrofit2.Response;

/**
 * if {@link Call#cancel()}called {@link #onStart(Call2)}、 {@link #onSuccess(Call2, Object)}、
 * {@link #onError(Call2, HttpError)}、 {@link #onCompleted(Call2)} will not be called
 *
 * @param <T> Successful response body type.
 */
@UiThread
public abstract class Callback2<T> {

    @NonNull
    public Result<T> parseResponse(Call2<T> call2, Response<T> response) {
        T body = response.body();
        if (response.isSuccessful()) {
            if (body != null) {
                return Result.success(body);
            } else {
                return Result.error(new HttpError("暂无数据", response));
            }
        }

        final String msg;
        switch (response.code()) {
            case 400:
                msg = "参数错误";
                break;
            case 401:
                msg = "身份未授权";
                break;
            case 403:
                msg = "禁止访问";
                break;
            case 404:
                msg = "地址未找到";
                break;
            default:
                msg = "服务异常";
        }
        return Result.error(new HttpError(msg, response));
    }

    /**
     * 统一解析Throwable对象转换为HttpError对象。如果为HttpError,
     * 则为{@link retrofit2.Converter#convert(Object)}内抛出的异常
     *
     * @param call2 call
     * @param t     Throwable
     * @return HttpError result
     */
    @NonNull
    public HttpError parseThrowable(Call2<T> call2, Throwable t) {
        if (t instanceof HttpError) {
            //用于convert函数直接抛出异常接收
            return (HttpError) t;
        } else if (t instanceof UnknownHostException) {
            return new HttpError("网络异常", t);
        } else if (t instanceof ConnectException) {
            return new HttpError("网络异常", t);
        } else if (t instanceof SocketException) {
            return new HttpError("服务异常", t);
        } else if (t instanceof SocketTimeoutException) {
            return new HttpError("响应超时", t);
        } else {
            return new HttpError("请求失败", t);
        }
    }

    public void onStart(Call2<T> call2) {
    }

    public void onCancel(Call2<T> call2) {
    }

    public abstract void onError(Call2<T> call2, HttpError error);

    public abstract void onSuccess(Call2<T> call2, T response);

    /**
     * 请求回调全部完成时执行
     *
     * @param call2 Call
     */
    public void onCompleted(Call2<T> call2) {
    }
}

重写callback2:

package com.simple.okhttp;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.google.gson.JsonSyntaxException;
import com.xcheng.retrofit.Call2;
import com.xcheng.retrofit.Callback2;
import com.xcheng.retrofit.HttpError;
import com.xcheng.view.controller.ILoadingView;

/**
 * Created by chengxin on 2017/9/24.
 */
public abstract class AnimCallback<T> extends Callback2<T> {
    private ILoadingView mLoadingView;

    public AnimCallback(@Nullable ILoadingView loadingView) {
        this.mLoadingView = loadingView;
    }

    @Override
    public void onStart(Call2<T> call2) {
        super.onStart(call2);
        if (mLoadingView != null)
            mLoadingView.showLoading();
    }

    @Override
    public void onCompleted(Call2<T> call2) {
        super.onCompleted(call2);
        if (mLoadingView != null)
            mLoadingView.hideLoading();
    }

    @NonNull
    @Override
    public HttpError parseThrowable(Call2<T> call2, Throwable t) {
        HttpError filterError;
        if (t instanceof JsonSyntaxException) {
            filterError = new HttpError("解析异常", t);
        } else {
            filterError = super.parseThrowable(call2, t);
        }
        return filterError;
    }
}

5、发起请求

/**
 * 创建时间:2018/4/8
 * 编写人: chengxin
 * 功能描述:测试接口
 */
public interface ApiService {
    //登录
    @FormUrlEncoded
    @POST("user/login")
    Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);

    //获取微信公众号列表
    @GET("wxarticle/chapters/json")
    Call2<List<WXArticle>> getWXarticle();

    //获取首页文章列表
    @GET("article/list/0/json")
    Call2<List<Article>> getArticle0();

    //下载文件
    @GET("http://shouji.360tpcdn.com/181115/4dc46bd86bef036da927bc59680f514f/com.ss.android.ugc.aweme_330.apk")
    Call2<File> loadDouYinApk();
}
获取列表
RetrofitManager.create(ApiService.class)
        .getWXarticle().enqueue(hashCode(), new AnimCallback<List<WXArticle>>(this) {
    @Override
    public void onError(Call2<List<WXArticle>> call2, HttpError error) {
        Toast.makeText(MainActivity.this, error.msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onSuccess(Call2<List<WXArticle>> call2, List<WXArticle> response) {
        Toast.makeText(MainActivity.this, "获取公众号列表成功", Toast.LENGTH_SHORT).show();

    }
});
下载文件
String filePath = new File(getContext().getExternalCacheDir(), "test_douyin.apk").getPath();
//构建可以监听进度的client
OkHttpClient client = new OkHttpClient().newBuilder()
        .addNetworkInterceptor(getProgressInterceptor()).build();

//构建可以下载文件的client
Retrofit retrofit = RetrofitManager.retrofit()
        .newBuilder()
        .callFactory(client)
        .addConverterFactory(new FileConverterFactory(filePath))
        .build();

retrofit.create(ApiService.class)
        .loadDouYinApk().enqueue("loadApk", new Callback2<File>() {
    @Override
    public void onStart(Call2<File> call2) {
        super.onStart(call2);
        button.setText("取消下载");
    }

    @Override
    public void onError(Call2<File> call2, HttpError error) {
        progressView.setProgress(0);
        Toast.makeText(MainActivity.this, error.msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onSuccess(Call2<File> call2, File response) {

    }

    @Override
    public void onCancel(Call2<File> call2) {
        super.onCancel(call2);
        progressView.setProgress(0);
        button.setText("下载抖音apk文件");
    }

    @Override
    public void onCompleted(Call2<File> call2) {
        super.onCompleted(call2);
        button.setText("下载完成");
    }
});

6、取消请求

tag为发起请求时传入的tag,这样就不用一直引用Call2对象用于取消请求了。

CallManager.getInstance().cancel(tag);
image.png

是不是很简单,能应对所有的需求,简洁易用

github地址:https://github.com/xchengDroid/EasyOkHttp

作者:xcheng_;转载请添加原文地址 https://www.jianshu.com/p/aeea4fe91102

相关文章

网友评论

  • 89e824652f8d:楼主辛苦啦,分享的东西很实用,正好碰到的问题解决了!!!!

本文标题:Retrofit 最简洁易用的封装,摆脱RxJava

本文链接:https://www.haomeiwen.com/subject/zmwpfqtx.html