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);}
来看看下一个模块,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; }}
本框架不涉及具体加解密的操作,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);}
onReceivedRet里面调用handleResult是父类里面的方法,其将通过中转站,回调到本类的onSuccess(CallbackCode, NetRetBean)方法。
这里我们实现了两个抽象方法,onReceivedRet和onSuccess,同时增加了一个抽象方法。
这里我们实现了两个抽象方法,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
- Android通用网络请求解析框架.2(构造框架)
- Android通用网络请求解析框架.5(使用框架)
- Android通用网络请求解析框架.1(需求,思想)
- Android通用网络请求解析框架.11(总结)
- Android通用网络请求解析框架.9(支持第三方解析框架)
- Android通用网络请求解析框架.7(同步请求,公共部分)
- Android通用网络请求解析框架.8(同步请求,分支部分)
- Android通用网络请求解析框架.6(自定义解析器)
- Android通用网络请求解析框架.3(代码实现,公共部分)
- Android通用网络请求解析框架.4(代码实现,分支部分)
- Android通用网络请求解析框架.10(发现问题,改善)
- Android网络请求框架----Okhttp3完全解析(2),封装框架
- Android网络请求框架
- Android 网络请求 框架
- 【框架】网络请求+Gson解析--Retrofit 2
- Android 通用网络框架封装
- Android网络请求框架—OKHttp 源码解析
- Android网络请求框架Velley的用法与解析
- go插件cobra命令用法
- Android MVP 详解(下)
- windows网络端口占用解决
- 如何用一个糟糕的流程毁掉你的公司
- 欢迎使用CSDN-markdown编辑器
- Android通用网络请求解析框架.2(构造框架)
- ROW_NUMBER()
- SLF4J学习
- 深入理解Java:注解(Annotation)自定义注解入门
- 流程内耗的雾霾几时休?
- JsonObject判断一个json串中是否含有某个key值
- linux grep 输入重定向(待补充)
- Android .9
- imx6单独编译内核make menuconfig 提示'make menuconfig' requires the ncurses libraries.