Android通用网络请求解析框架.7(同步请求,公共部分)

来源:互联网 发布:袁隆平 诺贝尔 知乎 编辑:程序博客网 时间:2024/05/16 07:44
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第7篇,讲解同步请求,公共部分。

开源库github地址 https://github.com/qq296216078/Android-Universial-NetFrame

如果有兴趣一起讨论本框架的内容,请加QQ群:271335749



在第一篇中,需求讲解的时候,我们希望得到数据时,已经回到主线程
NetHelper.get("url", new NetSingleBeanListener<NetUserBean>() {    @Override    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {        // 这里是ui线程    }    @Override    protected void onSuccess(NetUserBean userBean) {        // 这里是ui线程    }});

这种情况,也是最常见的情况,就是我们通常所说的异步请求。
但是在实际项目中,有时候不需要回到主线程,比如下面这种情况
private void startRequest() {    new Thread() {        @Override        public void run() {            request1();            request2();            try {                Thread.sleep(60 * 1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }.start();}

开发者需要一些在后台定时跑的任务,固定一分钟进行几个接口的请求,这些请求在返回时不需要在主线程中有操作,或者开发者想自己用Handler来返回到主线程。

这个时候,也就是通常所说的同步请求,如果继续使用异步请求的代码,是可能出现问题的。
因为异步请求的代码中有些部分是不需要的

1.不需要创建AsyncTask
2.不需要创建Handler

第1点,开发者发起同步请求的时候,必须是自己已经处在子线程中,大家都知道ui线程中进行网络请求是会出错的。

第2点,在第1点的前提下创建的Handler,原先实现的代码是会报错的,通常会是这个错
Can't create handler inside thread that has not called Looper.prepare()

因为在非ui线程中创建的Handler,并不能将消息传送到ui线程,需要加入一些Looper相关的代码才可以。而同步请求不需要将结果返回到ui线程,所以这里的代码也是不需要的。

因为第2点的问题,所以在子线程中进行同步请求时,直接使用异步请求代码,会出现异常。那该怎么办呢?

这里有两个解决方案:
1.在之前的代码的许多地方加上一些参数,在调用的时候逐层传递进去,框架内层根据参数判断要进行哪些操作
2.重新模仿之前的代码,实现一套同步请求的代码

笔者采用了第2种办法。原因也有两点:
1.如果采用第1种办法,框架中的代码将会变得更复杂,提供给开发者使用的入口也会变得复杂。这导致了在编程和使用上都变麻烦了。
2.如果采用第2种办法,并不是所有代码都要重写,只要关键部分进行修改就可以了,与线程操作无关的代码,可以不要修改,所以实现起来也不麻烦。

在这里,我们先不急着实现同步请求的内步代码,我们先假设已经实现了相应代码,直接开始使用。
那么之前的需求,实现起来会是这样的
private void startRequest() {    new Thread() {        @Override        public void run() {            NetHelper.get("url1", new NetStringListener() {                @Override                protected void onSuccess(String string) {                                        }                @Override                protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                }            }            NetHelper.get("url2", new NetStringListener() {                @Override                protected void onSuccess(String string) {                                        }                @Override                protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                }            }    }.start();}

url1和url2的请求是按顺序下来的,也就是说url1必须是请求结束了,要么回调了onSuccess,要么回调了onError,才会开始url2请求。这看起来非常的奇怪,同步执行的代码,看不出来他们的顺序性。再看看这样的需求
private void startRequest() {    new Thread() {        @Override        public void run() {            if (request1()) {                request2();            }        }    }.start();}

url2的请求需要依赖url1请求的结果。如果用NetHelper实现,代码看起来就非常的奇怪了
private void startRequest() {    new Thread() {        @Override        public void run() {            NetHelper.get("url1", new NetStringListener() {                @Override                protected void onSuccess(String string) {                    NetHelper.get("url2", new NetStringListener() {                        @Override                        protected void onSuccess(String string) {                        }                        @Override                        protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                        }                    });                }                @Override                protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                }            });        }    }.start();}

需要嵌套多层,同步执行的代码,因为回调的原因,让开发者看不明白执行顺序。而且回调方法里面的参数,并不能直接给下面的代码使用,必须要用到全局变量才行,回调方法中如果要使用外部的变量,也需要全局变量,或者定义为final的局部变量。明明是按顺序执行的代码,但这代码结构却让人感到非常蛋疼。对于按顺序同步执行的代码,笔者不能忍受用这种回调的方式来实现。

有没有好的办法解决呢?显然是有的,就像上面的代码一样,request1()的请求是有返回值的,而不是通过回调来返回。
那么,如果NetHelper.get方法具有返回值,就好办了,我们就可以这样完成需求
private void startRequest() {    new Thread() {        @Override        public void run() {            boolean ret = NetHelper.get("url1", new NetStringListener() {                @Override                protected void onSuccess(String string) {                                    }                @Override                protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                }            });                        if (ret) {                NetHelper.get("url2", new NetStringListener() {                    @Override                    protected void onSuccess(String string) {                    }                    @Override                    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {                    }                });            }        }    }.start();}

看起来好像是可以,我们甚至可以用NetTempListener,不需要实现回调,让代码更简洁
private void startRequest() {    new Thread() {        @Override        public void run() {            boolean ret = NetHelper.get("url1", new new NetTempListener());            if (ret) {                NetHelper.get("url2", new NetTempListener());            }        }    }.start();}

可是问题又来了,这里的get方法只能返回boolean类型,如果需求是根据url1请求的结果解析后的Bean的某一个属性来判断是否要进行url2请求,那么又要写一个不同返回值的get方法,而且内部逻辑又麻烦了许多。需求都是不断变化的,返回值要根据需求做不同的变化。之前我们做异步请求,将不同的返回结果进行归类,成功和失败时的返回值有不同的回调。而现在一个方法只有一个返回值,我们要怎样在不改代码的情况下,让这个返回值适合各种情况呢?

可能有些人想到了用Map,对,Map是可以,但是Map里面的key值是哪些,取出来后每个value都要强转,确实够麻烦。

有现成的东西为什么不用呢?对了,就是NetRetBean,我们让同步请求时,就返回NetRetBean。
返回NetRetBean的好处我想大家能够明白的,因为这个类中封装了请求成功和失败时所有情况的数据,包括解析后的,还有自定义解析的数据。

拿到NetRetBean返回值后,根据CallbackCode判断请求是否成功,
如果成功,NetRetBean中的外层数据也都不为null
如果成功,NetRetBean的内层数据也不为null
如果失败,可以根据CallbackCode进行判断是哪种原因导致的

在使用上,应该是这样的
private void startRequest() {    new Thread() {        @Override        public void run() {            NetRetBean netRetBean = NetHelper.getSync("url", new NetTempListener());            CallbackCode callbackCode = netRetBean.getCallbackCode();            switch (callbackCode) {                case CODE_SUCCESS_REQUEST:                    String string = (String) netRetBean.getServerObject();                    System.out.println(string);                    break;                case CODE_ERROR_HTTP_NOT_200:                case CODE_ERROR_REQUEST_EXP:                case CODE_ERROR_SERVER_DATA_ERROR:                case CODE_ERROR_JSON_EXP:                case CODE_ERROR_UNKNOWN:                default:                    System.out.println(netRetBean.toString());                    break;            }        }    }.start();}

这里,我们为NetHelper添加了getSync方法,表明使用同步请求。
做的是同步请求,所以强转操作必须开发者自己来进行,因为serverObject是Object类型的。

之前笔者也想过将NetRetBean中serverObject的类型写成泛型,根据传入不同的监听器和泛型,自动转成相应的类型。但是后来因为自定义解析器时返回的数据可能不只有一个,可能是非常多个的,最后还是得用到NetRetBean中的serverObjectMap,所以放弃了将serverObject类型写成泛型的写法。

那么现在,之前的需求,根据url1请求的结果来判断是否进行url2请求,也就变成这样了
private void startRequest() {    new Thread() {        @Override        public void run() {            NetRetBean netRetBean = NetHelper.getSync("url1", new NetTempListener());            if (netRetBean.isSuccess()) {                NetHelper.getSync("url2", new NetTempListener());            }        }    }.start();}

使用上代码好像没有比返回boolean简洁多少,但是NetRetBean好在可以通用,基本上所有返回结果的需求都包含了。

内部代码要如何实现呢?既然要返回值,那就应该逐层进行返回。看看代码,或许就更清楚了。

先从NetHelper入手,异步请求的代码利用NetExcutor进行get或者post操作,然后在返回时回调NetListener。
在这里,我们希望有数据返回,那么,直接在get或者post方法处进行返回,显然是非常合理的
public class NetHelper {    public static NetRetBean getSync(String url, boolean isWaitForToken, NetListener netListener) {        NetExcutor netExcutor = new NetExcutor();        netExcutor.setUrl(url);        netExcutor.setWaitForToken(isWaitForToken);        netExcutor.setNetListener(netListener);        return netExcutor.get();    }}

对比之前的代码,仅仅是让get方法具有返回值而已。

接下来我们实现内部代码。根据之前的分析,在涉及线程相关的代码的地方,不再使用之前的类,而是重新写一套代码。

而NetExcutor类,里面就有AsyncTask,那就把原来的AsyncTask给去掉,换上另一个名字SyncNetExcutor
package com.chenjian.net.core.sync;import com.chenjian.net.bean.NetRetBean;import com.chenjian.net.core.common.RequestType;import com.chenjian.net.request.RequestUtil;import com.chenjian.net.token.TokenUtil;/** * 网络请求处理核心类 * <p> * 作者: ChenJian * 时间: 2016.12.13 17:42 */public class SyncNetExcutor {    /**     * 请求url     */    private String mUrl;    /**     * 请求类型     */    private RequestType mRequestType;    /**     * post请求时的参数     */    private String mParams;    /**     * 是否先等待token请求成功     */    private boolean isWaitForToken;    /**     * 请求后的回调     */    private SyncNetListener mSyncNetListener;    public void setUrl(String url) {        this.mUrl = url;    }    private void setRequestType(RequestType requestType) {        this.mRequestType = requestType;    }    public void setParams(String params) {        this.mParams = params;    }    public void setWaitForToken(boolean waitForToken) {        isWaitForToken = waitForToken;    }    public void setSyncNetListener(SyncNetListener syncNetListener) {        this.mSyncNetListener = syncNetListener;    }    public NetRetBean get() {        setRequestType(RequestType.REQUEST_TYPE_GET);        return startRequest();    }    public NetRetBean post() {        setRequestType(RequestType.REQUEST_TYPE_POST);        return startRequest();    }    /**     * 同步请求,直接返回     *     * @return 返回 NetRetBean     */    private NetRetBean startRequest() {        if (isWaitForToken) {            TokenUtil.waitToken();        }        try {            String result = request();            return mSyncNetListener.sendSuccess(result);        } catch (Exception e) {            e.printStackTrace();            return mSyncNetListener.sendError(e);        }    }    public String getString() {        setRequestType(RequestType.REQUEST_TYPE_GET);        return startRequestString();    }    public String postString() {        setRequestType(RequestType.REQUEST_TYPE_POST);        return startRequestString();    }    /**     * 同步请求,直接返回     *     * @return 返回 String     */    private String startRequestString() {        if (isWaitForToken) {            TokenUtil.waitToken();        }        try {            return request();        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    private String request() throws Exception {        String result = null;        switch (mRequestType) {            case REQUEST_TYPE_GET:                result = RequestUtil.getRequest(mUrl);                break;            case REQUEST_TYPE_POST:                result = RequestUtil.postRequest(mUrl, mParams);                break;            default:                break;        }        return result;    }}

与NetExcutor的代码相比,有以下几处修改:
1.最大的变化就是去掉了AsyncTask,而在get和post两个入口不创建线程直接进行request请求。
2.NetListener换成了SyncNetListener。
3.get和post方法变成有返回值的了,返回值是NetRetBean,其返回值是从SyncNetListener处得来的。
4.增加了getString和postString方法,他返回的是String类型,是网络请求的直接返回结果。

先来看看SyncNetListener
package com.chenjian.net.core.sync;import com.chenjian.net.bean.NetRetBean;/** * 网络请求回调核心类 * <p> * 作者: ChenJian * 时间: 2016.12.13 17:42 */public interface SyncNetListener {    /**     * http请求,数据解密部分,成功     *     * @param result result     * @return NetRetBean     */    NetRetBean sendSuccess(String result);    /**     * http请求,数据解密部分,失败     *     * @param e e     * @return NetRetBean     */    NetRetBean sendError(Exception e);}

只是把之前的返回类型void改成NetRetBean,因为需要返回值嘛

因为内部代码的修改,外部调用也要做修改才行,我们接着来看看NetHelper入口吧
package com.chenjian.net.helper;import com.chenjian.net.bean.NetRetBean;import com.chenjian.net.core.async.NetExcutor;import com.chenjian.net.core.async.NetListener;import com.chenjian.net.core.sync.SyncNetExcutor;import com.chenjian.net.core.sync.SyncNetListener;import com.chenjian.net.data.NetConstants;/** * 网络请求工具类 * <p> * 作者: ChenJian * 时间: 2016.12.14 11:24 */public class NetHelper {    /**     * get同步请求     *     * @param url             url     * @param syncNetListener 监听器     * @return NetRetBean     */    public static NetRetBean getSync(String url, SyncNetListener syncNetListener) {        return getSync(url, NetConstants.defaultWaitForToken, syncNetListener);    }    /**     * get同步请求     *     * @param url             url     * @param isWaitForToken  是否等待token请求成功     * @param syncNetListener 监听器     * @return NetRetBean     */    public static NetRetBean getSync(String url, boolean isWaitForToken, SyncNetListener syncNetListener) {        SyncNetExcutor syncNetExcutor = new SyncNetExcutor();        syncNetExcutor.setUrl(url);        syncNetExcutor.setWaitForToken(isWaitForToken);        syncNetExcutor.setSyncNetListener(syncNetListener);        return syncNetExcutor.get();    }    /**     * post同步请求     *     * @param url             url     * @param params          参数     * @param syncNetListener 监听器     * @return NetRetBean     */    public static NetRetBean postSync(String url, String params, SyncNetListener syncNetListener) {        return postSync(url, params, NetConstants.defaultWaitForToken, syncNetListener);    }    /**     * post同步请求     *     * @param url             url     * @param params          参数     * @param isWaitForToken  是否等待token请求成功     * @param syncNetListener 监听器     * @return NetRetBean     */    public static NetRetBean postSync(String url, String params, boolean isWaitForToken, SyncNetListener syncNetListener) {        SyncNetExcutor syncNetExcutor = new SyncNetExcutor();        syncNetExcutor.setUrl(url);        syncNetExcutor.setParams(params);        syncNetExcutor.setWaitForToken(isWaitForToken);        syncNetExcutor.setSyncNetListener(syncNetListener);        return syncNetExcutor.post();    }    /**     * get同步请求,不设置监听器,直接返回数据给调用者     *     * @param url url     * @return String     */    public static String getStringSync(String url) {        return getStringSync(url, NetConstants.defaultWaitForToken);    }    /**     * get同步请求,不设置监听器,直接返回数据给调用者     *     * @param url            url     * @param isWaitForToken 是否等待token请求成功     * @return String     */    public static String getStringSync(String url, boolean isWaitForToken) {        SyncNetExcutor syncNetExcutor = new SyncNetExcutor();        syncNetExcutor.setUrl(url);        syncNetExcutor.setWaitForToken(isWaitForToken);        return syncNetExcutor.getString();    }    /**     * post同步请求,不设置监听器,直接返回数据给调用者     *     * @param url    url     * @param params 参数     * @return String     */    public static String postStringSync(String url, String params) {        return postStringSync(url, params, NetConstants.defaultWaitForToken);    }    /**     * post同步请求,不设置监听器,直接返回数据给调用者     *     * @param url            url     * @param params         参数     * @param isWaitForToken 是否等待token请求成功     * @return String     */    public static String postStringSync(String url, String params, boolean isWaitForToken) {        SyncNetExcutor syncNetExcutor = new SyncNetExcutor();        syncNetExcutor.setUrl(url);        syncNetExcutor.setParams(params);        syncNetExcutor.setWaitForToken(isWaitForToken);        return syncNetExcutor.postString();    }}

使用起来也就是调用SyncNetExcutor,然后再把返回值给开发者。
getSync和postSync返回类型是NetRetBean,是网络请求解析后的结果。
getStringSync和postStringSync返回的是String类型,是网络请求的直接返回结果。

如果你直接使用getStringSync或者postStringSync,是非常简单的
private void syncGetString() {    new Thread() {        @Override        public void run() {            String getString = NetHelper.getStringSync("url");            System.out.println(getString);            String postString = NetHelper.getStringSync("url");            System.out.println(postString);        }    }.start();}

这两个方法只是简单的返回网络请求的数据,并没有对数据进行任何解析,而且返回的string是可能为null的。

如果我们直接调用NetHelper.getSync,会是这样的
private void startRequest() {    new Thread() {        @Override        public void run() {            NetRetBean netRetBean = NetHelper.getSync("url", new SyncNetListener() {                @Override                public NetRetBean sendSuccess(String result) {                    return null;                }                @Override                public NetRetBean sendError(Exception e) {                    return null;                }            });        }    }.start();}

这里的回调并不是最后执行的地方。我们看看SyncNetExcutor中的部分代码
public NetRetBean get() {    setRequestType(RequestType.REQUEST_TYPE_GET);    return startRequest();}public NetRetBean post() {    setRequestType(RequestType.REQUEST_TYPE_POST);    return startRequest();}/** * 同步请求,直接返回 * * @return 返回 NetRetBean */private NetRetBean startRequest() {    if (isWaitForToken) {        TokenUtil.waitToken();    }    try {        String result = request();        return mSyncNetListener.sendSuccess(result);    } catch (Exception e) {        e.printStackTrace();        return mSyncNetListener.sendError(e);    }}

调用监听器来实现成功和失败时候的处理,而且都要返回一个NetRetBean,然后再把这个NetRetBean返回给上层。
所以最后执行的地方是getSync方法的返回,然后再往下执行,只是请求的过程当中调用了这两个回调中的一个。

从代码结构上,还是不能容忍的,而且,还他还没真正的去解析,还要开发者自己去完成解析和错误处理。
如果在回调的解析方法中直接返回null,那么返回的NetRetBean中的众多属性还是为null。
所以仅仅做到这里,同步请求的代码实现还没完成。解析和错误处理的代码,要在框架内部进行处理才合理。


回忆一下,异步请求时外层解析和错误处理是在NetHandleListener类里面,
而且,同步请求除了AsyncTask外,还有另一个与线程相关的代码,就是Handler,也在NetHandleListener中,
那么NetHandleListener也需要改写。

NetHandleListener继承自NetListener,所以我们实现一个SyncNetHandleListener,继承自SyncNetListener
package com.chenjian.net.listener.sync;import com.chenjian.net.bean.NetRetBean;import com.chenjian.net.core.sync.SyncNetListener;import com.chenjian.net.exp.RequestErrorException;import com.chenjian.net.exp.RespondErrorException;import com.chenjian.net.listener.common.CallbackCode;import org.json.JSONException;import org.json.JSONObject;/** * 公用网络逻辑,核心监听器。自定义监听器一般继承这个类 * <p> * 作者: ChenJian * 时间: 2016.12.15 11:12 */abstract public class SyncNetHandleListener implements SyncNetListener {    /**     * 处理NetRetBean并返回。是一个中转站。本类和其子类都可以调用这个方法     *     * @param netRetBean netRetBean     * @return netRetBean     */    protected NetRetBean handleResult(NetRetBean netRetBean) {        return netRetBean;    }    @Override    public NetRetBean sendSuccess(String result) {        NetRetBean netRetBean = new NetRetBean();        try {            JSONObject jsonObject = new JSONObject(result);            String code = jsonObject.getString("code");            String message = jsonObject.getString("message");            String time = jsonObject.getString("time");            String data = jsonObject.getString("data");            netRetBean.setServerCode(code);            netRetBean.setServerMsg(message);            netRetBean.setServerTime(time);            netRetBean.setServerData(data);            if (code.equals("00001")) {                netRetBean.setCallbackCode(CallbackCode.CODE_SUCCESS_REQUEST);                netRetBean = onReceivedRet(netRetBean);            } else {                netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_SERVER_DATA_ERROR);            }        } catch (JSONException e) {            e.printStackTrace();            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_JSON_EXP);        }        return handleResult(netRetBean);    }    @Override    public NetRetBean sendError(Exception exp) {        exp.printStackTrace();        NetRetBean netRetBean = new NetRetBean();        netRetBean.setException(exp);        try {            throw exp;        } catch (RespondErrorException e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_HTTP_NOT_200);        } catch (RequestErrorException e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_REQUEST_EXP);        } catch (JSONException e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_JSON_EXP);        } catch (Exception e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_UNKNOWN);        }        return handleResult(netRetBean);    }    /**     * 子类根据业务区分,将netRetBean解析成list或者单个实体,或者解析成其它结果     *     * @param netRetBean server返回的数据实体,data字段将在子类中解析     * @return 解析后的netRetBean     * @throws JSONException 解析json异常     */    abstract protected NetRetBean onReceivedRet(NetRetBean netRetBean) throws JSONException;}

与NetHandleListener相比,SyncNetHandleListener显得更加简洁,这里的代码有以下几处改变:
1.最大的变化就是去掉了Handler,因为无需将解析后的结果返回到ui线程
2.由于1的变化,handleResult方法将直接返回NetRetBean,而不是通过Handler把数据传递给ui线程
3.onReceivedRet是具有返回值的,因为sendSuccess里面需要返回结果
4.去掉了onSuccess和onError方法,因为这里不需要再回调,而是直接返回结果

第2点中的handleResult方法,虽然只有一行,但是笔者为了让代码与异步请求的保持相似的风格,还是保留了这个方法。

公共部分的代码,还包括RequestUtil,HttpUtil,还有一些相关的类,这些类的代码都没有做修改。

这个时候,如果我们再调用NetHelper.getSync,使用上SyncNetHandleListener,会是这样的
private void syncGetString() {    new Thread() {        @Override        public void run() {            NetRetBean netRetBean = NetHelper.getSync("url", new SyncNetHandleListener() {                @Override                protected NetRetBean onReceivedRet(NetRetBean netRetBean) throws JSONException {                    return null;                }            });        }    }.start();}

和使用SyncNetListener一样,还是存在代码执行顺序模糊的问题。还有内层数据的解析,需要开发者自己来完成。
如果在onReceivedRet回调的解析方法中直接返回null,那么在返回的NetRetBean的内层数据还是为null。

不过我们把外层的数据在SyncNetHandleListener类里面进行解析了,公共部分的代码实现完成了。
分支部分,即内层解析部分的代码还需要一定的篇幅,所以将其放到下一篇中去讲解。


至此,同步请求公共部分讲解完成。

下一篇,将讲解同步请求分支部分
Android通用网络请求解析框架.8(同部请求,分支部分)

0 0
原创粉丝点击