Android通用网络请求解析框架.2(构造框架)

来源:互联网 发布:cad电气画图软件 编辑:程序博客网 时间:2024/06/05 05:58
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第2篇,讲解构造框架。

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

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



本篇可能会有部分源码,但依然不是讲解具体源码实现。

上一篇中已经总结出了我们想要的网络请求框架,类似于下面这样
Util<T>.getBean("url") {    onSuccess(T t) {    }    onError(int errorCode, ErrorBean ErrorBean) {    }};

这显然,是在使用者层面的,一般是在Activity里面调用。

对于使用者,我们一般采用单例,或者静态方法。笔者感觉,静态方法在此处更方便,看起来更简洁。

因此,我们的第一个模块,也就是入口模块,应该是一个类里面拥有get和post等静态方法
public class NetHelper {    public static void get(String url) {    }    public static void post(String url, String param) {            }}

在静态方法里面,我们要进行网络请求,解析,回调。
因为内容太多,所以还是不能直接在get和post方法里面直接执行,还得交给另一个类,把他命名为NetExcutor,他将会是一个核心的枢纽。
也因为要回调,所以我们的入口,将会改成下面这样
public class NetHelper {    public static void get(String url, NetListener netListener) {        NetExcutor netExcutor = new NetExcutor();        netExcutor.setUrl(url);        netExcutor.setExcutorListener(listener);        netExcutor.get();    }    public static void post(String url, String params, NetListener netListener) {        NetExcutor netExcutor = new NetExcutor();        netExcutor.setUrl(url);        netExcutor.setParams(params);        netExcutor.setExcutorListener(listener);        netExcutor.post();    }}

NetHelper的工作非常明确,创建一个NetExcutor实例,把参数交给他,让他发起请求,往后需要回调,也是他来做。到这里,入口模块基本完成。


接着,我们开始第二个模块,深入NetExcutor。

在其中,必须有几个属性,url,params,netListener,并且至少要有这些属性的set方法。

更重要的,是要有两个公有方法,get和post,这两个方法中,开启线程,进行请求,并将结果通过listener回调。看看代码
public class NetExcutor {    /**     * 请求url     */    private String mUrl;    /**     * 请求类型     */    private RequestType mRequestType;    /**     * post请求时的参数     */    private String mParams;    /**     * 请求后的回调     */    private NetListener mExcutorListener;    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 setExcutorListener(NetListener listener) {        this.mExcutorListener = listener;    }    public void get() {        setRequestType(RequestType.REQUEST_TYPE_GET);        new NetTask().execute();    }    public void post() {        setRequestType(RequestType.REQUEST_TYPE_POST);        new NetTask().execute();    }    /**     * 网络请求异步任务,android 8 以上系统会自己对异步任务做线程池处理     * <p>     * 此处也可以改成自己去写线程池,让调用者可以配置线程池的相关参数     */    private class NetTask extends AsyncTask<String, Integer, Boolean> {        @Override        protected Boolean doInBackground(String... params) {            try {                String result = request();                mExcutorListener.sendSuccess(result);                return true;            } catch (Exception e) {                e.printStackTrace();                mExcutorListener.sendError(e);                return false;            }        }    }    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;    }}

结构上挺清楚的,保存好set进来的参数,在get和post时去做相应的请求。此处加了一个请求类型,来判断是get还是post。

关键的,此处用了AsyncTask,而不是Thread。可以说我偷懒了,但是正如上面注释所说,android 8 或者以上,系统已经对异步任务做线程池处理,我们打开源码看看
public abstract class AsyncTask<Params, Progress, Result> {    private static final String LOG_TAG = "AsyncTask";    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();    // We want at least 2 threads and at most 4 threads in the core pool,    // preferring to have 1 less than the CPU count to avoid saturating    // the CPU with background work    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;    private static final int KEEP_ALIVE_SECONDS = 30;    private static final ThreadFactory sThreadFactory = new ThreadFactory() {        private final AtomicInteger mCount = new AtomicInteger(1);        public Thread newThread(Runnable r) {            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());        }    };    private static final BlockingQueue<Runnable> sPoolWorkQueue =            new LinkedBlockingQueue<Runnable>(128);

此处只放上部分api24的源码,通过查看源码和注释以及文档,可以发现系统已经做了线程池处理,且一些参数是跟cpu核数相关的,可以说做法非常合理。

笔者并没有重写onPostExecute方法,而采用了AsyncTask和Handler结合使用,Handler下面会讲到。这是为了方便开发者修改成Thread和Handler的方式,那样的话,必须有自己的默认线程池配置,外部必须提供改变线程池配置方法,因为笔者的偷懒,线程池这块,暂时用AsyncTask代替了。其实系统内部AsyncTask也是使用线程和事件的方式,这种做法平常也有用到过,而且笔者在项目中已经使用本框架,并没有发现有什么问题。如果开发者需要,可以自行修改成线程池方式。

监听器的实现非常简单
public interface NetListener {    /**     * http请求,数据解密部分,成功     *     * @param result result     */    void sendSuccess(String result);    /**     * http请求,数据解密部分,失败     *     * @param e e     */    void sendError(Exception e);}

这里提到了数据解密,结合NetExcutor的代码,我们发现实际上http请求是调用RequestUtil类里面的方法,可以推断,数据的加解密是在其中完成。枢纽模块,完成。



来看看下一个模块,RequestUtil,直接上代码
public class RequestUtil {    /**     * get请求     *     * @param url url     * @return 返回请求的结果     * @throws Exception     */    public static String getRequest(String url) throws Exception {        String ret;        String tmp = HttpUtil.get(url);        ret = AesUtil.decryptToString(tmp);        return ret;    }    /**     * post请求     *     * @param url   url     * @param param post请求的参数     * @return 返回请求的结果     * @throws Exception     */    public static String postRequest(String url, String param) throws Exception {        String ret;        String data = AesUtil.encryptToString(param);        String tmp = HttpUtil.post(url, data);        ret = AesUtil.decryptToString(tmp);        return ret;    }}

这里还是没有直接进行http请求,因为在请求前和请求后,通常有其它的业务要处理,比如此处的加解密,由AesUtil完成。
本框架不涉及具体加解密的操作,AesUtil里面的方法也仅仅是将传入的参数原样返回。


由于篇幅,HttpUtil模块此处不做详细介绍,其内部就是通过HttpUrlConnection发起请求并返回数据,过程中有异常随时抛出,具体代码之后的章节会讲到。


到这里,请求的部分基本完成了,请求成功了,就会回调sendSuccess,失败了,就回调sendError。
但如果仅仅做到这个程度,开发者在外层调用的时候,会变成这样
NetHelper.get("url", new NetListener() {    @Override    public void sendSuccess(String result) {                    }    @Override    public void sendError(Exception e) {    } });

这里的result只是最原始的数据,还差了解析。那么,解析应该在哪里完成呢?如果让调用者直接实现,那么在使用的时候还是显得非常麻烦。
而且上一篇中所讲的外层内层数据解析分离的思想,还是没用上。先来回忆一下上一篇所讲的解析分层思想
abstract public class ParseJson {    private void parseJson(String jsonString) throws JSONException {        JSONObject jsonObject = new JSONObject(jsonString);        String code = jsonObject.getString("code");        String message = jsonObject.getString("message");        Object data = parseData(jsonObject.getString("data"));    }    abstract Object parseData(String jsonString) throws JSONException;}



因此,我们需要一个模块,来完成外层解析,很明显,他要继承NetListener
abstract public class NetParseListener implements NetListener {    @Override    public void 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);            onReceivedRet(netRetBean);            onSuccess();        } catch (JSONException e) {            onError();        }    }    @Override    public void sendError(Exception exp) {        exp.printStackTrace();        onError();    }    /**     * 子类根据业务区分,将netRetBean解析成list或者单个实体,或者解析成其它结果     *     * @param netRetBean server返回的数据实体,data字段将在子类中解析     * @throws JSONException 解析json异常     */    abstract protected void onReceivedRet(NetRetBean netRetBean) throws JSONException;    /**     * ui线程中实现这个方法来得到网络请求返回的数据     *     * @param successCode 成功的code     * @param netRetBean  成功的实体bean,data字段已经在子类中解析完成     */    abstract protected void onSuccess(CallbackCode successCode, NetRetBean netRetBean);    /**     * ui线程来处理错误码和错误信息     *     * @param errorCode  错误的code     * @param netRetBean 错误的实体bean,字段可能不完整(完整的话还是error吗?呵呵)     */    abstract protected void onError(CallbackCode errorCode, NetRetBean netRetBean);}

可以很清楚的看出来,在sendSuccess的时候,进行外层解析,然后把内层解析交给onReceivedRet,这个方法由子类根据具体业务来实现。

还有,这里使用到了一个NetRetBean,顾名思义,是返回数据的包装,可以说是一个大杂烩
public class NetRetBean {    /**     * 返回码,具体说明请看{@link com.chenjian.net.listener.common.CallbackCode}     */    private CallbackCode mCallbackCode;    /**     * 请求过程中发生异常,包括请求时,解析时。可能为null     */    private Exception mException;    /**     * 服务端的返回码,是服务端返回的数据中解析“code”字段得到的。可能为null     */    private String mServerCode;    /**     * 服务端返回的消息,是服务端返回的数据中解析“message”字段得到的。可能为null     */    private String mServerMsg;    /**     * 服务端返回的时间,是服务端返回的数据中解析“time”字段得到的。可能为null     */    private String mServerTime;    /**     * 服务端返回的数据,是服务端返回的数据中解析“data”字段得到的。可能为null     */    private String mServerData;    /**     * 服务端返回的数据解析成的bean,是用mServerData字段进行json解析得到的。可能为null     */    private Object mServerObject;}
属性的注释已经说明得很清楚。这里还有个CallbackCode,是一个枚举类型,里面定义了返回码。

这个时候,因为这个类中已经实现了sendSuccess和sendError方法,而且他增加了三个抽象方法,
所以如果直接使用NetParseListener,你就要实现三个抽象方法
NetHelper.get("url", new NetParseListener() {    @Override    protected void onReceivedRet(NetRetBean netRetBean) throws JSONException {                    }    @Override    protected void onSuccess(CallbackCode successCode, NetRetBean netRetBean) {    }    @Override    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {    }});

首先,解析还是没有,onReceivedRet方法里面的netRetBean,只是外层解析了,内层的mServerData字段还没解析,也就是说mServerObject字段还是null。

不急,先来解决一下另一个问题,那就是这三个方法,还是在子线程,他们还没返回到ui线程,要怎么办呢?其实在NetParseListener里面写一个Handler就行了
abstract public class NetHandleListener implements NetListener {    @SuppressLint("HandlerLeak")    private Handler mHandler = new Handler() {        public void handleMessage(Message msg) {            switch (msg.what) {                case 1:                    callbackResult((NetRetBean) msg.obj);                    break;            }        }    };    private void callbackResult(NetRetBean netRetBean) {        switch (netRetBean.getCallbackCode()) {            case CODE_ERROR_SERVER_DATA_ERROR:            case CODE_ERROR_JSON_EXP:            case CODE_ERROR_UNKNOWN:                onError(netRetBean.getCallbackCode(), netRetBean);                break;            case CODE_SUCCESS_REQUEST:            case CODE_SUCCESS_LOCAL:                onSuccess(netRetBean.getCallbackCode(), netRetBean);                break;            default:                break;        }    }    /**     * 将非ui线程处理结果传到ui线程去。是一个中转站。本类和其子类都可以调用这个方法     *     * @param netRetBean 要返回给ui线程的实体     */    protected void handleResult(NetRetBean netRetBean) {        Message msg = Message.obtain();        msg.what = 1;        msg.obj = netRetBean;        mHandler.sendMessage(msg);    }    @Override    public void 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);                onReceivedRet(netRetBean);                return;            } else {                netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_SERVER_DATA_ERROR);            }        } catch (JSONException e) {            e.printStackTrace();            netRetBean.setCallbackCode(CODE_ERROR_JSON_EXP);        }        handleResult(netRetBean);    }    @Override    public void sendError(Exception exp) {        exp.printStackTrace();        NetRetBean netRetBean = new NetRetBean();        netRetBean.setException(exp);        try {            throw exp;        } catch (JSONException e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_JSON_EXP);        } catch (Exception e) {            netRetBean.setCallbackCode(CallbackCode.CODE_ERROR_UNKNOWN);        }        handleResult(netRetBean);    }        abstract protected void onReceivedRet(NetRetBean netRetBean) throws JSONException;        abstract protected void onSuccess(CallbackCode successCode, NetRetBean netRetBean);        abstract protected void onError(CallbackCode errorCode, NetRetBean netRetBean);}

此时不是直接调用onError和onSuccess,而是通过中转站,handleResult方法,来转给ui线程。这样,开发者进行使用的时候,回调已经是在ui线程了。
此处也看出了,Handler的代码,只写了一次。这个类,名字也改成了NetHandleListener。

接下来,去完成分层解析的内层,内层解析还是不能直接让开发者来实现,应该进行包装。用同样的思想,我们再继承NetHandleListener
abstract public class NetSingleBeanListener extends NetHandleListener {    @Override    protected void onReceivedRet(NetRetBean netRetBean) throws JSONException {        JSONObject object = new JSONObject(netRetBean.getServerData());        Bean bean = BeanUtil.parseItem(object);        netRetBean.setServerObject(bean);        handleResult(netRetBean);    }    @SuppressWarnings("unchecked")    @Override    protected void onSuccess(CallbackCode successCode, NetRetBean netRetBean) {        onSuccess((Bean) netRetBean.getServerObject());    }    abstract protected void onSuccess(Bean bean);}

NetSingleBeanListener,顾名思义,是解析单个Bean,在其中,使用了BeanUtil去解析,BeanUtil里面,可以使用gson,fastjson,也可以自己解析。
onReceivedRet里面调用handleResult是父类里面的方法,其将通过中转站,回调到本类的onSuccess(CallbackCode, NetRetBean)方法。
这里我们实现了两个抽象方法,onReceivedRet和onSuccess,同时增加了一个抽象方法。
这样,开发者在使用的时候,只要重写onError和onSuccess(Bean bean)方法就行了
NetHelper.get("url", new NetSingleBeanListener() {    @Override    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {    }    @Override    protected void onSuccess(Bean bean) {                    }});

可是大问题出现了,NetSingleBeanListener怎么知道你要解析哪个Bean呢?

你应该想起来了,就是上一篇所说的:泛型

泛型要怎么传入呢?我想方法不只有一种,可以将其当成方法的参数,也可以直接写在类上。笔者采用了后面一种做法
经过修改后,NetSingleBeanListener,就会是这样
abstract public class NetSingleBeanListener<T> extends NetHandleListener {    @Override    protected void onReceivedRet(NetRetBean netRetBean) throws JSONException {        JSONObject object = new JSONObject(netRetBean.getServerData());        T t = NetBeanUtil.parseItem(object);        netRetBean.setServerObject(t);        handleResult(netRetBean);    }    @SuppressWarnings("unchecked")    @Override    protected void onSuccess(CallbackCode successCode, NetRetBean netRetBean) {        onSuccess((T) netRetBean.getServerObject());    }    /**     * 运行在ui线程,返回单个实体     *     * @param t 当前bean     */    abstract protected void onSuccess(T t);}

开发者在调用的时候,传入具体类型,onSuccess里面,就会回调具体类型
NetHelper.get("url", new NetSingleBeanListener<NetUserBean>() {    @Override    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {    }    @Override    protected void onSuccess(NetUserBean netUserBean) {    }});

可以说到这里,已经实现得相当完美了,你传入什么类型的Bean,回调的时候,参数就是什么类型的。
但是在NetSingleBeanListener里,那个NetBeanUtil,是怎么解析Bean的?传入的Bean仅仅是一个泛型T,要怎么把JsonObject解析成T实体?
想当然的,我实现了以下的代码
public class NetBeanUtil {    public static <T> T parseItem(JSONObject jsonObject) throws JSONException {        T t = new T();        t.initByJson(jsonObject);        return t;    }}

此段代码有两个错,
1.  T t = new T(); 这行代码并不能编译通过,java不允许编译时期对泛型用new来创建实例。
2.  T 是未知类型,他哪里来的initByJson方法?

解决第1个问题,只能通过反射的方式来解决了。像第三方解析框架,gson,fastjson,也用到了同样的方法
private static <T> T getBean(Class aClass) {    Class<T> entityClass = (Class<T>) ((ParameterizedType) aClass.getGenericSuperclass()).getActualTypeArguments()[0];    T entity = null;    try {        // 使用newInstance创建实例的类,必须有无参构造方法        entity = entityClass.newInstance();    } catch (InstantiationException e) {        e.printStackTrace();    } catch (IllegalAccessException e) {        e.printStackTrace();    }    return entity;}

这段代码还是得解释一下,把aClass父类取出来,准确的说是返回Type类型,强转成ParameterizedType类型,再取出泛型参数数组的第0个,也就是泛型的Class对象。
试想一下,如果aClass传入的是NetSingleBeanListener的子类,那么entityClass不就是泛型T了吗。
Class类拿到了,就可以使用反射了,可以获取他的方法,也可以创建实例,此处用了newInstance来创建实体
注意:使用newInstance创建实例的类,必须有相应的构造方法,此处用的是无参构造方法。
这样,第一个问题,就解决了。

第2个问题,为了让某些类都拥有一个方法,在Java中,你肯定也用过,而且一直在用,那就是继承。
在Java的泛型中,也可以用继承,再次感谢这个伟大的创举。
就像你自定义了BaseAdapter,你要实现多个方法,比如getView方法。

那我们,也要定义一个带有initByJson的类
abstract public class NetBaseBean {    /**     * 子类实现这个方法,在其中解析json     *     * @param jsonObject 将要解析的JsonObject     * @throws JSONException     */    abstract public void initByJson(JSONObject jsonObject) throws JSONException;}

定义成抽象类,子类继承时必须实现这个方法。因此我们的NetBeanUtil就可以修改为下面这样
public class NetBaseBeanUtil {    public static <T extends NetBaseBean> T parseItem(Class aClass, JSONObject jsonObject) throws JSONException {        T t = getBean(aClass, tIndex);        t.initByJson(jsonObject);        return t;    }        @SuppressWarnings("unchecked")    private static <T extends NetBaseBean> T getBean(Class aClass) {        Class<T> entityClass = (Class<T>) ((ParameterizedType) aClass.getGenericSuperclass()).getActualTypeArguments()[0];        T entity = null;        try {            // 使用newInstance创建实例的类,必须有无参构造方法            entity = entityClass.newInstance();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        return entity;    }}

这样,在外部调用的时候,你传入的泛型Bean,是继承NetBaseBean的就行了。
只是你的Bean要实现initByJson方法,在此方法里面,你可以选择android自带的api来解析,也可以使用gson,或者fastjson来解析。

最后一起来看一下,使用本框架,完成一个网络请求,你将要做哪些内容。
假设服务端返回的数据是这样的:
{    "code":"00001",    "message":"login success",    "time":"1479807260",    "data":{        "id":"123",        "name":"chenjian"    }}

data里面放着是用户数据,你需要定义一个Bean,他继承NetBaseBean
public class NetUserBean extends NetBaseBean {    private String id;    private String name;    @Override    public void initByJson(JSONObject jsonObject) throws JSONException {        this.id = jsonObject.optString("id");        this.name = jsonObject.optString("name");    }}
initByJson方法中,我们用的是api里面的类来解析,而没用gson和fastjson等框架,当然你也可以使用。

再来看看使用
NetHelper.get("url", new NetSingleBeanListener<NetUserBean>() {    @Override    protected void onError(CallbackCode errorCode, NetRetBean netRetBean) {        // 这里是ui线程    }    @Override    protected void onSuccess(NetUserBean userBean) {        // 这里是ui线程    }});

可以看到,完成一个网络请求解析,代码省了很多,而且结构性也比较好。
但最为关键的是,如果你有其它的请求,返回的也是单个Bean的,你只需要完成以下两个操作:
1.定义一个Bean继承NetBaseBean
2.在重写的initByJson方法里面对Bean进行解析
你就可以使用NetHelper和NetSingleBeanListener配合着进行网络请求了。

当然,你可能也需要NetListBeanListener,NetCustomBeanListener,NetStringListener。由于篇幅,这些内容将放到后面的章节讲解。

讲到这里,已经把整个框架的流程梳理了一遍,因为不是完整代码实现,所以读者可能会有不明确的地方。但读者可以继续阅读后面的3、4两篇代码实现,第5篇使用框架之后,再回来看看本篇,可能理解起来就更轻松了。

下一篇将讲解公共部分代码实现
Android通用网络请求解析框架.3(代码实现,公共部分)
0 0