Giter VIP home page Giter VIP logo

xuexiangjys / xhttp2 Goto Github PK

View Code? Open in Web Editor NEW
398.0 16.0 83.0 13.94 MB

💪A powerful network request library, encapsulated using the RxJava2 + Retrofit2 + OKHttp combination.(一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装)

Home Page: https://github.com/xuexiangjys/XHttp2/wiki

License: Apache License 2.0

Java 100.00%
xhttp gradle rxjava cookie rxjava2-retrofit2-okhttp x-library

xhttp2's Introduction

XHttp2

api I Star

一个功能强悍的网络请求库,使用RxJava2 + Retrofit2 + OKHttp组合进行封装。还不赶紧点击使用说明文档,体验一下吧!

在提issue前,请先阅读【提问的智慧】,并严格按照issue模板进行填写,节约大家的时间。

在使用前,请一定要仔细阅读使用说明文档,重要的事情说三遍!!!

在使用前,请一定要仔细阅读使用说明文档,重要的事情说三遍!!!

在使用前,请一定要仔细阅读使用说明文档,重要的事情说三遍!!!

关于我

公众号 掘金 知乎 CSDN 简书 思否 哔哩哔哩 今日头条
我的Android开源之旅 点我 点我 点我 点我 点我 点我 点我

X系列库快速集成

为了方便大家快速集成X系列框架库,我提供了一个空壳模版供大家参考使用: https://github.com/xuexiangjys/TemplateAppProject


特征

  • 支持默认、全局、局部三个层次的配置功能。
  • 支持动态配置和自定义底层框架OkHttpClient、Retrofit.
  • 加入基础ApiService,减少Api冗余。
  • 支持多种方式访问网络GET、POST、PUT、DELETE等请求协议。
  • 支持网络缓存,六种缓存策略可选,涵盖大多数业务场景。
  • 支持固定添加header和动态添加header。
  • 支持添加全局参数和动态添加局部参数。
  • 支持文件下载、多文件上传和表单提交数据。
  • 支持文件请求、上传、下载的进度回调、错误回调,也可以自定义回调。
  • 支持任意数据结构的自动解析。
  • 支持添加动态参数例如timeStamp时间戳、token、签名sign。
  • 支持自定义的扩展API。
  • 支持多个请求合并。
  • 支持Cookie管理。
  • 支持异步、同步请求。
  • 支持Https、自签名网站Https的访问、双向验证。
  • 支持失败重试机制,可以指定重试次数、重试间隔时间。
  • 支持根据key删除网络缓存和清空网络缓存。
  • 提供默认的标准ApiResult(遵循OpenApi格式)解析和回调,并且可自定义ApiResult。
  • 支持取消数据请求,取消订阅,带有对话框的请求不需要手动取消请求,对话框消失会自动取消请求。
  • 支持请求数据结果采用回调和订阅两种方式。
  • 提供"默认API"、"接口协议"以及"统一请求实体"三种方式进行网络请求,支持自定义网络请求协议。
  • 返回结果和异常统一处理,支持自定义异常处理。
  • 结合RxJava,线程切换灵活。
  • 请求实体支持注解配置,配置网络请求接口的url、是否需要验证token以及请求参数的key。
  • 拥有统一的网络请求取消机制。

点击查看项目设计类图

Star趋势图

Stargazers over time


1、演示(请star支持)

1.1、Demo演示动画

1.2、Demo下载

downloads

1.3、api服务安装

服务端的搭建详细请点击查看

2、如何使用

目前支持主流开发工具AndroidStudio的使用,直接配置build.gradle,增加依赖即可.

2.1、Android Studio导入方法,添加Gradle依赖

1.先在项目根目录的 build.gradle 的 repositories 添加:

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

2.然后在应用项目(一般是app)的 build.gradle 的 dependencies 添加:

以下是版本说明,选择一个即可。

  • androidx版本:2.0.0及以上
dependencies {
  ...
  implementation 'com.github.xuexiangjys:XHttp2:2.0.4'

  implementation 'com.google.code.gson:gson:2.8.5'
  implementation 'com.squareup.okhttp3:okhttp:3.10.0'
  implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
  implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}
  • support版本:1.0.4及以下
dependencies {
  ...
  implementation 'com.github.xuexiangjys:XHttp2:1.0.4'

  implementation 'com.google.code.gson:gson:2.8.2'
  implementation 'com.squareup.okhttp3:okhttp:3.10.0'
  implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
  implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
}

3.在Application中初始化XHttpSDK

XHttpSDK.init(this);   //初始化网络请求框架,必须首先执行
XHttpSDK.debug("XHttp");  //需要调试的时候执行
XHttpSDK.setBaseUrl(SettingSPUtils.getInstance().getApiURL());  //设置网络请求的基础地址

4.全局初始化配置(非必要)

除了上述的操作以外,你还可以使用XHttp.getInstance()对网络请求框架进行全局性参数配置,配置一些公用默认的参数,这样我们就不需要为每个请求都进行设置。方法如下:

方法名 备注
debug 设置日志的打印模式
setBaseUrl 设置全局baseUrl
setSubUrl 设置全局subUrl
setReadTimeOut 设置全局读取超时时间
setWriteTimeOut 设置全局写入超时时间
setConnectTimeout 设置全局连接超时时间
setTimeout 设置全局超时时间
setRetryCount 设置全局超时重试次数
setRetryDelay 设置全局超时重试延迟时间
setRetryIncreaseDelay 设置全局超时重试延迟叠加时间
setCacheMode 设置全局的缓存模式
setIsDiskCache 设置是否是磁盘缓存
setMemoryMaxSize 设置内存缓存的最大数量
setCacheTime 设置全局的缓存过期时间
setCacheMaxSize 设置全局的磁盘缓存大小,默认50M
setCacheDirectory 设置全局缓存的路径,默认是应用包下面的缓存
setCacheDiskConverter 设置全局缓存的转换器
addCommonParams 添加全局公共请求参数
addCommonHeaders 添加全局公共请求参数
addInterceptor 添加全局拦截器
addNetworkInterceptor 添加全局网络拦截器
setOkproxy 全局设置OkHttpClient的代理
setOkconnectionPool 设置全局OkHttpClient的请求连接池
setOkclient 全局为Retrofit设置自定义的OkHttpClient
addConverterFactory 设置全局Converter.Factory,默认GsonConverterFactory.create()
addCallAdapterFactory 设置全局CallAdapter.Factory,默认RxJavaCallAdapterFactory.create()
setHostnameVerifier 设置https的全局访问规则
setCertificates 设置https的全局自签名证书
setCookieStore 设置全局cookie存取规则
setStrictMode 设置严格模式,在严格模式下,json返回的data数据不能为null

如何进行网络请求

需要注意的是,所以请求返回的结果必须要满足以下格式:

{
    "Code":0, //响应码,0为成功,否则失败
    "Msg":"", //请求失败的原因说明
    "Data":{} //返回的数据对象
}

其中CodeMsgData建议使用大写字母,当然使用小写字母也没有问题,否则无法解析成功。

【注意】这里Code为0才代表请求成功,如果你的Code码不是0代表成功,你可以使用XHttpSDK.setSuccessCode设置代表成功的值。

需要自定义返回的实体API请点击查看

1、使用XHttp默认api进行请求

1.使用XHttp.post、XHttp.get、XHttp.delete、XHttp.put、XHttp.downLoad构建请求。

2.修改request的请求参数。

方法名 类型 默认值 备注
baseUrl String 设置该请求的baseUrl
timeOut long 15000 设置超时时间
accessToken boolean false 是否需要验证token
threadType String 设置请求的线程调度类型
syncRequest boolean false 设置是否是同步请求(不开子线程)
onMainThread boolean true 请求完成后是否回到主线程
upJson String "" 上传Json格式的数据请求
keepJson boolean false 返回保持json的形式
retryCount int 设置超时重试的次数
retryDelay int 设置超时重试的延迟时间
retryIncreaseDelay int 设置超时重试叠加延时
headers HttpHeaders 添加头信息
params HttpParams 设置表单请求参数
cacheMode CacheMode CacheMode.NO_CACHE 设置缓存的模式

3.调用execute方法执行请求。execute一般有如下两种方式:

  • execute(CallBack callBack): 直接回调结果。

  • execute(Class clazz)和execute(Type type): 回调Observable对象,可通过订阅获取到结果。

4.请求使用演示

XHttp.get("/user/getAllUser")
        .syncRequest(false) //异步请求
        .onMainThread(true) //回到主线程
        .execute(new SimpleCallBack<List<User>>() {
            @Override
            public void onSuccess(List<User> response) {
                refreshLayout.finishRefresh(true);
                if (response != null && response.size() > 0) {
                    mUserAdapter.refresh(response);
                    mLlStateful.showContent();
                } else {
                    mLlStateful.showEmpty();
                }
            }
            @Override
            public void onError(ApiException e) {
                refreshLayout.finishRefresh(false);
                mLlStateful.showError(e.getMessage(), null);
            }

        });
XHttp.post("/user/deleteUser")
        .params("userId", item.getUserId())
        .execute(Boolean.class)
        .subscribeWith(new TipRequestSubscriber<Boolean>() {
            @Override
            protected void onSuccess(Boolean aBoolean) {
                ToastUtils.toast("删除成功!");
                setFragmentResult(RESULT_OK, null);
                popToBack();
            }
        });


2、使用XHttpRequest封装的统一请求实体进行请求【仅支持post请求】

在使用它之前,需要下载/定义对应的实体协议,如下:

@RequestParams(url = "/user/addUser", accessToken = false)
public static class UserService_AddUser extends XHttpRequest {

    /**
     *
     */
    public User request;

    @Override
    protected Boolean getResponseEntityType() {
        return null;
    }
}

1.注解说明

  • @RequestParams
注解参数 类型 默认值 备注
baseUrl String "" 设置该请求的baseUrl
url String "" 请求网络接口地址
timeout long 15000 设置超时时间
keepJson boolean false 是否保存json
accessToken boolean true 设置是否需要验证token
cacheMode CacheMode CacheMode.NO_CACHE 设置请求的缓存模式
cacheTime long -2(使用全局设置) 设置缓存有效时间
  • @ParamKey
注解参数 类型 默认值 备注
key String / 请求参数的key

2.使用XHttpSDK进行请求。

  • post(XHttpRequest xHttpRequest, boolean isSyncRequest, boolean toMainThread): 获取PostRequest请求(使用实体参数名作为请求Key)。

  • postToMain(XHttpRequest xHttpRequest): 获取PostRequest请求(主线程->主线程)。

  • postToIO(XHttpRequest xHttpRequest): 获取PostRequest请求(主线程->子线程)。

  • postInThread(XHttpRequest xHttpRequest): 获取PostRequest请求(子线程->子线程)。

  • execute(XHttpRequest xHttpRequest, boolean isSyncRequest, boolean toMainThread) : 执行PostRequest请求,返回observable对象(使用实体参数名作为请求Key)。

  • executeToMain(XHttpRequest xHttpRequest): 执行post请求,返回observable对象(主线程->主线程)

  • executeToMain(XHttpRequest xHttpRequest,BaseSubscriber<T> subscriber): 执行post请求并进行订阅,返回订阅信息(主线程->主线程)

3.请求使用演示。

XHttpRequest req = ApiProvider.getAddUserReq(getRandomUser());
XHttpSDK.executeToMain(req, new ProgressLoadingSubscriber<Boolean>(mIProgressLoader) {
    @Override
    public void onSuccess(Boolean aBoolean) {
        ToastUtils.toast("用户添加成功!");
        mRefreshLayout.autoRefresh();
    }
});

3、使用XHttpProxy代理进行请求

在使用它之前,需要下载/定义对应的接口协议,如下:

/**
 * 图书管理
 */
public interface IBook {
    /**
     * 购买书
     *
     * @param bookId 图书ID
     * @param userId 用户ID
     * @param number 购买数量
     */
    @NetMethod(parameterNames = {"bookId", "userId", "number"}, url = "/order/addOrder/", accessToken = false)
    Observable<Boolean> buyBook(int bookId, int userId, int number);
    /**
     * 获取图书
     *
     * @param pageNum 第几页数
     * @param pageSize 每页的数量
     */
    @NetMethod(parameterNames = {"pageNum", "pageSize"}, paramType = FORM_BODY, url = "/book/findBooks/", accessToken = false)
    Observable<List<Book>> getBooks(int pageNum, int pageSize);

    /**
     * 获取所有图书
     *
     */
    @NetMethod(action = GET, url = "/book/getAllBook", accessToken = false)
    Observable<List<Book>> getAllBooks();
}

1.注解说明

  • @NetMethod
注解参数 类型 默认值 备注
parameterNames String[] {} 参数名集合
paramType int JSON=1 param的类型
action String POST="post" 请求动作
baseUrl String "" 设置该请求的baseUrl
url String "" 请求网络接口地址
timeout long 15000 设置超时时间
keepJson boolean false 是否保存json
accessToken boolean true 设置是否需要验证token
cacheMode CacheMode CacheMode.NO_CACHE 设置请求的缓存模式
cacheTime long -2(使用全局设置) 设置缓存有效时间
cacheKeyIndex int -1(所有参数) 作为缓存key的请求参数索引

2.使用XHttpProxy进行请求。

构建一个XHttpProxy,将定义的api接口传入后,直接调用接口进行请求。

构造XHttpProxy可以传入ThreadType,默认是ThreadType.TO_MAIN

  • TO_MAIN: executeToMain(main -> io -> main)

【注意】请确保网络请求在主线程中【实质是异步请求(切换到io线程),且响应的线程又切换至主线程】

  • TO_IO: executeToIO(main -> io -> io)

【注意】请确保网络请求在主线程中【实质是异步请求(切换到io线程),不过响应的线程不变,还是之前请求的那个io线程】

  • IN_THREAD: executeInThread(io -> io -> io)

【注意】请确保网络请求在子线程中才可以使用该类型【实质是不做任何线程调度的同步请求】

3.请求使用演示。

//使用XHttpProxy进行接口代理请求
XHttpProxy.proxy(TestApi.IOrder.class)
        .buyBook(mBookAdapter.getItem(position).getBookId(), UserManager.getInstance().getUser().getUserId(), 1)
        .subscribeWith(new TipRequestSubscriber<Boolean>() {
            @Override
            public void onSuccess(Boolean aBoolean) {
                ToastUtils.toast("图书购买" + (aBoolean ? "成功" : "失败") + "!");
                mRefreshLayout.autoRefresh();
            }
        });

4、文件上传和下载

1.文件上传【multipart/form-data】

使用post的文件表单上传。使用XHttp.post,然后使用params传递附带的参数,使用uploadFile传递需要上传的文件,使用示例如下:

mIProgressLoader.updateMessage("上传中...");
XHttp.post("/book/uploadBookPicture")
        .params("bookId", book.getBookId())
        .uploadFile("file", FileUtils.getFileByPath(mPicturePath), new IProgressResponseCallBack() {
            @Override
            public void onResponseProgress(long bytesWritten, long contentLength, boolean done) {

            }
        }).execute(Boolean.class)
        .compose(RxLifecycle.with(this).<Boolean>bindToLifecycle())
        .subscribeWith(new ProgressLoadingSubscriber<Boolean>(mIProgressLoader) {
            @Override
            public void onSuccess(Boolean aBoolean) {
                mIsEditSuccess = true;
                ToastUtils.toast("图片上传" + (aBoolean ? "成功" : "失败") + "!");
            }
        });

2.文件下载

使用XHttp.downLoad,传入下载的地址url、保存文件的路径以及文件名即可完成文件的下载,使用示例如下:

XHttp.downLoad(BookAdapter.getBookImgUrl(book))
        .savePath(PathUtils.getExtPicturesPath())
        .execute(new DownloadProgressCallBack<String>() {
            @Override
            public void onStart() {
                HProgressDialogUtils.showHorizontalProgressDialog(getContext(), "图片下载中...", true);
            }

            @Override
            public void onError(ApiException e) {
                ToastUtils.toast(e.getMessage());
                HProgressDialogUtils.cancel();
            }

            @Override
            public void update(long bytesRead, long contentLength, boolean done) {
                HProgressDialogUtils.onLoading(contentLength, bytesRead); //更新进度条
            }

            @Override
            public void onComplete(String path) {
                ToastUtils.toast("图片下载成功, 保存路径:" + path);
                HProgressDialogUtils.cancel();
            }
        });

高阶网络请求操作

请求生命周期绑定

1.请求loading加载和请求生命周期绑定

在请求时,订阅ProgressLoadingSubscriber或者ProgressLoadingCallBack,传入请求消息加载者IProgressLoader,即可完成生命周期的绑定。示例如下:

XHttpRequest req = ApiProvider.getAddUserReq(getRandomUser());
    XHttpSDK.executeToMain(req, new ProgressLoadingSubscriber<Boolean>(mIProgressLoader) {
        @Override
        public void onSuccess(Boolean aBoolean) {
            ToastUtils.toast("用户添加成功!");
            mRefreshLayout.autoRefresh();
        }
    });

2.网络请求生命周期和Activity/Fragment生命周期绑定

(1)这里需要依赖一下RxUtil2

implementation 'com.github.xuexiangjys:rxutil2:1.1.2'

(2)在所在的Activity的onCreate()下锁定Activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RxLifecycle.injectRxLifecycle(this);
}

(3)然后在请求中使用RxJava的compose的操作符进行绑定。

.compose(RxLifecycle.with(this).<Boolean>bindToLifecycle())

拦截器

日志拦截器

(1)框架默认提供一个实现好的日志拦截器HttpLoggingInterceptor,通过XHttpSDK.debug("XHttp");就可以设置进去,它有5种打印模式

  • NONE: 不打印log

  • BASIC: 只打印"请求首行"和"响应首行"。

  • HEADERS: 打印请求和响应的所有 Header

  • PARAM: 只打印请求和响应参数

  • BODY: 打印所有数据(默认是这种)

(2)如果需要对网络请求的相关参数进行自定义记录的话,可以继承HttpLoggingInterceptor实现一个自己的网络请求日志拦截器,重写logForRequestlogForResponse两个方法即可。

(3)设置自定义的日志拦截器.

XHttpSDK.debug(new CustomLoggingInterceptor());

动态参数添加拦截器

有时候,我们需要对所有请求添加一些固定的请求参数,但是这些参数的值又是变化的,这个时候我们就需要动态添加请求参数【例如,请求的token、时间戳以及签名等】

(1)继承BaseDynamicInterceptor,实现updateDynamicParams方法,如下:

@Override
protected TreeMap<String, Object> updateDynamicParams(TreeMap<String, Object> dynamicMap) {
    if (isAccessToken()) {//是否添加token
        dynamicMap.put("token", TokenManager.getInstance().getToken());
    }
    if (isSign()) {//是否添加签名
        dynamicMap.put("sign", TokenManager.getInstance().getSign());
    }
    if (isTimeStamp()) {//是否添加请求时间戳
        dynamicMap.put("timeStamp", DateUtils.getNowMills());
    }
    return dynamicMap;//dynamicMap:是原有的全局参数+局部参数+新增的动态参数
}

(2)设置动态参数添加拦截器。

XHttpSDK.addInterceptor(new CustomDynamicInterceptor()); //设置动态参数添加拦截器

失效请求校验拦截器

当服务端返回一些独特的错误码(一般是token校验错误、失效,请求过于频繁等),需要我们进行全局性的拦截捕获,并作出相应的响应时,我们就需要定义一个特殊的拦截器求处理这些请求。

(1)继承BaseExpiredInterceptor,实现isResponseExpiredresponseExpired方法,如下:

/**
 * 判断是否是失效的响应
 *
 * @param oldResponse
 * @param bodyString
 * @return {@code true} : 失效 <br>  {@code false} : 有效
 */
@Override
protected ExpiredInfo isResponseExpired(Response oldResponse, String bodyString) {
    int code = JSONUtils.getInt(bodyString, ApiResult.CODE, 0);
    ExpiredInfo expiredInfo = new ExpiredInfo(code);
    switch (code) {
        case TOKEN_INVALID:
        case TOKEN_MISSING:
            expiredInfo.setExpiredType(KEY_TOKEN_EXPIRED)
                    .setBodyString(bodyString);
            break;
        case AUTH_ERROR:
            expiredInfo.setExpiredType(KEY_UNREGISTERED_USER)
                    .setBodyString(bodyString);
            break;
        default:
            break;
    }
    return expiredInfo;
}

/**
 * 失效响应的处理
 *
 * @return 获取新的有效请求响应
 */
@Override
protected Response responseExpired(Response oldResponse, Chain chain, ExpiredInfo expiredInfo) {
    switch(expiredInfo.getExpiredType()) {
        case KEY_TOKEN_EXPIRED:
            User user = TokenManager.getInstance().getLoginUser();
            if (user != null) {
                final boolean[] isGetNewToken = {false};
                HttpLog.e("正在重新获取token...");
                XHttpProxy.proxy(TestApi.IAuthorization.class, ThreadType.IN_THREAD)
                        .login(user.getLoginName(), user.getPassword())
                        .subscribeWith(new NoTipRequestSubscriber<LoginInfo>() {
                            @Override
                            protected void onSuccess(LoginInfo loginInfo) {
                                TokenManager.getInstance()
                                        .setToken(loginInfo.getToken())
                                        .setLoginUser(loginInfo.getUser());
                                isGetNewToken[0] = true;
                                HttpLog.e("重新获取token成功:" + loginInfo.getToken());
                            }
                        });
                if (isGetNewToken[0]) {
                    try {
                        HttpLog.e("使用新的token重新进行请求...");
                        return chain.proceed(HttpUtils.updateUrlParams(chain.request(), "token", TokenManager.getInstance().getToken()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                XRouter.getInstance().build("/xhttp/login").navigation();
                return HttpUtils.getErrorResponse(oldResponse, expiredInfo.getCode(), "请先进行登录!");
            }
            break;
        case KEY_UNREGISTERED_USER:
            return HttpUtils.getErrorResponse(oldResponse, expiredInfo.getCode(), "非法用户登录!");
        default:
            break;
    }
    return null;
}

(2)设置失效请求校验拦截器。

XHttpSDK.addInterceptor(new CustomExpiredInterceptor()); //请求失效校验拦截器

自定义API请求

自定义请求响应的API结构

如果你不想使用默认的ApiResult实体作为统一的服务端响应实体,比如说你想要下面的响应实体:

private int errorCode; //请求的错误码
private String errorInfo; //请求错误的原因描述
private T result; //请求的结果
private long timeStamp; //服务端返回的时间戳

(1)首先,继承ApiResult实体,重写其getCodegetMsgisSuccessgetDatasetData方法。

public class CustomApiResult<T> extends ApiResult<T> {

    private int errorCode;
    private String errorInfo;
    private T result;
    private long timeStamp;

    public int getErrorCode() {
        return errorCode;
    }

    public CustomApiResult<T> setErrorCode(int errorCode) {
        this.errorCode = errorCode;
        return this;
    }

    public String getErrorInfo() {
        return errorInfo;
    }

    public CustomApiResult<T> setErrorInfo(String errorInfo) {
        this.errorInfo = errorInfo;
        return this;
    }

    public T getResult() {
        return result;
    }

    public CustomApiResult<T> setResult(T result) {
        this.result = result;
        return this;
    }

    public long getTimeStamp() {
        return timeStamp;
    }

    public CustomApiResult<T> setTimeStamp(long timeStamp) {
        this.timeStamp = timeStamp;
        return this;
    }

    @Override
    public int getCode() {
        return errorCode;
    }

    @Override
    public String getMsg() {
        return errorInfo;
    }

    @Override
    public boolean isSuccess() {
        return errorCode == 0;
    }

    @Override
    public void setData(T data) {
        result = data;
    }

    @Override
    public T getData() {
        return result;
    }

    @Override
    public String toString() {
        return "ApiResult{" +
                "errorCode='" + errorCode + '\'' +
                ", errorInfo='" + errorInfo + '\'' +
                ", timeStamp='" + timeStamp + '\'' +
                ", result=" + result +
                '}';
    }
}

(2)进行请求的时候使用execute(CallBackProxy)或者execute(CallClazzProxy方法进行请求

XHttp.get("/test/testCustomResult")
            .execute(new CallBackProxy<CustomApiResult<Boolean>, Boolean>(new TipRequestCallBack<Boolean>() {
                @Override
                public void onSuccess(Boolean response) throws Throwable {
                    ToastUtils.toast("请求成功:" + response);
                }
            }){});  //千万注意,这里的{}一定不能去掉,否则解析错误

【注意】上面提示的{}一定不能去掉,否则解析错误, 会报"ApiResult.class.isAssignableFrom(cls) err!!"的错误。

如果你觉得写一长串比较麻烦,你可以自定义请求继承你需要的请求方式,例如这里是get请求,我们可以这样写:

public class CustomGetRequest extends GetRequest {

    public CustomGetRequest(String url) {
        super(url);
    }

    @Override
    public <T> Observable<T> execute(Type type) {
        return execute(new CallClazzProxy<CustomApiResult<T>, T>(type) {
        });
    }

    @Override
    public <T> Disposable execute(CallBack<T> callBack) {
        return execute(new CallBackProxy<CustomApiResult<T>, T>(callBack) {
        });
    }
}

然后我们就可以用自定义的CustomGetRequest进行请求了,是不是简化了很多呢。

new CustomGetRequest("/test/testCustomResult")
        .execute(new TipRequestCallBack<Boolean>() {
            @Override
            public void onSuccess(Boolean response) throws Throwable {
                ToastUtils.toast("请求成功:" + response);
            }
        });

使用自定义的retrofit接口

如果你对retrofit接口情有独钟,我也提供了相应的api方便调用.

1.定义retrofit接口。例如我定义一个用户添加的接口:

/**
 * 使用的是retrofit的接口定义
 */
public interface UserService {
    @POST("/user/registerUser/")
    @Headers({"Content-Type: application/json", "Accept: application/json"})
    Observable<ApiResult<Boolean>> registerUser(@Body RequestBody jsonBody);


    @POST("/user/registerUser/")
    @Headers({"Content-Type: application/json", "Accept: application/json"})
    Observable<ApiResult> register(@Body User user);
}

2.使用XHttp.custom()构建的CustomRequest进行请求,你可以使用apiCallcall进行请求。

  • apiCall: 针对的是retrofit定义的接口,返回的是Observable<ApiResult>的情况,对ApiResult进行拆包,直接获取数据。对于上面定义的第一个接口registerUser

  • call: 针对的是retrofit定义的接口,返回的是Observable的情况,不对ApiResult进行拆包。对于上面定义的第二个接口register

使用示例如下:

CustomRequest request = XHttp.custom();
request.apiCall(request.create(TestApi.UserService.class)
        .registerUser(HttpUtils.getJsonRequestBody(UserManager.getInstance().getRandomUser())))
        .subscribeWith(new TipRequestSubscriber<Boolean>() {
            @Override
            protected void onSuccess(Boolean aBoolean) {
                ToastUtils.toast("添加用户成功!");
            }
        });
CustomRequest request = XHttp.custom();
request.call(request.create(TestApi.UserService.class)
        .register(HttpUtils.getJsonRequestBody(UserManager.getInstance().getRandomUser())))
        .subscribeWith(new TipRequestSubscriber<ApiResult>() {
            @Override
            protected void onSuccess(ApiResult apiResult) {
                ToastUtils.toast("添加用户成功!");
                showResult(JsonUtil.toJson(apiResult));
            }
        });

缓存策略

目前框架提供了如下8种缓存策略:

  • NO_CACHE: 不使用缓存(默认方式)

  • DEFAULT: 完全按照HTTP协议的默认缓存规则,走OKhttp的Cache缓存

  • FIRST_REMOTE: 先请求网络,请求网络失败后再加载缓存

  • FIRST_CACHE: 先加载缓存,缓存没有再去请求网络

  • ONLY_REMOTE: 仅加载网络,但数据依然会被缓存

  • ONLY_CACHE: 只读取缓存

  • CACHE_REMOTE: 先使用缓存,不管是否存在,仍然请求网络,会回调两次

  • CACHE_REMOTE_DISTINCT: 先使用缓存,不管是否存在,仍然请求网络,会先把缓存回调给你,等网络请求回来发现数据是一样的就不会再返回,否则再返回(这样做的目的是防止数据是一样的你也需要刷新界面)

对于缓存的实现,提供了磁盘缓存LruDiskCache和内存缓存LruMemoryCache两种实现,默认使用的是磁盘缓存。

(1)可以先进行缓存的全局性配置,配置缓存的有效期、缓存大小,缓存路径、序列化器等。

XHttp.getInstance()
        .setIsDiskCache(true) //设置使用磁盘缓存
        .setCacheTime(60 * 1000) //设置全局缓存有效期为一分钟
        .setCacheVersion(1) //设置全局缓存的版本
        .setCacheDirectory(Utils.getDiskCacheDir(this, "XHttp")) //设置全局缓存保存的目录路径
        .setCacheMode(CacheMode.NO_CACHE) //设置全局的缓存策略
        .setCacheDiskConverter(new GsonDiskConverter())//默认缓存使用序列化转化
        .setCacheMaxSize(50 * 1024 * 1024);//设置缓存大小为50M

(2)在进行请求的时候,设置缓存模式和缓存的key即可。如下:

XHttp.get("/book/getAllBook")
        .timeOut(10 * 1000)//测试局部超时10s
        .cacheMode(mCacheMode)
        .cacheKey(CACHE_KEY)//缓存key
        .retryCount(5)//重试次数
        .cacheTime(5 * 60)//缓存时间300s,默认-1永久缓存  okhttp和自定义缓存都起作用
        .cacheDiskConverter(new GsonDiskConverter())//默认使用的是 new SerializableDiskConverter();
        .timeStamp(true)
        .execute(new ProgressLoadingCallBack<CacheResult<List<Book>>>(mIProgressLoader) {
            @Override
            public void onSuccess(CacheResult<List<Book>> cacheResult) {
                ToastUtils.toast("请求成功!");
                String from;
                if (cacheResult.isFromCache) {
                    from = "我来自缓存";
                } else {
                    from = "我来自远程网络";
                }
                showResult(from + "\n" + JsonUtil.toJson(cacheResult.data));
            }

            @Override
            public void onError(ApiException e) {
                super.onError(e);
                ToastUtils.toast(e.getDisplayMessage());
            }
        });

混淆配置

#XHttp2
-keep class com.xuexiang.xhttp2.model.** { *; }
-keep class com.xuexiang.xhttp2.cache.model.** { *; }
-keep class com.xuexiang.xhttp2.cache.stategy.**{*;}
-keep class com.xuexiang.xhttp2.annotation.** { *; }

#okhttp
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
-dontwarn javax.annotation.**

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

# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

#如果用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错
-keepattributes Signature
-keep class com.google.gson.stream.** { *; }
-keepattributes EnclosingMethod
-keep class org.xz_sale.entity.**{*;}
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }

特别感谢

https://github.com/zhou-you/RxEasyHttp

如果觉得项目还不错,可以考虑打赏一波

你的打赏是我维护的动力,我将会列出所有打赏人员的清单在下方作为凭证,打赏前请留下打赏项目的备注!

pay.png

感谢下面小伙伴的打赏:

姓名 金额 方式
*声 50¥ 微信
**东 5¥ 支付宝

联系方式

更多资讯内容,欢迎扫描关注我的个人微信公众号:【我的Android开源之旅】

xhttp2's People

Contributors

littlecurl avatar xuexiangjys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xhttp2's Issues

下载请求

发现了一个小小小的问题:
在【DownloadRequest】下载请求中,环境在线程冲中并不能【mIsSyncRequest == true】使用同步的请求;
假如当前环境处于【自定义线程池】中,正在下载大文件的时候,网络发生闪断或者文件服务器突然关闭,订阅者就会有概率接收不到由【AsyncTimeout】抛出的【InterruptedIOException异常】,而导致卡死

PS:
1.当然这种情况只是偶然,并不一定发生,【大概率会抛出:connection reset 异常】
2.关于同步请求,小弟有个小建议:可以使用【CountDownLatch】辅助类喔

image

返回code为String类型,需要怎么操作

问题描述(必填)
http接口返回的是String类型的code,需要修改框架中的哪写部分?

使用的XHttp2版本(必填)
2.0.2

期望的效果
自定义ApiResult,并且code为String类型

NetMethod能否支持设置hearder参数哈

提Bug前需要做的事情

1.如果是集成问题的话,请保证仔细按照如何引用的步骤,一步一步来,不要跳步骤!
2.详细阅读过使用手册,并且确保是框架的问题。

如果以上都不能解决你的问题,那么请按照以下说明仔细填写信息,这里需要说明的是:不符合填写要求的issue一律不予理会,希望这样能节约大家的时间!


问题描述(必填)
你好,这个框架用着很不错,请问一下NetMethod注解能否支持设置hearder参数呀,有时候需要动态设置头部参数(当然我知道直接调用Xhttp.post().headers("","")这种方式是可以动态设置headers),只是希望NetMethod注解的这种方式也能支持呢,谢谢

使用的XHttp2版本(必填)
2.0.4

期望的效果
NetMethod注解的这种方式也能支持设置hearders

附加信息
不是bug,只是小小建议哈

xhttp2使用时提示类型转换错误

提Bug前需要做的事情

1.如果是集成问题的话,请保证仔细按照如何引用的步骤,一步一步来,不要跳步骤!
2.详细阅读过使用手册,并且确保是框架的问题。

如果以上都不能解决你的问题,那么请按照以下说明仔细填写信息,这里需要说明的是:不符合填写要求的issue一律不予理会,希望这样能节约大家的时间!


问题描述(必填)
xhttp2使用时提示类型转换错误

使用的XHttp2版本(必填)
最新版本

如何重现(必填)
重现的步骤:
xhttp2使用时提示类型转换错误,第一次使用这个框架,不清楚怎么回事,也没有找到相关的答案。

期望的效果
对你期望的效果进行清晰而简明的描述。

截图
如果方便的话,贴一下程序截图和代码片段以帮助解释您的问题。

设备信息
请填写一下你运行设备的信息,信息越全越有助于我理解问题

  • 设备名: [e.g. 华为P20]
  • Android版本: [e.g. Android 7.0]
  • 设备型号 [e.g. ]
  • 系统版本(手机厂商定制rom)

附加信息
在此处添加任何有关该问题的任何其他说明。

如何经行多个业务请求的输入

我下面这些用法很麻烦,数据传输有时候也有问题
我想了解下xhttp2 在动态出入baseURL 怎么弄
我看你的代码是在application 的 oncreate 中注入
XHttpSDK.setBaseUrl(SettingSPUtils.getInstance().getApiURL()); //设置网络请求的基础地址

这样就有点被动,如果
我有多个ApiURL 那你这个就不能使用了
如下

使用
RetrofitFactory.getInstance(MyApi.APP_SHOP_DOMAIN)
.getShoesService().getShoesPage(0,7)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.subscribe(new Consumer<List>() {
@OverRide
public void accept(List shoesList) throws Exception {
AdShoesDataAdapter adShoesDataAdapter = new AdShoesDataAdapter(getContext(), shoesList);
adShoesView.setAdapter(adShoesDataAdapter);
adShoesDataAdapter.setmItemClickListener(new AdShoesDataAdapter.MyItemClickListener() {
@OverRide
public void onItemClick(View view, int position) {
Intent intent = new Intent(getContext(), GroupBuyingActivity.class);
intent.putExtra("identifier", String.valueOf(shoesList.get(position).getIdentifier()));
startActivity(intent);
}
});
}
});

public interface MyApi {

String APP_STUDY_DOMAIN = "http://192.168.1.101:10082";
String APP_SHOP_DOMAIN = "http://192.168.1.101:10081";

}

public class RetrofitFactory {

private  ShoesService mShoesService;
private static RetrofitFactory instance;
private static final int TIMEOUT_READ = 15;
private static final int TIMEOUT_CONNECTION = 15;

public static RetrofitFactory getInstance(String baseURL) {
    if (instance == null) {
        synchronized (RetrofitManager.class) {
            if (instance == null) {
                instance = new RetrofitFactory(baseURL);
            }
        }
    }
    return instance;
}

public RetrofitFactory(String baseURL) {
    get(baseURL);
}

private <T> T create(String baseURL, Class<T> service){
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseURL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(MyHttpApplication.getApplication().createOkHttpClient())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build();
    return retrofit.create(service);
}


private void get(String baseURL){

    HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor("App Loging : ");
    HttpLoggingInterceptor.Level level = BuildConfig.DEBUG ?
            HttpLoggingInterceptor.Level.HEADERS :
            HttpLoggingInterceptor.Level.NONE;
    logInterceptor.setLevel(level);

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

    OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .addInterceptor(new LoggingInterceptor())
            .readTimeout(TIMEOUT_READ, TimeUnit.SECONDS)
            .connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS)
            .build();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseURL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(mOkHttpClient)
            .build();
    this.mShoesService = retrofit.create(ShoesService.class);
}

public ShoesService getShoesService() {
    return mShoesService;
}

class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        //这个chain里面包含了request和response,所以你要什么都可以从这里拿
        Request request = chain.request();
        long t1 = System.nanoTime();//请求发起的时间
        LogUtils.sf(String.format("发送请求 %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();//收到响应的时间
        //这里不能直接使用response.body().string()的方式输出日志
        //因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一
        //个新的response给应用层处理
        ResponseBody responseBody = response.peekBody(1024 * 1024);
        LogUtils.sf(String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
                response.request().url(),
                responseBody.string(),
                (t2 - t1) / 1e6d,
                response.headers()));
        return response;
    }
}

}

Subscriber is onError

ttp]: -------------------------------response-------------------------------
I/[XHttp]: <-- 200 http://192.168.1.101:19081/shoes/query/89851 (271ms)

I/[XHttp]: Content-Type: application/json;charset=UTF-8
I/[XHttp]: Transfer-Encoding: chunked
Date: Mon, 04 Feb 2019 05:04:16 GMT
I/[XHttp]: Cache-Control: no-cache
I/[XHttp]:
I/[XHttp]: body:{"shoes":{"id":8,"name":"Belle/百丽冬季专柜同款黑色牛皮/人造革男休闲鞋5KF01DM7","price":"439","buyer":2122,"type":1,"cover":"http://res.warunion.com/shoes/89851/100573221.jpg","identifier":89851,"size":"40,41,42,43,44","color":"黑色,白色,蓝色","shopid":0,"isself":0,"status":0,"slaveprice":111.0,"quantity":345,"currenttime":"2019-02-03"},"images":[{"id":1,"identifier":222,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_00_b.jpg","imagestatus":1,"shoesorder":1},{"id":2,"identifier":221,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_01_b.jpg","imagestatus":1,"shoesorder":2},{"id":3,"identifier":223,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_02_b.jpg\r\n","imagestatus":1,"shoesorder":3},{"id":4,"identifier":224,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_03_b.jpg\r\n","imagestatus":1,"shoesorder":4},{"id":5,"identifier":225,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_04_b.jpg\r\n","imagestatus":1,"shoesorder":5},{"id":6,"identifier":226,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_10_b.jpg\r\n","imagestatus":1,"shoesorder":6},{"id":7,"identifier":227,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_11_b.jpg\r\n","imagestatus":1,"shoesorder":7},{"id":8,"identifier":228,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_12_b.jpg","imagestatus":1,"shoesorder":8},{"id":9,"identifier":229,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_40_b.jpg","imagestatus":1,"shoesorder":9},{"id":10,"identifier":230,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_41_b.jpg\r\n","imagestatus":1,"shoesorder":10},{"id":11,"identifier":231,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_42_b.jpg\r\n","imagestatus":1,"shoesorder":11},{"id":12,"identifier":232,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_51_b.jpg\r\n","imagestatus":1,"shoesorder":12},{"id":13,"identifier":233,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_52_b.jpg\r\n","imagestatus":1,"shoesorder":13},{"id":14,"identifier":234,"shoesidentifier":89851,"imageaddr":"http://res.warunion.com/shoes/89851/100573221_52_b.jpg\r\n","imagestatus":1,"shoesorder":14}]}
I/[XHttp]: <-- END HTTP
E/[XHttp]: --> Subscriber is onError
--> e instanceof ApiException, message:空指针错误
E/[Logger]: com.xuexiang.xhttp2.exception.ApiException: 空指针错误
at com.xuexiang.xhttp2.exception.ApiExceptionHandler.handleException(ApiExceptionHandler.java:51)
at com.xuexiang.xhttp2.transform.func.HttpResponseThrowableFunc.apply(HttpResponseThrowableFunc.java:34)
at com.xuexiang.xhttp2.transform.func.HttpResponseThrowableFunc.apply(HttpResponseThrowableFunc.java:31)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext$OnErrorNextObserver.onError(ObservableOnErrorNext.java:91)
at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
at io.reactivex.internal.observers.BasicFuseableObserver.fail(BasicFuseableObserver.java:110)
at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:61)
at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:64)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:51)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onNext(BodyObservable.java:37)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:44)
at io.reactivex.Observable.subscribe(Observable.java:12005)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:34)
at io.reactivex.Observable.subscribe(Observable.java:12005)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:33)
at io.reactivex.Observable.subscribe(Observable.java:12005)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:33)
at io.reactivex.Observable.subscribe(Observable.java:12005)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext.subscribeActual(ObservableOnErrorNext.java:38)
at io.reactivex.Observable.subscribe(Observable.java:12005)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:571)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
D/EGL_emulation: eglMakeCurrent: 0xdd305480: ver 2 0 (tinfo 0xdd303790)
D/EGL_emulation: eglMakeCurrent: 0xdd305480: ver 2 0 (tinfo 0xdd303790)

请更新下三方库支持版本

implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
使用最新的三方库,暴露很多问题哟。

关于ApiResultFunc 自定义ApiResult 解析时可能存在错误

在调试时发现 ApiResultFunc 类 apply() 方法,line:74 总是执行自定义 ApiResult 的解析。并且内部解析方法 解析会错误。
走默认ApiResult的解析则 Success ,

public ApiResult<T> apply(ResponseBody responseBody) throws Exception {
        ApiResult<T> apiResult = new ApiResult<>();
        apiResult.setCode(-1);
        if (mType instanceof ParameterizedType) {
            // 自定义ApiResult
            apiResult = parseCustomApiResult(responseBody, apiResult);
        } else {
            // 默认ApiResult
            apiResult = parseApiResult(responseBody, apiResult);
        }
        return apiResult;
    }

if (mType instanceof ParameterizedType) 这个条件判断 应该 alwasy true 吧? 不管是使用自定义的那种post 方式, 还是直接使用 XHttp.post() 内部默认的方式。 条件每次都会默认走进这里执行。

tips:
我项目中 code = 1 为成功, 修改了下代码,调试了一番。使用默认 ApiResult 解析逻辑可以解析成功。

比较喜欢你的系列的类库,上手ing

请求失败: 首页数据获取成功

`log里看着请求成功了,打印了返回的数据,但是走了OnError()方法。。。用的内置的通用请求,返回数据格式符合要求,Data,Msg,Code

你好,请问我上传图片如何使用生命周期绑定?

   XHttp.get("/face/statistics")
            .syncRequest(false) //异步请求
            .onMainThread(true)
            //回到主线程
            .execute(object : CallBackProxy<CustomApiResult<FaceInfoBean>, FaceInfoBean>(object :TipRequestCallBack<FaceInfoBean>() {
                override fun onSuccess(response: FaceInfoBean?) {
                     Log.i("TAG", "请求成功:" + response)

                }

            }) {})

目前我使用这种方式上传图片是没有问题的,请问如何使用 .compose(RxLifecycle.with(this).bindToLifecycle())方式上传图片,请解答下,谢谢

希望提供限流支持

部分业务场景做下载时,防止带宽被完全占用,需要做限流,当前框架不支持,希望可以改进

如何开启同步请求的?

兄弟,怎么开启同步请求的?我这么写 syncRequest(true).onMainThread(false) 还是会报错:W/System.err: android.os.NetworkOnMainThreadException

文件上传在真机上总是报网络请求超时,而模拟器上基本不会

问题描述(必填)

写了个图片上传功能,用到了文件上传,但是在真机上总是报网络请求超时错误,即使设置了超时时间为60s。而模拟器上基本不会有这个问题。希望作者大大帮忙看看👀,感谢

使用的XHttp2版本(2.04)

期望的效果

不管是真机还是模拟器都能成功上传图片。

截图

图片上传代码:

image

真机报错:

image

模拟器成功上传图片,但是也报这个错:

image

设备信息

  • 设备名: [小米手机]
  • Android版本: [Android 10.0]

apk打包后的软件,接受后台返回数据为空

问题描述(必填)
在进行debug的时候程序无异常,但打包为apk,进行安装后,接受返回数据为空

使用的XHttp2版本(必填)
2.0.4

如何重现(必填)
重现的步骤:
XHttp.post("/user/tet")
.params("uphone",etPhoneNumber.getText().toString())
.params("upsd",etVerifyCode.getText().toString())
// .upJson(JsonUtil.toJson(user))
.syncRequest(false)
.execute(new SimpleCallBack() {
@OverRide
public void onSuccess(User response) throws Throwable {
Util.setUser(response);
ActivityUtils.startActivity(MainActivity.class);
}
@OverRide
public void onError(ApiException e) {
XToastUtils.toast("账号或密码错误");
System.out.println("Error");
}
});

response 为空

期望的效果
对你期望的效果进行清晰而简明的描述。

截图
如果方便的话,贴一下程序截图和代码片段以帮助解释您的问题。

设备信息
请填写一下你运行设备的信息,信息越全越有助于我理解问题

  • 设备名: [e.g. 华为P20]
  • Android版本: [e.g. Android 7.0]
  • 设备型号 [e.g. ]
  • 系统版本(手机厂商定制rom)

附加信息
在此处添加任何有关该问题的任何其他说明。

使用Xhttp2 出现 BEGIN_OBJECT but was BEGIN_ARRAY 错误

W/System.err: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 2 column 2 path $

网上搜了一下,发现很多使用 使用RxJava2 + Retrofit2 的都容易出现这样的问题,建议针对这样的问题进行处理,现在找不到处理的地方

这个是不是不支持嵌套泛型啊?

json如下

{
	"code": "0",
	"msg": "成功!",
	"data": {
		"list": [
			{ "id": "112", "name": "巡检0002#" },
			{ "id": "113", "name": "巡检0003#" },
		],
		"total": 7,
		"size": 20,
		"current": 1
	}
}

我定义的 类如下

public class PageRes<T> {
        private Integer total;
        private Integer size;
        private Integer current;
}

这个 T可能是 设备类 ,或者 是 其他 的 类
然后 我用请求

        XHttp.post("/netservice/net/inspection/page")
                .upJson(JsonUtil.toJson(new BaseQueryParams(1, 20)))
                .execute(new TipCallBack<PageRes<Device>>() {
                    @Override
                    public void onSuccess(PageRes<Device> response) {
                    }

这样解析会报 类型转换错误
但是我 如果 把 泛型去掉 ,他就能解析了

        XHttp.post("/netservice/net/inspection/page")
                .upJson(JsonUtil.toJson(new BaseQueryParams(1, 20)))
                .execute(new TipCallBack<PageRes>() {
                    @Override
                    public void onSuccess(PageRes response) {
// 泛型去掉可以解析,但是我要 自己 解析 response 里面 的list 转成 实体列表 

                    }

作者能不能加上 嵌套 泛型支持呢

网络请求成功,但总是进入onError()方法

问题描述(必填)

在app开发过程中请求后端API没有问题,打包成 release apk 并安装到手机后请求报错,总是进入onError()方法,但是请求是成功的。(备注:如果打成 debug 的 apk 是没有问题的,打成 release apk 就有这个问题,不知道为什么,还希望作者大大帮忙看看)。

使用的XHttp2版本(2.0.4)

如何重现(必填)

网络请求报错问题。

期望的效果

请求成功进入onSuccess()方法。

截图

  1. 请求方法代码截图:

image

  1. 请求响应以及报错截图:

image

可以看到请求报错了,message为null

3.自定义api代码截图:

image

设备信息

  • 设备名: [小米手机]
  • Android版本: [Android 10.0]

空值处理方法分享

分享:由于 rxJava2 不支持空值传递,所以 ApiResult 中 data 为空时会抛异常,所以小弟做了以下修改

image

位于:com.xuexiang.xhttp2.transform.func.ApiResultFunc

Xhttp 请求生命周期绑定

你好,我阅读了文档,网络请求生命周期和Activity/Fragment生命周期绑定,请问在XHTTP请求中如何使用RxJava的compose的操作符进行绑定?Xhttp请求中无法调用compose函数

使用疑問

问题描述(必填)
再使用过程中, 自定义请求响应的API结构 CustomApiResult ,问我如果想在 执行结果成功 里面,如何能拿到 第一层级里面的 result 字段, 因为 SimpleCallBack 中 传递的类型一般都是 T data , 的类型。 onSuccess 只能拿到 轉換後的 data 數據。 如何拿到 第一層級的 其它字段?

image

大佬,能否兼容一下非正常返回的ApiResult中,服务端返回 data 为空字符串的这种情况?或者解决方案 谢谢

提Bug前需要做的事情

1.如果是集成问题的话,请保证仔细按照如何引用的步骤,一步一步来,不要跳步骤!
2.详细阅读过使用手册,并且确保是框架的问题。

如果以上都不能解决你的问题,那么请按照以下说明仔细填写信息,这里需要说明的是:不符合填写要求的issue一律不予理会,希望这样能节约大家的时间!


问题描述(必填)

大佬,能否兼容一下非正常返回的ApiResult中,服务端返回 data 为空字符串的这种情况?
正常的返回都没有问题,框架很完美,但是异常数据 或者空的数据 就会有解析错误的问题!
当然。这不是框架的问题,框架很完美,要是能更好的兼容非正常数据 那就太完美了!

使用的XHttp2版本(必填)

最新版本

如何重现(必填)
重现的步骤:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

期望的效果
对你期望的效果进行清晰而简明的描述。

截图
如果方便的话,贴一下程序截图和代码片段以帮助解释您的问题。

设备信息
请填写一下你运行设备的信息,信息越全越有助于我理解问题

  • 设备名: [e.g. 华为P20]
  • Android版本: [e.g. Android 7.0]
  • 设备型号 [e.g. ]
  • 系统版本(手机厂商定制rom)

附加信息
在此处添加任何有关该问题的任何其他说明。

不支持直接返回jsonObject格式

提Bug前需要做的事情

1.如果是集成问题的话,请保证仔细按照如何引用的步骤,一步一步来,不要跳步骤!
2.详细阅读过使用手册,并且确保是框架的问题。

如果以上都不能解决你的问题,那么请按照以下说明仔细填写信息,这里需要说明的是:不符合填写要求的issue一律不予理会,希望这样能节约大家的时间!


问题描述(必填)
对问题进行清晰而简明的描述,把握问题的关键点。

使用的XHttp2版本(必填)

如何重现(必填)
重现的步骤:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

期望的效果
对你期望的效果进行清晰而简明的描述。

截图
如果方便的话,贴一下程序截图和代码片段以帮助解释您的问题。

设备信息
请填写一下你运行设备的信息,信息越全越有助于我理解问题

  • 设备名: [e.g. 华为P20]
  • Android版本: [e.g. Android 7.0]
  • 设备型号 [e.g. ]
  • 系统版本(手机厂商定制rom)

附加信息
在此处添加任何有关该问题的任何其他说明。

无法解析接收的JSON中字符串格式日期

在使用XHttp2 POST请求接收JSON数据时,若数据中带有字符串时间,则无法成功

com.github.xuexiangjys:XHttp2:2.0.4

截图
image
image

设备信息
IDEA 2021.3.1 模拟器Pixel XL api 24

附加信息
在此处添加任何有关该问题的任何其他说明。

根据文件路径获取不到文件

因为项目需要用SDK30,上传文件时,出现:
I/[XHttp]: <-- HTTP FAILED: /storage/emulated/0/Android/data/com.xuexiang.templateproject/cache/XQRCode_1652508557266.png (No such file or directory)
E/[XHttp]: --> Subscriber is onError
E/[XHttp]: --> e instanceof ApiException, message:java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.xuexiang.templateproject/cache/XQRCode_1652508557266.png (No such file or directory)
E/[XHttp]: com.xuexiang.xhttp2.exception.ApiException: java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.xuexiang.templateproject/cache/XQRCode_1652508557266.png (No such file or directory)
at com.xuexiang.xhttp2.exception.ApiExceptionHandler.handleException(ApiExceptionHandler.java:51)
at com.xuexiang.xhttp2.transform.func.HttpResponseThrowableFunc.apply(HttpResponseThrowableFunc.java:34)
at com.xuexiang.xhttp2.transform.func.HttpResponseThrowableFunc.apply(HttpResponseThrowableFunc.java:31)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext$OnErrorNextObserver.onError(ObservableOnErrorNext.java:91)
at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
at retrofit2.adapter.rxjava2.BodyObservable$BodyObserver.onError(BodyObservable.java:77)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:60)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at retrofit2.adapter.rxjava2.BodyObservable.subscribeActual(BodyObservable.java:35)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:32)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:32)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at io.reactivex.internal.operators.observable.ObservableOnErrorNext.subscribeActual(ObservableOnErrorNext.java:38)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
Caused by: java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.xuexiang.templateproject/cache/XQRCode_1652508557266.png (No such file or directory)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.(FileInputStream.java:146)
at okio.Okio.source(Okio.kt:194)
at okhttp3.RequestBody$3.writeTo(RequestBody.java:119)
at com.xuexiang.xhttp2.request.body.UploadProgressRequestBody.writeTo(UploadProgressRequestBody.java:87)
at okhttp3.MultipartBody.writeOrCountBytes(MultipartBody.java:173)
at okhttp3.MultipartBody.writeTo(MultipartBody.java:114)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:72)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.xuexiang.xhttp2.interceptor.NoCacheInterceptor.intercept(NoCacheInterceptor.java:36)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorC
E/[XHttp]: hain.java:121)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:127)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.xuexiang.xhttp2.interceptor.HeadersInterceptor.intercept(HeadersInterceptor.java:56)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.xuexiang.xhttp2.interceptor.NoCacheInterceptor.intercept(NoCacheInterceptor.java:36)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at com.xuexiang.xhttp2.interceptor.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:110)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:257)
at okhttp3.RealCall.execute(RealCall.java:93)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:204)
at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:46)
... 18 more

org.json.JSONException: No value for Code

你好 我这样写
.onMainThread(true)
.execute(new SimpleCallBack() {
@OverRide
public void onSuccess(User response) {
Log.e("test","返回的response--->"+response.toString());
}
@OverRide
public void onError(ApiException e) {
Log.e("test","返回的error--->"+e.getDetailMessage());
}

            });

然后发生了下面的错误 请问数据格式有要求吗? 不能直接获取一个jsonObject吗?

org.json.JSONException: No value for Code
2018-08-14 14:51:11.805 24706-24851/org.telegram.biyouchat.beta W/System.err: at org.json.JSONObject.get(JSONObject.java:389)
2018-08-14 14:51:11.805 24706-24851/org.telegram.biyouchat.beta W/System.err: at org.json.JSONObject.getInt(JSONObject.java:478)
at com.xuexiang.xhttp2.transform.func.ApiResultFunc.checkApiResult(ApiResultFunc.java:174)

使用中问题和解决方法记录

自定义Result

可能遇到的问题:

  1. ApiResult.class.isAssignableFrom(cls)err!! 原因类被混淆了 需要添加 -keep class 路径
  2. 返回code为不是int类型;
    解决当然是 新建一个枚举映射类啊HttpResponseCode
public class CustomApiResult<T> extends ApiResult<T> {

    private String errorCode;
    private String errorMessage;
    private String requestId;
    private Boolean success;
    private String timestamp;

     //省略get set....

    @Override
    public int getCode() {
        return HttpResponseCode.transServerCode(errorCode);
    }

    @Override
    public String getMsg() {
        return errorMessage;
    }

    @Override
    public boolean isSuccess() {
        return success;
    }

    @Override
    public CustomApiResult setData(T data) {
        super.setData(data);
        return this;
    }

    @Override
    public T getData() {
        return super.getData();
    }

    @Override
    public String toString() {
        return "CustomApiResult{" +
                "code='" + super.getCode() + '\'' +
                ", message='" + super.getMsg() + '\'' +
                ", result=" + super.getData() +
                '}';
    }
}




public enum HttpResponseCode {
    //成功code
    SUCCESS_CODE("0000", 200),
    //未绑定店铺
    NO_SHOP_ERROR_CODE("A0001", 201),
    //需要验证码
    NEED_LOGIN_CODE("A0211", 202);

    HttpResponseCode(String serverCode, int pdaCode) {
        this.serverCode = serverCode;
        this.pdaCode = pdaCode;
    }

    private String serverCode;
    private int pdaCode;

    public String getServerCode() {
        return serverCode;
    }

    public int getPdaCode() {
        return pdaCode;
    }

    public static int transServerCode(String serverCode) {
        for (HttpResponseCode code : HttpResponseCode.values()) {
            if (code.getServerCode().equals(serverCode)) {
                return code.getPdaCode();
            }
        }
        return 0;
    }
}

重要事情说三遍

别忘了 添加 : -keep class CustomApiResult 路径

别忘了 添加 : -keep class CustomApiResult 路径

别忘了 添加 : -keep class CustomApiResult 路径

返回 的Data 不是一个JsonObject是String Long 等类型

{
    "errorCode": "00000",
    "errorMessage": null,
    "data": "rsapublckkey",
    "requestId": "xfdf22121s",
    "success": true
}




//此处以String为例子
XHttp.get("/web/auth/getEncryptKey")
			.execute(new CallBackProxy<CustomApiResult<String>, String>(new SimpleCallBack<String>() {
				@Override
				public void onSuccess(String responseString) throws Throwable {
				//这里的responseString 为 上面的整个后台返回报文
				ApiResponse response = GSONUtil.parseStringData(responseString, ApiResponse.class);
				//然后  类型自己转 判空啥的 注意
				String a = response.getData(),toSting();
				}
			}){});  //千万注意,这里的{}一定不能去掉,否则解析错误


//lombok插件
@Data
public class ApiResponse implements Serializable {
    private String errorCode;
    private String errorMessage;
    private String requestId;
    private Boolean success;
    private Object data;
 
}

动态添加参数的拦截器遇到的坑

添加token 时当token 包含特殊字符 如 空格 等 会转义导致后台校验token失败
解决:不使用拦截器添加;在XHttp.get("") 或者XHttp.post("")是添加Header;
在自己项目里 对 XHttp.get("") ;post等请求 再封装一层,在这里添加Header 等其他操作

Cookie

    XHttp.getInstance().setCookieStore(CookieManager.getInstance(application));

反正我这一行代码就解决 了;

其他走代理的请求 没用过 没试过;

PUT请求无法被拦截添加请求头的问题

问题描述(必填)
on 29 Jan 动态拦截器支持请求头的更新

BaseDynamicInterceptor 里面只做了对 GET 和 POST 的拦截处理,既然 XHttp2 支持 PUT 和 DELETE ,我认为有必要也做一下拦截,一视同仁。

使用的XHttp2版本(必填)
2.0.4(最新版本)

如何重现(必填)
重现的步骤:

  1. CustomDynamicInterceptor 添加Token,打个断点
  2. XHttp.put().accessToken(true)
  3. 请求时,CustomDynamicInterceptor断点无法进入

期望的效果
期望 BaseDynamicInterceptor 中添加对 PUT 和 DELETE 的拦截支持。

截图
image

请求后台总是返回onError,不会进onSuccess

请求后台部分的代码

            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.put("Accept", "application/json, text/plain, */*");
            String token = TokenUtils.getToken();
            String author = "Bearer " + token;
            httpHeaders.put("Authorization", author);
            String reqJson = new Gson().toJson(bizRideOrder1);
            HttpParams params = new HttpParams();
            List<BizRideOrder> result = new ArrayList<>();
            XHttp.post("/system/order")
                    .upJson(reqJson)
                    .headers(httpHeaders)
                    .onMainThread(true)
                    .execute(new SimpleCallBack<BizRideOrder>() {
                        @Override
                        public void onSuccess(BizRideOrder response) throws Throwable {
                            bizRideOrder = response;
                            flags = true;
                        }
                        @Override
                        public void onError(ApiException e) {
                            ToastUtils.toast(e.toString());
                        }
                    });

请求返回的日志截图:
image

全局配置:
image

关于ApiResultFunc.parseCustomApiResult()方法有可能造成空指针的问题

问题描述(必填)
有些后端业务,完成之后,data会返回为null,会造成空指针错误。

比如,简单的注册,成功后返回:
{
code:0,
msg:"success",
data:null
}
我在使用的时候,这样会造成空指针错误,Debug半天没找到在哪修改。
后来干脆就在 ApiResultFunc.parseCustomApiResult() 这个方法里,解析完 json 之后,判断一下 data 是否为null,是null 的话,就用 new Object() 代替一下。

 ApiResult result = mGson.fromJson(json, mType);

if (result.getData() == null) {

    result.setData(new Object());

}

这样做是否正确?是我代码逻辑错误,还是框架Bug?

使用的XHttp2版本(必填)
2.0.0

如何重现(必填)
重现的步骤:

  1. 后端 data 返回为 null
  2. 前端使用 new SimpleCallBack<Object>(){} 回调

期望的效果
期望回答一下,这样做是否正确?是我代码逻辑错误,还是框架Bug?

截图
image

文件下载开始后如何能够在update的回调中停止下载?

问题描述(必填)
问题:使用XHttp.downLoad方法进行下载文件,我需要在下载的update回调中停止下载,要调用什么方法能停止呢?

使用的XHttp2版本(必填)
版本:xhttp2 = "2.0.4"

如何重现(必填)

XHttp.downLoad(serverUrl + ApiConstant.API_FILE_DOWN + programResource.getResourceId())
                .savePath(MyApp.CACHE_RESOURCE_PATH)
                .saveName(fileName)
                .execute(new DownloadProgressCallBack<String>() {
                    @Override
                    public void onStart() {
                        XLogger.e("onStart");
                    }

                    @Override
                    public void onError(ApiException e) {
                        //这里想要停止这个下载,要如何停止??????????
                        
                    }

                    @Override
                    public void update(long bytesRead, long contentLength, boolean done) {
                    }

                    @Override
                    public void onComplete(String path) {

                    }
                });

可以画个框架设计图吗

还有个问题,设计Xhttp.get(url) 这种封装,和直接使用Okhttp提供的请求方式类似, Retrofit将路径和请求解耦,这种哦反向封装有必要吗

如果服务器端返回的Data字段为null会报“空指针错误”

问题描述(必填)
返回格式完全参照:https://github.com/xuexiangjys/XHttp2/wiki/Response%E5%AE%9E%E4%BD%93%E8%A7%84%E8%8C%83 中的标准格式,但是如果服务器端返回的Data字段为null会报“空指针错误”。

使用的XHttp2版本(必填)
2.0.0(使用当前最新的 https://github.com/xuexiangjys/TemplateAppProject 模板)

如何重现(必填)
重现的步骤:
使用XHttpProxy进行请求,如果服务器端接口返回数组中Data为null则报空指针错误。非null不会有这个错误。

期望的效果
Data为null时将对象解析为null,而不是抛出错误。

addPostParamsSign

image
private Request addPostParamsSign(Request request) throws UnsupportedEncodingException {
if (request.body() instanceof FormBody) {
FormBody.Builder bodyBuilder = new FormBody.Builder();
FormBody formBody = (FormBody) request.body();

        //原有的参数
        TreeMap<String, Object> oldParams = new TreeMap<>();
        for (int i = 0; i < formBody.size(); i++) {
            oldParams.put(formBody.encodedName(i), formBody.encodedValue(i));
        }

        //拼装新的参数
        TreeMap<String, Object> newParams = updateDynamicParams(oldParams);
        Utils.checkNotNull(newParams, "newParams == null");
        for (Map.Entry<String, Object> entry : newParams.entrySet()) {
            String value = URLDecoder.decode(String.valueOf(entry.getValue()), HttpUtils.UTF8.name());
            bodyBuilder.addEncoded(entry.getKey(), value);
        }
        String url = HttpUtils.createUrlFromParams(HttpUtils.parseUrl(mHttpUrl.url().toString()), newParams);
        HttpLog.i(url);
        formBody = bodyBuilder.build();
        request = createNewRequest(request.newBuilder().post(formBody));
    } else if (request.body() instanceof MultipartBody) {
        MultipartBody multipartBody = (MultipartBody) request.body();
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        List<MultipartBody.Part> oldParts = multipartBody.parts();

        //拼装新的参数
        List<MultipartBody.Part> newParts = new ArrayList<>(oldParts);
        TreeMap<String, Object> oldParams = new TreeMap<>();
        TreeMap<String, Object> newParams = updateDynamicParams(oldParams);
        for (Map.Entry<String, Object> paramEntry : newParams.entrySet()) {
            MultipartBody.Part part = MultipartBody.Part.createFormData(paramEntry.getKey(), String.valueOf(paramEntry.getValue()));
            newParts.add(part);
        }
        for (MultipartBody.Part part : newParts) {
            bodyBuilder.addPart(part);
        }
        multipartBody = bodyBuilder.build();
        request = createNewRequest(request.newBuilder().post(multipartBody));
    } else if (request.body() instanceof RequestBody) {
        TreeMap<String, Object> params = updateDynamicParams(new TreeMap<String, Object>());
        String url = HttpUtils.createUrlFromParams(HttpUtils.parseUrl(mHttpUrl.url().toString()), params);
        request = createNewRequest(request.newBuilder().url(url));
    }
    return request;
}

URLDecoder.decode 需要修改为URLEncoder.encode

设置非严格模式不起作用

问题描述(必填)
设置非严格模式后,依然会空指针

使用的XHttp2版本(必填)
2.0.4

如何重现(必填)
1.XHttp.getInstance().setStrictMode(false);
2.发送一个简单业务请求

期望的效果
data:null时不报空指针

截图
image

使用自定义请求响应,获取不到result

问题描述

由于请求返回code为字符串且正常码为“00000000”,所以自定义了请求响应
只对errorCode做了Integer.parseInt(errorCode),但在运行时报空指针错误,打断点发现除了errorCode为0,errorInfo和result均为null

使用的XHttp2版本

1.0.2

如何重现

自定义请求响应,将参考的

    public CustomApiResult<T> setErrorCode(int errorCode) {
        this.errorCode = errorCode;
        return this;
    }

修改为

    public CustomApiResult<T> setErrorCode(String errorCode) {
        this.errorCode = Integer.parseInt(errorCode);
        return this;
    }

请求方式

XHttp.post("/test/res")
                .baseUrl(App.URL_BASE)
                .execute(new CallBackProxy<CustomApiResult<Response>, Response>(new SimpleCallBack<Response>() {
                    @Override
                    public void onSuccess(Response response) throws Throwable {
                        callBack.onSuccess(response);
                    }

                    @Override
                    public void onError(ApiException e) {
                        callBack.onError(e);
                    }
                }){});

期望的效果

正常获得返回值

截图

断点截图

日志

2019-12-03 15:30:03.005 21871-25669/com.SpaceElves.box I/XHttp: -------------------------------response-------------------------------
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: <-- 200 OK https://prod.api.spaceevels.com/test/res (346ms)
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp:  
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Server: nginx
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Date: Tue, 03 Dec 2019 07:30:03 GMT
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Content-Type: application/json; charset=UTF-8
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Transfer-Encoding: chunked
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Connection: keep-alive
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	X-Powered-By: PHP/7.3.0
2019-12-03 15:30:03.006 21871-25669/com.SpaceElves.box I/XHttp: 	Cache-Control: no-cache
2019-12-03 15:30:03.007 21871-25669/com.SpaceElves.box I/XHttp:  
2019-12-03 15:30:03.008 21871-25669/com.SpaceElves.box I/XHttp: 	body:{"code":"00000000","msg":"服务调用成功!","data":{"token":"eaa824d3b50a53d38200084373e5f626"}}
2019-12-03 15:30:03.008 21871-25669/com.SpaceElves.box I/XHttp: <-- END HTTP
2019-12-03 15:30:03.016 21871-25669/com.SpaceElves.box D/CustomApiResult: isSuccess(): errorCode = 0
2019-12-03 15:30:03.016 21871-25669/com.SpaceElves.box D/CustomApiResult: isSuccess(): return = true
2019-12-03 15:30:03.017 21871-21871/com.SpaceElves.box E/XHttp: --> Subscriber is onError
2019-12-03 15:30:03.017 21871-21871/com.SpaceElves.box E/XHttp: --> e instanceof ApiException, message:空指针错误

设备信息

  • 设备名: 小米6
  • Android版本: Android 9
  • 设备型号 MI 6
  • 系统版本 MIUI 11 9.11.28

如何同步获取网络请求结果

XHttp.get(baseUrl + "/api/GetDeviceNo")
.params("imei",imei)
.keepJson(true)
.syncRequest(true)
.execute(String.class)
.subscribe(new TipRequestSubscriber() {
@OverRide
protected void onSuccess(String res) {
orderNo = res.replace(""","");
Log.e("GetOrderNo", "onSuccess: orderNo=" + orderNo);
}

                @Override
                public void onError(ApiException e) {
                    Log.e("GetOrderNo", "onError: ex =" + e.getMessage() );
                }
            });

报如下错误
W/System.err: android.os.NetworkOnMainThreadException
W/System.err: at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1288)

如何显示服务器端返回的httpstate的自定义消息,

服务器响应结果:HttpState:401,response body返回下面的内容

{
"code": 401,
"data": "用户不存在",
"message": "Unauthorized"
}

请问如何在UI上面显示这个json里面data属性的消息?
XHttpSDK.setSuccessCode(200); 设置过200了。
拦截器

public class CustomExpiredInterceptor extends BaseExpiredInterceptor {
@OverRide
protected ExpiredInfo isResponseExpired(Response oldResponse, String bodyString) {
int code = JSONUtils.getInt(bodyString.toLowerCase(), "code", 0);
ExpiredInfo expiredInfo = new ExpiredInfo(code);
expiredInfo.setExpiredType(code)
.setCode(code)
.setBodyString(bodyString);
return expiredInfo;
}
@SuppressLint("CheckResult")
@OverRide
protected Response responseExpired(Response oldResponse, Chain chain, ExpiredInfo expiredInfo) {
return HttpUtils.getErrorResponse(oldResponse, expiredInfo.getCode(), expiredInfo.getBodyString());
}
}

登录操作

XHttpSDK.postToMain(login).execute(new SimpleCallBack() {
@OverRide
public void onSuccess(String token) throws Throwable {
if (!TextUtils.isEmpty(token)) {
LoginFragment.super.popToBack();
ActivityUtils.startActivity(MainActivity.class);
} else {
ToasterUtils.error("token没有!");
}
}
@OverRide
public void onError(ApiException e) {
ToasterUtils.error(e.getMessage(), 3000);
XLog.get().e(e, e.getMessage());
}
});

自定义API请求的话,如果有和默认的json的某个key一样的话,异常:定义多个json字段的exception

其他时候正常,自定义API请求的话,如果有和默认的json的某个key一样的话,
比如服务端返回的是:

{
        "success": true,
        "errorCode": "-1",
        "msg": "登录成功!",
        "body": {xxx}
}

会抛出异常declares multiple JSON fields named msg
但如果在自定义的ApiResult中不定义msg的话,重写父类的getMsg方法。如果返回结果successtrue的话,正常调用onSuccess;如果返回结果不是true,就会走onError。抛出ApiExceptiondetailMessagemsg的内容。

不知道是我食用姿势不对还是咋的。

返回其它的的json的话,正常使用。。。。。。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.