业务封装

在封装业务之前开发者应该先了解示例转换器对Kalle发起请求和Converter有个基本的了解。

本文将以JSON为例,结合比较常见的几种业务场景做示例。由于要服务端数据需要描述业务是否成功和业务失败的原因,因此我们来约定一下服务端返回数据的结构。

在任何情况下,服务器返回的body中的JSON数据必须是一个对象,用code返回业务状态,用data返回客户端要请求的实际数据,用message返回业务失败后的提示信息;其中code值为1时表示业务成功,code值为其它时表示业务失败;其中data可以是Entity,也可以是List<Entity>需要特别注意的是有些服务端会把httpCode同时作为业务状态码,这也是完全正确的,封装原理与本文相同。

服务端返回的数据的结构应该是:

{
    "code": 1,
    "data": ?,
    "message": "I am message."
}

例如返回的业务数据是空时:

{
    "code": 1,
    "data": null,
    "message": "Succeed"
}

例如返回的业务数据是字符串或者数字时:

{
    "code": 1,
    "data": "20180101",
    "message": "Succeed"
}

例如返回的业务数据是一个对象时:

{
    "code": 1,
    "data": {
        "name": "Kalle",
        "url": "https://github.com/yanzhenjie/Kalle",
    },
    "message": "Succeed"
}

例如返回的业务数据是一个列表时:

{
    "code": 1,
    "data": [
        {
            "name": "Kalle",
            "url": "https://github.com/yanzhenjie/Kalle",
        },
        {
            "name": "Kalle",
            "url": "https://github.com/yanzhenjie/Kalle",
        },
    ],
    "message": "Succeed."
}

例如当业务失败时返回失败原因:

{
    "code": 0,
    "data": null,
    "message": "帐号密码错误"
}

客户端对应的JavaBean是这样子:

/**
 * 目标实体类。
 */
public class UserInfo {
    String name;
    String url;
    ...
}

/**
 * Http业务包装类,Entity。
 */
public class HttpEntity<T> {
    int code;
    String data;
    String message;
    ...
}

上述数据的结构是有规则的,codemessage的数据类型是没有变化过的,只有data的类型会变化,但是data的类型无论怎么变,它的根本形式还是String,因此我们先用String来接受,然后再解析成目标实体对象。这只是用来辅助我们封装的,不会在每一个接口都需要这样去解析。

Converter和Callback的关系

示例转换器中分别介绍了CallbackConverter,我们在Callback中指定了业务成功和业务失败时的本地数据类型,在Converter中把服务器的返回的数据转换为本地数据类型的数据。

在异步请求中需要用Callback()实例来接受响应结果,Callback被初始化时需要指定两个泛型,第一个泛型是当业务成功时需要返回的对象的类型,第二个泛型是当业务失败时需要返回的对象的类型。

为了便于理解,先举个例子。我们在成功时返回AB,在失败时返回CD:

Kalle.get("http://www.example.com")
    .perform(new Callback<AB, CD>() {
        @Override
        public void onResponse(SimpleResponse<AB, CD> response) {
            if(response.isSucceed()) {
                // 业务成功,拿到业务成功的数据。
                AB ab = response.succeed();
                ...
            } else {
                // 业务失败,拿到业务失败的数据。
                CD cd = response.failed();
                ...
            }
            ...
        }
    });

解析数据时需要在Converterconvert()方法中解析,convert()方法规定了两个泛型,分别对应上述Callback的两个泛型,convert()方法入参有两个Type分别对应上述两个泛型的具体类型。

public interface Converter {
    <S, F> SimpleResponse<S, F> convert(Type succeed, Type failed,
            Response response, boolean fromCache) throws Exception;
}

这里的S对应上面ABF对应上面的CDsucceedS的具体类型,failedF的具体类型。例如当ABUserInfo时,那么succeed就是UserInfo.class,当CDString时,那么failed就是String.class

封装

如果客户端老老实实先请求HttpEntity,那么在onResponse()中的业务判断过程肯定会特别繁琐。能不能直接请求到服务器返回的UserInfo或者List<UserInfo>,并且仅仅只做一次业务成功与否的判断就可以呢,答案是肯定的。

理想的情况下我们想这样写代码:

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<UserInfo>() {
        @Override
        public void onResponse(SimpleResponse<UserInfo> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                // 拿到data对应的数据转成的实际对象。
                UserInfo user = response.succeed();
                ...
            } else {
                // 拿到message对应的提示,显示给用户。
                Toast.show(response.failed());
            }
        }
    });

...

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<List<UserInfo>>() {
        @Override
        public void onResponse(SimpleResponse<List<UserInfo>> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                List<UserInfo> userList = response.succeed();
                ...
            } else {
                Toast.show(response.failed());
            }
        }
    });

然后我们按照理想情况来封装Converter(本例使用的Json解析工具是FastJson):

public class JsonConverter implements Converter {

    @Override
    public <S, F> SimpleResponse<S, F> convert(Type succeed, Type failed,
            Response response, boolean fromCache) throws Exception {
        S succeedData = null; // 业务成功的数据。
        F failedData = null; // 业务失败的数据。

        int code = response.code();
        String serverJson = response.body().string();
        if (code >= 200 && code < 300) { // Http请求成功。
            HttpEntity httpEntity;
            try {
                httpEntity = JSON.parseObject(serverJson, HttpEntity.class);
            } catch (Exception e) {
                httpEntity = new HttpEntity();
                httpEntity.setCode(0);
                httpEntity.setMessage("服务器数据格式异常");
            }

            if (httpEntity.getCode() == 1) { // 服务端业务成功。
                try {
                    if (succeed == Integer.class) {
                        Integer succeedInt = Integer.parseInt(data);
                        succeedData = (S)succeedInt;
                    } else if (succeed == Long.class) {
                        Long succeedLong = Long.parseLong(data);
                        succeedData = (S)succeedLong;
                    } else if (succeed == String.class) {
                        succeedData = (S)data;
                    } else if (succeed == Boolean.class) {
                        Boolean succeedBoolean = Boolean.parseBoolean(data);
                        succeedData = (S)succeedBoolean;
                    } else if (succeed == JSONObject.class) {
                        JSONObject object = JSONObject.parseObject(data);
                        succeedData = (S)object;
                    } else {
                        succeedData = JSON.parseObject(data, succeed);
                    }
                } catch (Exception e) {
                    failedData = (F) "服务器数据格式异常";
                }
            } else {
                // 业务失败,获取服务端提示信息。
                failedData = (F) httpEntity.getMessage();
            }
        } else if (code >= 400 && code < 500) { // 客户端请求不符合服务端要求。
            failedData = (F) "发生未知异常";
        } else if (code >= 500) { // 服务端发生异常。
            failedData = (F) "服务器开小差啦";
        }

        // 包装成SimpleResponse返回。
        return SimpleResponse.<S, F>newBuilder()
                .code(response.code())
                .headers(response.headers())
                .fromCache(fromCache)
                .succeed(succeedData)
                .failed(failedData)
                .build();
    }
}

Converter中代码初看起来比较多,但是一点也无复杂。我们来分析一下解析过程:

  1. 先拿到httpCodehttpBody
  2. 根据httpCode判断客户端是否正常请求,服务端接口是否正常响应
  3. 解析httpBody数据为HttpEntity实体
  4. HttpEntity的业务状态码code判断业务是否成功
  5. 业务成功后解析业务数据中的data为我们真正想要的数据类型
  6. 业务失败后获取服务端设置的提示消息
  7. 对各个解析加一些try-catch的异常兼容处理

封装之后的使用示例

请求JavaBean

JSON数据如下:

{
    "code": 1,
    "data": {
        "name": "Kalle",
        "url": "https://github.com/yanzhenjie/Kalle",
    },
    "message": "Succeed"
}

请求的代码如下:

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<UserInfo>() {
        @Override
        public void onResponse(SimpleResponse<UserInfo> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                UserInfo user = response.succeed();
                ...
            } else {
                Toast.show(response.failed());
            }
        }
    });

请求List

JSON数据如下:

{
    "code": 1,
    "data": [
        {
            "name": "Kalle",
            "url": "https://github.com/yanzhenjie/Kalle",
        },
        {
            "name": "Kalle",
            "url": "https://github.com/yanzhenjie/Kalle",
        },
    ],
    "message": "Succeed."
}

请求的代码如下:

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<List<UserInfo>>() {
        @Override
        public void onResponse(SimpleResponse<List<UserInfo>> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                List<UserInfo> userList = response.succeed();
                ...
            } else {
                Toast.show(response.failed());
            }
        }
    });

请求null数据

JSON数据如下:

{
    "code": 1,
    "data": null,
    "message": "Succeed"
}

请求的代码如下:

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<String>() {
        @Override
        public void onResponse(SimpleResponse<String> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                ...
            } else {
                Toast.show(response.failed());
            }
        }
    });

请求String或者Integer数据

例如返回的业务数据是字符串或者数字时:

{
    "code": 1,
    "data": "20180101",
    "message": "Succeed"
}

请求的代码如下:

Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<String>() {
        @Override
        public void onResponse(SimpleResponse<String> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                String orderId = response.succeed();
            } else {
                Toast.show(response.failed());
            }
        }
    });
Kalle.get("http://www.example.com")
    .perform(new SimpleCallback<Integer>() {
        @Override
        public void onResponse(SimpleResponse<Integer> response) {
            if(response.isSucceed())) { // Http成功,业务也成功。
                int age = response.succeed();
            } else {
                Toast.show(response.failed());
            }
        }
    });

封装Dialog和处理异常

上面已经封装了业务数据的处理,但是还有Dialog和异常没处理。在Kalle中这些都通过Callback来做,CallbackonStart()方法和onEnd()方法可以用来显示和关闭Dialog,onException()方法用来处理异常。

异常是指本地网络错误、Url解析失败、连接服务器超时和读取响应超时等一系列Kalle无法直接处理的异常,这些错误也必须由开发者来处理,因为这属于业务的范畴。

需要自定义一个Callback,用来替换上面例子中使用的SimpleCallback

public abstract class DefineCallback<S> extends Callback<S, String> {

    private Dialog mDialog;

    public DefineCallback(Context context) {
        mDialog = new WaitDialog(context);
    }

    @Override
    public Type getSucceed() {
        // 通过反射获取业务成功的数据类型。
        Type superClass = getClass().getGenericSuperclass();
        return ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    @Override
    public Type getFailed() {
        // 返回失败时的数据类型,String。
        return String.class;
    }

    @Override
    public void onStart() {
        // 请求开始啦,显示Dialog。
        if (mDialog != null && !mDialog.isShowing()) {
            mDialog.show();
        }
    }

    @Override
    public void onException(Exception e) {
        // 发生异常了,回调到onResonse()中。
        String message;
        if (e instanceof NetworkError) {
            message = "网络不可用";
        } else if (e instanceof URLError) {
            message = "Url格式错误";
        } else if (e instanceof HostError) {
            message = "没有找到Url指定服务器";
        } else if (e instanceof ConnectTimeoutError) {
            message = "连接服务器超时,请重试";
        } else if (e instanceof WriteException) {
            message = "发送数据错误,请检查网络";
        } else if (e instanceof ReadTimeoutError) {
            message = "读取服务器数据超时,请检查网络";
        } else if (e instanceof ParseError) {
            message = "解析数据时发生异常";
        } else {
            message = "发生未知异常,请稍后重试";
        }

        SimpleResponse<S, String> response = SimpleResponse.<S, String>newBuilder()
                .failed(message)
                .build();
        onResponse(response);
    }

    @Override
    public void onCancel() {
        // 请求被取消了,如果开发者需要,请自行处理。
    }

    @Override
    public void onEnd() {
        // 请求结束啦,关闭Dialog。
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
    }
}

更多异常相关的信息请参考异常,这里例举了一部分常见的业务场景,开发者可根据自身需求扩展。

results matching ""

    No results matching ""