笔者将通过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);
}
来看看下一个模块,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篇使用框架之后,再回来看看本篇,可能理解起来就更轻松了。
下一篇将讲解公共部分代码实现