logo
当前位置:首 页 > 移动开发 >android > 查看文章

分析

实际需求:
返回json格式:

{
"code": 1,
"msg": "",
"time": 1470717690,
"data": T
}

其中data千变万化,但是总体来说是code决定了data的内容,所以在解析response返回值时需要对code进行一系列逻辑处理,这很重要。

以一个最常用的使用场景为例,去服务器取得我的新闻列表,因为我的这个其中涉及了权限验证即token的获取,当然也包含了token状态的过期以及服务器异常导致token无法通过验证的场景,基本所有情况都考虑到了。

下面我以代码的顺序为主来说明,防止扯来扯去,扯的自己都不知道说道哪里了。

前提:最好了解一点相关知识,关于三者Rxjava、Retrofit、Okhttp

gradle 引入的一些类库(三者Rxjava、Retrofit、Okhttp)

    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'io.reactivex:rxjava:1.1.7'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.orhanobut:logger:1.15'

构建工具类

我的主包:com.rz.app

public class HttpMethods {
    private static OkHttpClient okHttpClient = new OkHttpClient();
    private static Converter.Factory gsonConverterFactory = GsonConverterFactory.create();
    private static Converter.Factory advancedGsonConverterFactory = com.rz.app.api.convert.GsonConverterFactory.create();
    private static CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();
    public static Retrofit getApi() {
        Retrofit retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl("http://api.com/")
                .addConverterFactory(advancedGsonConverterFactory)
                .addCallAdapterFactory(rxJavaCallAdapterFactory)
                .build();
        return retrofit;
    }
}

重写gsoncovertfactory相关类(一共三个)

因为官方自带的这个处理转化类实在是粗糙,如果data的格式不对,那么会直接抛出JsonSyntaxException,这就很暴力了,我们应该根据code来判断相应的逻辑。举个例子:想获取新闻列表,那么data是数组格式,但是如果token过期,被中间件拦截(可以理解成权限验证),这个data就不返回或者干脆返回空值,那么会抛出这个异常,无法获取code不能准确做出相应的逻辑处理。

先和服务端约定好错误码

  • 我定义此异常
//code 为-9
public class ErrorException extends RuntimeException{
    public ErrorException(String s) {
        super(s);
    }
}
//code 为-1
public class GetTokenException extends RuntimeException {
}
//code 为0
public class MsgException extends RuntimeException{
    public MsgException(String s) {
        super(s);
    }
}
//code 为-2
public class NeedLoginException extends RuntimeException{
    public NeedLoginException(String s) {
        super(s);
    }
}

解释一下:
-9 请求错误(api的url错误)、服务端异常
-2 账号有误,客户端记录的账号不正确,会跳到登入页面
-1 token过期
0 正常情况下错误状态

  • 定义code的result类,用来作为中间返回值类
public class Result {
    private int code;
    private String msg;
    private int time;

    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 int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }

}
  • 定义返回值类,注意和上面的区别
public class Msg<T> {
    private int code;
    private String msg;
    private int time;
    private T data;

    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 int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • copy源码,首先copy过来三个文件,其实主要是修改GsonResponseBodyConverter。
    @Override
    public T convert(ResponseBody value) throws IOException {
        String respose = value.string();
        Result msg = gson.fromJson(respose, Result.class); // Result即为上面定义的中间返回值类

        if (msg.getCode() < 1) {
            value.close();
            switch (msg.getCode()) {
                case -9:
                    throw new ErrorException(msg.getMsg());

                case -2:
                    throw new NeedLoginException("需要登入");

                case -1:
                    Logger.d("token 过期");
                    throw new GetTokenException();

                case 0:
                    throw new MsgException(msg.getMsg());
            }
            throw new ErrorException("未定义错误码");
        }

        MediaType mediaType = value.contentType();
        Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
        InputStream inputStream = new ByteArrayInputStream(respose.getBytes());
        Reader reader = new InputStreamReader(inputStream,charset);

        JsonReader jsonReader = gson.newJsonReader(reader);

        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }

注意这里有个坑,使用不当会抛出java.lang.IllegalStateException,可以参考这位小伙伴http://www.jianshu.com/p/5b8b1062866b

因为你只能对ResponseBody读取一次 , 如果你调用了response.body().string()两次或者response.body().charStream()两次就会出现这个异常, 先调用string()再调用charStream()也不可以。
所以通常的做法是读取一次之后就保存起来,下次就不从ResponseBody里读取。

Rxjava主题逻辑

好了做了这么多的准备工作重要,开始重头戏了,先说一下思路,

判断token:
—-1为null:抛出GetTokenException,在retryWhen中用存储的账号网络请求token
—-2不为空:flatMap中请求news数据

1中:
—-1.1账号错误,抛出NeedLoginException,那么直接跳到登入页面
—-1.2获得了token,存储起来,并且flatMap中请求news数据

1.2中:
—-1.2.1如果服务器出现异常,即无法验证token,返回-1,抛出GetTokenException,会重复获取token如果不加处理,那就gg了,所以加个zipWith,只请求三次,第四次直接抛出ErrorException(“服务器异常”),远离gg

2中
—-返回-1,token过期,那么同1.2.1

retryWhen为中间处理层,subscribe中onError终极处理

假设token我们直接定义为

public class Token extends BaseModel {
    private String actoken;
    private int time;

    public String getActoken() {
        return actoken;
    }

    public void setActoken(String actoken) {
        this.actoken = actoken;
    }

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }

}

接口如下定义,News为新闻类(就不贴了)

public class ApiService {
    public interface GetTokenApi {
        @GET("index/gettoken/mobile/{mobile}/password/{password}")
        Observable<Msg<Token>> result(@Path("mobile") String mobile,@Path("password") String password);
    }

    public interface NewsApi {
        @GET("mycenter/getNews/token/{token}")
        Observable<Msg<List<News>>> result(@Path("token") String token);
    }
}

下面开始Rxjava处理主要逻辑

    protected Token token=null;
    protected Subscription subscription;
    //解除订阅
    protected void unsubscribe() {
        if (subscription != null && !subscription.isUnsubscribed()) {
            subscription.unsubscribe();
        }
    }

    ...

    public void getNews(){
        unsubscribe();
        subscription = Observable.just(null)
            .flatMap(new Func1<Object,Observable<List<News>>>(){

                @Override
                public Observable<List<News>> call(Object o) {
                    if(token == null){
                        Logger.d("token为null");
                        return Observable.error(new GetTokenException());
                    }
                    Logger.d("使用缓存的token");
                    // TODO: 本地判断token是否过期,当然服务器也会二次判断

                    return getApi().create(ApiService.NewsApi.class)
                            .result(token.getActoken())
                            .map(new Func1<Msg<List<News>>, List<News>>() {
                                @Override
                                public List<News> call(Msg<List<News>> listMsg) {
                                    return listMsg.getData();
                                }
                            });
                }
            })
            .retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                @Override
                public Observable<?> call(Observable<? extends Throwable> observable) {
                    return observable
                        .zipWith(Observable.range(1, 4), new Func2<Throwable, Integer, Throwable>() {
                            @Override
                            public Throwable call(Throwable throwable, Integer integer) {
                                if(integer == 4){
                                    throw new ErrorException("服务器异常");
                                }
                                return throwable;
                            }
                        })
                        .flatMap(new Func1<Throwable, Observable<?>>() {
                            @Override
                            public Observable<?> call(final Throwable throwable) {

                                if (throwable instanceof GetTokenException) {
                                    //TODO 获取存储的账户信息,用来获取token
                                    //如果没有,即首次登入,或者token过期,或者刚刚客户端注销等业务判断  需要抛出一个NeedLoginExcption
                                    // 这里假设有记录
                                    boolean firstLogin = false;
                                    boolean tokenExpired = false;
                                    boolean logoff = false;
                                    if(firstLogin || tokenExpired || logoff){
                                        return Observable.error(new NeedLoginException("需要登入"));
                                    }
                                    return getApi()
                                        .create(ApiService.GetTokenApi.class)
                                        .result("12345678910", "123456")
                                        .map(new Func1<Msg<Token>, Token>() {
                                            @Override
                                            public Token call(Msg<Token> msg) {
                                                return msg.getData();
                                            }
                                        })
                                        .doOnNext(new Action1<Token>() {
                                            @Override
                                            public void call(Token t) {
                                                Logger.d("存储token");
                                                //TODO: 存入缓存等
                                                token = t;
                                            }
                                        });
                                }

                                return Observable.error(throwable);
                            }
                        });
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())

            .subscribe(new Subscriber<List<News>>() {
                @Override
                public void onCompleted() {
                    //TODO: 完成逻辑
                }

                @Override
                public void onError(Throwable throwable) {
                    if(throwable instanceof NeedLoginException){
                        //TODO: 跳到登入页面
                    }else if(throwable instanceof ErrorException){
                        //TODO: 提示 throwable.getMessage()
                    }else if(throwable instanceof MsgException) {
                        //TODO: 提示 throwable.getMessage()
                    }else {
                        Logger.d(throwable.getClass());
                        //TODO: 还剩下网络异常处理
                    }
                }

                @Override
                public void onNext(List<KrNews> krNewses) {
                    //TODO: 更新ui
                }
            });
    }

附上我测试的结果图

token为空,会先使用账号获取token,在获取目标数据

1.gif

token可用,直接使用缓存的token

2.gif

服务器错误,尝试3次,第四次抛出异常,途中循环了三次

作者:laidian
链接:http://www.jianshu.com/p/56632762c199
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

说说梦想,谈谈感悟 ,聊聊技术,有啥要说的来github留言吧 https://github.com/cjx2328

—— 陈 建鑫

陈建鑫
你可能也喜欢Related Posts
footer logo
未经许可请勿自行使用、转载、修改、复制、发行、出售、发表或以其它方式利用本网站之内容。站长联系:cjx2328#126.com(修改#为@)
Copyright ©ziao Studio All Rights Reserved. E-mail:cjx2328#126.com(#号改成@) 沪ICP备14052271号-3