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

19 篇文章 0 订阅
14 篇文章 0 订阅
笔者将通过11篇博客对个人开源框架进行讲解,本篇为第2篇,讲解构造框架。


如果有兴趣一起讨论本框架的内容,请加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篇使用框架之后,再回来看看本篇,可能理解起来就更轻松了。

下一篇将讲解公共部分代码实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值