android http——网络请求二次封装的框架设计

android app中少不了要用到网络请求。网上已经有很多优秀的网络请求框架,谷歌推荐的okhttp、齐射并发的volley 、异步请求的asynchttp。

但是我要说的并不是重新设计网络请求框架,而是基于所有优秀网络请求框架进行的二次封装,成为一个为自己项目所用的网络请求。

不要问为什么这么做,只要你使用到了网络请求,不管有没有用第三方请求框架都必须为它(网络请求流程处理)再次设计一个简单易用的框架,我们把这个过程叫做网络请求的二次封装。

先来看看一个网络请求的流程:
1,get,post请求数据
请求数据需要url,如果是post请求还需要一个map集合在存储请求参数;
那么就可以把请求数据看作:

String url="http://192.168.1.1:8080/websservice/index/selectindex.action?";
        Map<String,Object> parameters=new HashMap();
        parameters.put("userinfo.name","admin");
        parameters.put("userinfo.password","admins1");

这么一个数据结构。

2,发出请求

private void sendPostRequest(String url, Map params) {
        //首先判断是否有网络
        if (!NetWorkUtil.isNetworkConnected(mContext)) return;
        HttpClient.post(Context, url, params, new HttpBack() {

            @Override
            public void onSuccess(int statusCode, byte[] responseBody) {
                try {
                    String jsonSuccess = new String(responseBody, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(int statusCode, byte[] responseBody, Message error) {
                try {
                    String jsonFinal = new String(responseBody, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

这段代码只是模拟一个post网络请求,并且经过一系列的处理返回成功或者失败到相应的接口。

3,处理结果
拿到结果后 我们一般会根据服务器返回的固定字段来解析json数据:

    //状态码
    String TAG_STATUS_CODE = "statusCode";

    //请求失败后的失败内容
    String TAG_MESSAGE = "message";

    //json对象
    String TAG_OBJECT = "object";

    //json集合
    String TAG_OBJECTS = "objects";

通过和服务器返回数据相匹配来解析json并保存到相应的实体类中。

4,显示结果
当我们拿到处理后的结果后要发送给相应的页面用于显示。这时候的处理就“八仙过海各显神通”了。不过最常见的就是通过观察者模式,让结果进入发送者内,然后给所有观察者发送网络请求的处理结果。

这就是一个网络请求的大致流程。其中具体的网络请求网上就有很多优秀的处理框架了。我们也没有必要再去自己写,况且自己写的不一定完美。

所以,我们要做的就是既要用别人的框架也要将别人的框架和自己app的耦合性降到最低。

那么我们应该从什么地方入手呢?
先看一张图:
android http——网络请求二次封装的框架设计_第1张图片

这就是大概的一个接口对接的示意图。
而我们要做的部分就是将红色框,即app内部请求和解析进行二次封装。

—————————————————分割线———————————————–

那么我要说的就是为网络请求流程处理设计的框架。

先来看一张我要讲的网络请求二次封装的框架结构示意图:
android http——网络请求二次封装的框架设计_第2张图片

得到处理过的结果的ResponseResult类(也就是图中的ResponseSuccess等类)可以使用观察者模式向所有的观察者发出请求返回结果。

接下来我们就一步一步的实现这个二次封装的框架。等到成品出来后,各位看客就能感受到它的简单和易用是多么的强大。

1,准备工作

/**
 * 请求的url
 */
public class RequestUrls {

    public static final String IP = "http://192.168.1.1:8080/";//url
    public static final String ROOT_URL = IP + "HttpService/";//项目名

    /**
     * 获取首页数据的URL
     */
    public static final String GET_INDEX_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action";

}
/**
 * 服务器返回状态吗
 */
public class StatusCode {
    /**
     * 状态码:请求成功
     */
    public static final int REQUEST_SUCCESS = 200;

    /**
     * 状态码:没有更多数据
     */
    public static final int NOT_MORE_DATA = 201;

    /**
     * 状态码:服务器繁忙
     */
    public static final int SERVER_BUSY = 300;

    /**
     * 状态码:未知错误
     */
    public static final int SERVER_ERROR = 500;
}

还有MD5Util、Base64、NetWorkUtil;
这没有什么可说的。

2,生成默认参数和参数map实例

一般来说,一个网络请求必须带的几个参数:
1,app key;服务器与前端使用同一个名字来匹配。

2,时间戳;目的是为了控制这个请求在一个时间范围内有效。例如:两分钟。如果超过了两分钟,还在用这个请求请求服务器那就不会成功。

3,接口版本号;升级专用。前端app升级后为了兼容后台接口而定制的。

4,sign(md5加密);这个加密方式每个人有每个人的做法。我的做法是将所有的请求参数加起来生成一个md5密文然后后台拿到我的请求参数后同样的方法生成一个md5密文然后匹配。

这些参数应该和服务器一起去协商定义。
那么有了这些写死的,每次请求都要有的参数我们就可以提前实例化一个map先把他们存起来。

/**
 * 生成默认参数和参数map实例
 */
public class SignUtils {

    public static final String KEY_SIGN = "sign";

    public static final String KEY_PRIVATE = "key";

    public static final String KEY_TIMESTAMP = "timestamp";

    public static final String KEY_VERSION = "version";

    public static String getSignMD5(Map param) {
        Collection keySet = param.keySet();
        List list = new ArrayList<>(keySet);

        //对key键值按字典升序排序
        Collections.sort(list);
        String paramStr = "";
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(KEY_SIGN)) continue;
            paramStr += list.get(i) + "=" + param.get(list.get(i)) + "&";
        }
        if (!TextUtils.isEmpty(paramStr))
            paramStr = paramStr.substring(0, paramStr.length() - 1);
        return MD5.MD5(paramStr);
    }

    public static Map getParameters() {
        Map params = new HashMap<>();
        params.put(KEY_PRIVATE, "alsfoxShop_plat");
        params.put(KEY_VERSION,"1.01");
        params.put(KEY_TIMESTAMP, System.currentTimeMillis());
        return params;
    }
}

3,定义网络请求方法接口

/**
 * 该接口规定所有请求所需的常量和方法
 */
public interface IRequest {

    //状态码
    String TAG_STATUS_CODE = "statusCode";

    //请求失败后的失败内容
    String TAG_MESSAGE = "message";

    //json对象
    String TAG_OBJECT = "object";

    //json集合
    String TAG_OBJECTS = "objects";

    //默认超时时间
    int VALUE_DEFAULT_TIME_OUT = 20 * 1000;

    /**
     * 发送get请求
     */
    void sendGetRequest(RequestAction action);

    /**
     * 发送post请求,包含多文件上传方式的传文件
     *
     * @param action 请求对象
     */
    void sendPostRequest(RequestAction action);

    /**
     * 取消所有请求,可能中断请求
     */
    void cancelAllRequests(boolean mayInterruptIfRunning);

    /**
     * 重新设置请求超时时间
     */
    void setTimeOut(int value);

    /**
     * 下载文件
     */
    void downloadFile(String url);
}

一般也就这些方法,如果有更多方法也没关系。
这个接口先放在这里,暂且最后在实现具体的方法。

4,请求参数封装

上面我说到,请求的参数无非包含rul还有参数的map集合。
这里我们也做一下封装。

/**
 * 外界将请求的地址,请求结果的解析参照对象以及请求参数都保存到这个类中
 */
public class RequestContent {

    /**
     * 发送请求的路径
     */
    private String requestUrl;

    /**
     * 请求结果的解析参照对象(服务器返回的数据类型)
     */
    private Class cls;

    /**
     * 请求参数,new出一个map对象,将默认的先添加进去
     */
    private Map parameters = SignUtils.getParameters();

    /**
     * 外界传入参数的构造方法,没有返回结果的请求
     */
    public RequestContent(String requestUrl) {
        this.requestUrl = requestUrl;
    }

    /**
     * 外界传入参数的构造方法,带返回结果的请求,需要传入一个对象依照它来解析数据
     */
    public RequestContent(String requestUrl, Class cls) {
        this.requestUrl = requestUrl;
        this.cls = cls;
    }
//还有变量的get/set方法,这里就不贴出来了
……
}

需要注意的是,请求参数的封装中貌似多了一个class参数。其实这个参数也算是请求参数的一个。
这个参数的作用是当服务器返回了成功后的结果,json数据解析能够更具这个类来解析。

后面的代码就可以看出它的作用。

5,返回结果封装

/**
 * 返回结果抽象类,工厂方法模式
 */
public abstract class ResponseResult {
    //请求对象
    protected RequestAction requestAction;
    //状态码
    protected int statusCode;
}

抽象类需要三个方法继承。成功,失败和所有。类似于try/catch/finally方法。

/**
 * 成功得到服务器结果后,所有的参数进入这个方法;
 */
public class ResponseSuccess extends ResponseResult {

    //服务器返回的内容
    private Object content;

    public Object getContent() {
        return content;
    }

    public void setContent(Object content) {
        this.content = content;
    }

}
/**
 * 失败得到服务器结果后,所有的参数进入这个方法
 */
public class ResponseFailure extends ResponseResult {

    private String message;//错误信息

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
/**
 * 不管服务器的结果是成功还是失败,所有的参数进入这个方法
 */

public class ResponseComplete extends ResponseResult {

    /**
     * 不管成功或者失败都会进入的方法
     * 类似于try/catch方法中的finally方法
     */

    //服务器返回的内容
    private Object content;
    private String message;//错误信息

    public Object getContent() {
        return content;
    }

    public void setContent(Object content) {
        this.content = content;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

这样一来,
成功的请求进ResponseSuccess;这个类中就包含了:对象,状态码和内容;
失败的请求进ResponseFailure;这个类中就包含了:对象,状态码和错误信息;
不管失败和成功都要进ResponseComplete;这个类中就包含了:对象,状态码、内容和错误信息;当然,它的参数会因为状态不同而出现null。

至于抽象类中的RequestAction 则是接下来要讲的。

6,发出请求的枚举


/**
 * 发出请求的枚举
 */
public enum RequestAction {

    /**
     * 枚举中实例化一个请求对象,并传入请求的url,如果该请求有返回值则还需要传入解析参照对象
     */
    GET_INDEX_CONTENT(new RequestContent(RequestUrls.GET_INDEX_CONTENT_URL, String.class)),;
    /**
     * 构造器中传入请求对象
     */
    public RequestContent requestContent;

    RequestAction(RequestContent requestContent) {
        this.requestContent = requestContent;
    }
}

通过带有构造器的枚举在每一个枚举中都实例化一个请求内容(RequestContent )对象。
这样,当我们调用每一个枚举的时候都会得到一个RequestContent的对象,于此同时也就得到了RequestContent对象中的请求参数map集合实例(getParameters())

有了请求参数实例我们就可以不断的往里面添加参数。这样做的目的是把填写url,给json数据一个解析参照对象(String.class)以及实例化一个参数集合的工作都交给枚举来完成。

这样,我们只需要调用枚举,传入url和数据解析参照对象的class就能够得到参数集合,就能够发出请求。

7,继承网络请求接口的具体实现

/**
 * 具体的请求方法
 */
public class Requester implements IRequest {

    //因为这里继承了接口所以不能通过枚举来实现单例了
    private static volatile IRequest instance = null;

    public synchronized static IRequest getInstance() {
        if (instance == null) {
            synchronized (IRequest.class) {
                if (instance == null) {
                    instance = new Requester();
                }
            }
        }
        return instance;
    }

    private EventBus eventBus;
    private Gson gson;

    private Requester() {
        //eventbus是一个三方工具库,可以代替观察者模式发送网络请求返回结果。如果不会用这个工具库,可以百度,或者自己换成可以处理返回结果的代码;例如接口,例如观察者模式
        eventBus = EventBus.getDefault();
        //gson是一个三方json数据解析工具库。
        gson = new Gson();
    }

    //解析成功的参数
    private ResponseResult createResponseSuccess(int statusCode, String response, RequestAction requestAction) {
        ResponseSuccess responseSuccess = null;
        try {
            JSONObject jsonObject = new JSONObject(response);
            responseSuccess = new ResponseSuccess();
            responseSuccess.setStatusCode(statusCode);
            responseSuccess.setRequestAction(requestAction);
            if (requestAction.getClass() == null) return responseSuccess;
            if (jsonObject.has(TAG_OBJECT)) {//如果json数据中包含object,说明是一个对象
                Object object = gson.fromJson(jsonObject.getString(TAG_OBJECT), requestAction.getClass());//这里用到的是gson的解析数据方法
                responseSuccess.setContent(object);
            } else if (jsonObject.has(TAG_OBJECTS)) {//如果json数据中包含objects,说明是一个对象集合
                JSONArray array = jsonObject.getJSONArray(TAG_OBJECTS);
                List data = new ArrayList<>();
                for (int i = 0; i < array.length(); i++) {
                    data.add(gson.fromJson(array.getString(i), requestAction.getClass()));
                }
                responseSuccess.setContent(data);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return responseSuccess;
    }

    //解析失败的参数
    private ResponseResult createResponseFailure(int statusCode, String message, RequestAction requestAction) {
        ResponseFailure responseFailure = new ResponseFailure();
        responseFailure.setStatusCode(statusCode);
        responseFailure.setMessage(message);
        responseFailure.setRequestAction(requestAction);
        return responseFailure;
    }

    //不管成功还是失败都进该方法进行解析
    private ResponseResult createResponseComplete(int statusCode, String responseString, RequestAction requestAction) {
        ResponseComplete responseComplete = new ResponseComplete();
        responseComplete.setStatusCode(statusCode);
        responseComplete.setMessage(responseString);
        responseComplete.setRequestAction(requestAction);
        return responseComplete;
    }

    /**
     * post请求
     */
    @Override
    public void sendPostRequest(final RequestAction action) {
        if (!NetWorkUtil.isNetworkConnected(UIAppliaction.getInstance())) return;
        if (action.requestContent.getParameters() == null) {
            HttpClient.get(action.requestContent.getRequestUrl(), httpBackState(action));
        } else {
            HttpClient.post(action.requestContent.getRequestUrl(), action.requestContent.getParameters(), httpBackState(action));
        }
    }

    //服务器返回结果处理
    private void httpBackState(final RequestAction action) {
        //该方法是网络请求框架中的回调方法,这里只是模拟代码
        new HttpBack() {
            @Override
            public void onSuccess(int statusCode, byte[] responseBody) {
                try {
                    String jsonSuccess = new String(responseBody, "UTF-8");
                    ResponseResult success = createResponseSuccess(statusCode, jsonSuccess, action);
                    ResponseResult complete = createResponseComplete(statusCode, null, action);
                    //给发送了网络请求的类返回请求结果
                    eventBus.post(success);
                    eventBus.post(complete);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(int statusCode, byte[] responseBody, String error) {
                try {
                    ResponseResult failure = createResponseFailure(statusCode, error, action);
                    ResponseResult complete = createResponseComplete(statusCode, error, action);
                    //给发送了网络请求的类返回请求结果
                    eventBus.post(failure);
                    eventBus.post(complete);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }

    /**
     * get请求
     */
    @Override
    public void sendGetRequest(final RequestAction action) {
        sendPostRequest(action);
    }

    /**
     * 取消所有请求
     *
     * @param mayInterruptIfRunning
     */
    @Override
    public void cancelAllRequests(boolean mayInterruptIfRunning) {
        HttpClient.cancelAllRequests(mayInterruptIfRunning);
    }

    /**
     * 设置超时时间
     *
     * @param value 毫秒
     */
    @Override
    public void setTimeOut(int value) {
        HttpClient.setOutTime(value);
    }

    /**
     * 下载文件
     *
     * @param url
     */
    @Override
    public void downloadFile(String url) {
        //模拟下载文件
        HttpClient.downloadFile(url, new HttpFileBack() {
            @Override
            public void onStart(int statusCode, byte[] responseBody) {

            }

            @Override
            public void onOngoing(int statusCode, byte[] responseBody, double schedule) {

            }

            @Override
            public void onError(int statusCode, byte[] responseBody, String message) {

            }

        });
    }
} 
  

其中HttpClient是模拟代码。它指你使用的一切网络请求框架。

因为框架的设计无法用具体代码表达出来,所以这里我选择使用模拟逻辑处理过程。

到这里,整个框架的设计就完成了。那么我们应该怎么去使用呢?
使用网络请求:

 //使用方法
    public void test() {
     //实例化请求方法
        IRequest request = Requester.getInstance();
        //post请求

        //通过枚举获得请求参数的map实例
        Map params = RequestAction.GET_INDEX_CONTENT.requestContent.getParameters();
        //添加参数
        params.put("userinfo.name", "admin");
        params.put("userinfo.pwd", "admins1");
        //发送网络请求
        request.sendPostRequest(RequestAction.GET_INDEX_CONTENT);

        //get请求
        request.sendGetRequest(RequestAction.GET_INDEX_CONTENT);
    }

不管你在哪里提交网络请求,都只需要这么几行代码就搞定。

那么我们再来看如何添加新的rul:
首先在RequestUrls类中添加一个url:

 public static final String GET_USER_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action";

然后在RequestAction枚举中添加一个新枚举:

GET_USER_CONTENT(new RequestContent(RequestUrls.GET_USER_CONTENT_URL, UserInfoBean.class)),

这样,添加一个新的请求就完成了。

不管是提交请求,还是添加新请求都是几行代码就解决的问题。

那么我们如何去接收返回结果呢?
不管你是用的是eventbus.jar还是接口还是观察者模式得到返回结果后都可以这样处理:

 //成功
    public void onRequestSuccess(ResponseSuccess success) {
        switch (success.getRequestAction()) {
            case GET_INDEX_CONTENT:
                success.getContent();//内容
                success.getStatusCode();//返回码
                success.getRequestAction();//请求枚举
                success.getClass();//参照的解析类型
                break;
        }
    }

    //失败
    public void onRequestFailure(ResponseFailure failure) {
        switch (failure.getRequestAction()) {
            case GET_INDEX_CONTENT:
                failure.getMessage();//错误信息
                failure.getStatusCode();//返回码
                failure.getRequestAction();//请求枚举
                failure.getClass();//参照的解析类型
                break;
        }
    }

    //所有
    public void onRequestCompleted(ResponseComplete complete) {
        switch (complete.getRequestAction()) {
            case GET_INDEX_CONTENT:
                //注意:解析的内容可能为null,因为成功或者失败都会进来
                complete.getContent();//内容,可能为null
                complete.getMessage();//错误信息,可能为null
                complete.getStatusCode();//返回码
                complete.getRequestAction();//请求枚举
                complete.getClass();//参照的解析类型
                break;
        }
    }

拿到结果后就可以自己处理一些逻辑了。

整个二次封装的流程就是这样。
这个二次封装的框架目的在于简化请求接收代码,并且留下接口(onSuccess,onFailure)以供第三方框架使用。

现在再返回来看这张图:
android http——网络请求二次封装的框架设计_第3张图片
我们二次封装网络请求的目的就是做红色的框所做的工作。并且把整个网络请求流程分开来,解耦。

就像几个楔形:服务器<第三方框架<二次封装框架<数据请求
这样才能构成一个完整的app网络请求流程。而且这样分为几个楔形,成功的达到了解耦的作用。

这样,不管你换哪个第三方请求框架,只要把第三方请求框架的onSuccess()和onFailure()方法给接上去就可以照常使用

这也是二次封装网络请求而设计框架的最重要的目的。

这里写成博客以供大家前来学习。

你可能感兴趣的:(android http——网络请求二次封装的框架设计)