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

笔者将通过11篇博客对个人开源框架进行讲解,本篇为第7篇,讲解同步请求,公共部分。

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

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



在第一篇中,需求讲解的时候,我们希望得到数据时,已经回到主线程
NetHelper.get("url", new NetSingleBeanListener() {
    @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;

/**
 * 网络请求处理核心类
 * 

* 作者: 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;

/**
 * 网络请求回调核心类
 * 

* 作者: 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;

/**
 * 网络请求工具类
 * 

* 作者: 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;

/**
 * 公用网络逻辑,核心监听器。自定义监听器一般继承这个类
 * 

* 作者: 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(同部请求,分支部分)

你可能感兴趣的:(Android,Java,移动开发)