简介:
网络框架现在已经很多了,什么github上,opensource,等等开源代码托管网站上,可以找到很多,最基础的httpurlconnection 或者 Apache的 网络开发包已经会用了,我现在有点儿懒 没时间和意识去研究阅读框架源码,只有项目中用到时才会去看。经过这2三年的工作,我在项目中也大致理了一下项目的网络交互部分,这里拿其中一个开发过的app的网络处理部分来写吧。(由于我水平现在有限,瞎比划两下,代码有丑陋和不合理的地方)
当要介绍或者描述一个东西时,如何给别人能清晰的展现出来还时需要一定描写水平的,以前都不在乎这些,慢慢的自己描述的隔了一段时间自己都读不通顺了,工作中有时也会出现因为沟通问题产生不必要的时间开销。我记得刚开始学编程时一个老师说过:先know how ,再know why,这样循序渐进的学比较有收获,所以我觉得要去研究一个技术点或者知识点 首先就是要去熟悉 或者使用,然后在去看原理啊 ,代码之类的。我写的这个网络框架第一要求就是方便调用,因为在公司项目中这样用习惯了,当时也觉得组长封装的挺好的,其次有时间的话性能或者扩展性方面再做的好一点儿吧,工作3年了总得追求点儿高级的。这里分3步来描述这个网络框架吧:
一、网络描述
二、如何使用
三、网络总体描述
四、源码细节
一、网络描述
对于这个简单的网络框架,我这里最原始的目的一是易用,二是能扩展,三是性能改良一下。能做到一就可以了,有时间再考虑其他。
二、使用
由于客户端和服务器端交互 都有特定的数据格式,我这里不是说的xml或者json,而是服务端和客户端约定的数据格式,我们将它称为 协议 ,好多客户端都有自己的协议来和服务端交互,因此 使用时我们可以想到有协议对象。光有协议对象还不行,既然有了协议对象,那就得有发送协议的对象,而一旦有了发送的,那就必须得有接收的,这样以来app和后台交互涉及的必要的 元素就是协议,发送者,接收者。
协议么 就是bean了 ,封装各种字段之类的
发送者么 就是httpurlconnecion 把bean给后台,当然复杂的话可能要请求队列啊,网络缓存啊,多线程发送控制啊,我们先不考虑那么复杂,复杂了脑子疼。这里只简单放一个队列,当把请求放到队列中后 就不用管了,只要相信发送者会把bean发给后台就行。
接收者么 就是我们接收数据的地方,可以UI线程或者其他地方,掐指一算应该是个接口回调。
于是简单的使用方式就出来了:
<span style="font-size:18px;"> //协议对象
Cmdlogin2 cmdlogin = new Cmdlogin2();
//发送者:就是一个请求
Request request = new Request(cmdlogin,
//接收数据者
new UIResponseListener() {
@Override
public void responseExchCallBack(Params params) {
Object resData = params.getResponseData();
Toast.makeText(MainActivity.this, resData.toString(), 0).show();
}
@Override
public void errorExchCallBack(Params params) {
String msg = params.responseErrorMsg;
String url = params.getUrl().toString();
Toast.makeText(MainActivity.this, url+"-"+msg, 0).show();
}
});
NetWorkContainer.getInstance().add(request);</span>
<span style="font-size:18px;">package com.niuzhihua.dao.domain;
import org.json.JSONException;
import com.niuzhihua.core.config.L;
import com.niuzhihua.core.protocol.Params;
public class Cmdlogin2 extends Params{
public Cmdlogin2() {
}
/**
* 打包
*/
@Override
protected Object initsingleProtocol() {
String singleProtocol = "初始化具体发送的数据。。。";
L.i(singleProtocol);
return singleProtocol;
}
/**
* 解析
*/
@Override
public Object unPackDate(byte[] response) {
L.i("解析处理。。。");
responseData = new String(response);
L.i("解析处理完毕。。。");
return responseData;
}
}
</span>
如果再简洁的写的话 ,两串代码 连者写下来就可以了。这里我们只需要记住 核心的类NetWorkContainer就可以了,其他的类都可以根据传参来写出来。 NetWorkContainer可以改为形象,大方的名字。 协议对象CmdLogin2 继承子Params类,继承后会复写必须要的方法,也可以空实现,总之可以根据自己的app的协议需要来实现。
三、网络框架总体描述
1:总体描述其实只要说一下NetWorkContainer就可以了,NetWorkContainer内不持有一个阻塞队列 BlockingQueue,我们在UIthread中 利用NetWorkContainer把请求放到阻塞队列中,然后不断的从这个队列中取出请求.
2:发给后台:拿到请求后 ,把这个请求加到线程池中去处理。
3:拿到结果:groupExecuService.take(); // ExecutorCompletionService
4: 再用Handler返回给UIthread就可以了。这就是大致过程。
这整个过程中,所有的数据 都封装在一个Request对象中。当发请求时,发这个Request对象,当处理请求时,把这个Request对象加到线程池,最后返回的结果数据也在Request对象中。
四、主要类代码:
NetWorkContainer 发送者:
<span style="font-size:18px;">package com.niuzhihua.core.net;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import android.os.Handler;
import android.os.Message;
import com.niuzhihua.core.config.GlobalConfig;
import com.niuzhihua.core.config.L;
import com.niuzhihua.core.protocol.Params;
/**
* 管理网络请求的池子:
* 包含一个存放请求的 队列(BlockingQueue),包含一个线程池(ExecutorService,ExecutorCompletionService),
* 此类是单实例的,当创建实例后开启一个线程,并一直监听队列,当队列中有数据(请求)时,
* 就把这个请求取出,放到线程池中取处理,(注意这个取出请求和处理请求一直在子线程中 ,while(true){1:取 2:处理请求}),
* 当线程池处理完请求后就已经将返回的数据解析到当次请求对象中。所以处理完请求后取出请求对象即可拿到请求对象中的listener对象,
* 这个listener对象就是负责回调UI thread的,然后就可以发送message到Handler,在handler中收到message后回调处理即可。
*
*/
public class NetWorkContainer {
//单实例的容器
private static NetWorkContainer t = new NetWorkContainer();
public static NetWorkContainer getInstance() {
return t;
}
private NetWorkContainer() {
//开启一个 线程 等待并处理 请求。直到主动关闭当前容器
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Request request = blockingQueue.take();
doRequest(request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
L.i("********线程已经启动");
}
// 能拿到一组任务的结果的池子
private final static ExecutorService executorService = Executors
.newFixedThreadPool(GlobalConfig.THREAD_COUNT_IN_POOL);
private final static ExecutorCompletionService<Request> groupExecuService = new ExecutorCompletionService<Request>(
executorService);
/**
* 存放请求的队列
*/
private BlockingQueue<Request> blockingQueue = new ArrayBlockingQueue<Request>(
GlobalConfig.THREAD_COUNT_IN_POOL);
//static int testcount = 0;
public synchronized void add(Request request) {
// 如果队列没有满,则加入本次请求
if (blockingQueue.size() < GlobalConfig.THREAD_COUNT_IN_POOL) {
blockingQueue.offer(request);
// testcount ++;
// L.i("处理的请求数目:####"+testcount);
}
}
Handler handlerResponseCallBack = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Request request = (Request) msg.obj;
if (GlobalConfig.RESPONSE_OK == request.getParams().getResponseCode()) {
request.getListener().responseExchCallBack(request.getParams());
} else {
request.getListener().errorExchCallBack(request.getParams());
}
}
};
/**/
/*
* 处理网络当前网络请求 Request request 封装当前请求的 对象
*/
public void doRequest(Request request) {
doRequestByPool(request);
}
/**
* @param p
* 请求发送的数据
* @param theListener
* 回调处理的实例
*/
private void doRequestByPool(Request request) {
// 创建一个网络任务
// NetWork n = new NetWork(request.getParams(), request.getListener());
NetWork n = new NetWork(request);
// 提交网络任务到线程池
groupExecuService.submit(n);
Future<Request> result;
try {
// 获取线程执行后的结果
result = groupExecuService.take();
Request resultRequestData = result.get();
//e. 消息缓存:通过 Handler 的 obtainMessage 回收 Message 对象,减少 Message 对象的创建开销
//Message msg = new Message();
Message msg =handlerResponseCallBack.obtainMessage();
//在这里,resultRequestData和request 都可以,因为数据都保存在这个对象里了。
msg.obj = resultRequestData;
handlerResponseCallBack.sendMessage(msg);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
public void cancel() {
executorService.shutdown();
}
}
</span>
NetWork :
<span style="font-size:18px;">package com.niuzhihua.core.net;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.concurrent.Callable;
import com.niuzhihua.core.config.GlobalConfig;
import com.niuzhihua.core.config.L;
import com.niuzhihua.core.interfaces.UIResponseListener;
import com.niuzhihua.core.protocol.Params;
/**
* 网络请求类:
* 一、负责执行http请求。由于用的线程池是可以拿到一组线程的结果。所以实现的是Callable接口。
* 不管是Callable接口还是Runnable接口,这个类的核心是发送数据(HttpURLConnection),拿到返回的数据后
* 并解析数据(这个类持有请求对象,请求对象中有一个成员是封装请求参数的对象,这个封装请求参数的对象就有打包数据和解析数据的功能)。
*
* 二、
* 根据是否添加了除url外的请求数据 来确定是get或者post请求</br>
* 只有url的:get请求
* 否则post
*/
public class NetWork implements Callable<Request> {
/**
* 网络请求发送的数据
*/
public Params params;
/**
* 回调接口,用来回调网络请求回来执行的响应方法
*/
UIResponseListener theListener;
Request request ;
public NetWork(Params cmd, UIResponseListener theListener) {
this.params = cmd;
this.theListener = theListener;
}
public NetWork(Request request) {
this.params = request.getParams();
this.theListener = request.getListener();
this.request = request;
}
/**
* post请求
*
* @param url
* 请求地址
* @param content
* 发送的内容
*/
public Request request() {
/*
* String language; String currentLanguage; language =
* Locale.getDefault().getDisplayLanguage(); if (language.indexOf("中文")
* != -1) { currentLanguage = "zh"; } else { currentLanguage = "en"; }
*/
String content = (String) params.getRequestData();
URL rui = null;
HttpURLConnection conn = null;
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
byte[] data = null;
int resCode = 0;
try {
rui = params.getUrl();
// 设置连接属性
conn = (HttpURLConnection) rui.openConnection();
conn.setRequestProperty("Connection", "Keep-Alive");
// 设置超时时间
conn.setConnectTimeout(GlobalConfig.CONNECT_TIME_OUT);
conn.setReadTimeout(GlobalConfig.READ_TIME_OUT);
// 根据是否添加了请求数据来确定是get或者post请求
if (content == null || "".equals(content)) {
conn.setRequestMethod("GET");
L.i("GET");
} else {
conn.setRequestMethod("POST");
L.i("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("Content-type",
"application/octest-stream");
// conn.setRequestProperty("Content-type", "application/json");
conn.setRequestProperty("Content-Length",
String.valueOf(content.getBytes().length));
conn.connect();
OutputStream os = null;
try {
os = conn.getOutputStream();
os.write(content.getBytes());
os.flush();
} catch (Exception e) {
e.printStackTrace();
}finally{
if (os != null) {
os.close();
}
}
}
//对于网络异常情况,返回错误码(已经连接服务)或者本地定义的提示信息(未连接服务)。
resCode = conn.getResponseCode();
L.i("响应码:" + resCode);
params.setResponseCode(resCode);
// 判断HTTPCODE是否正常
if (resCode == HttpURLConnection.HTTP_OK) {
inputStream = conn.getInputStream();
/*
* byteArrayOutputStream = new ByteArrayOutputStream(); int i;
* // 一个个字符读取,2g手机网络一个TCP包较小 while ((i = inputStream.read()) !=
* -1) { byteArrayOutputStream.write(i); } data =
* byteArrayOutputStream.toByteArray();
*/
//2种读取方式
//data = StreamTool.read(inputStream);
data = StreamTool.bufferRead(inputStream).getBytes();
//解析网络返回数据
params.unPackDate(data);
}
} catch (Exception e) {
if(e instanceof SocketTimeoutException){
params.responseErrorMsg = GlobalConfig.RESPONSE_TIME_OUT_MSG;
}else{
params.responseErrorMsg = e.getMessage();
}
} finally {
if (conn != null) {
conn.disconnect();
}
try {
if (inputStream != null)
inputStream.close();
if (byteArrayOutputStream != null)
byteArrayOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return request;
}
/**
* 用来执行 网络操作:包括发送数据,以及返回数据后解析数据等
*/
@Override
public Request call() {
// 发送的请求方式 如果发送的内容为空 则为get请求,否则为post请求
return request();
}
}
</span>
<span style="font-size:18px;">package com.niuzhihua.core.net;
import com.niuzhihua.core.interfaces.UIResponseListener;
import com.niuzhihua.core.protocol.Params;
/**
* 封装一个请求,包括请求数据和回调接口
* 请求数据:封装请求传递的参数,url,协议字段等。
* 回调接口:当前请求也有一个属性,就是处理它的接口,这个接口将服务器返回的数据传递到其他组件(UI)
*/
public class Request {
public Params getParams() {
return params;
}
public void setParams(Params p) {
this.params = p;
}
public UIResponseListener getListener() {
return listener;
}
public void setListener(UIResponseListener listener) {
this.listener = listener;
}
/**
* 封装请求所传递的数据
*/
private Params params;
/**
* 当次请求的回调处理
*/
private UIResponseListener listener ;
public Request(Params p,UIResponseListener listener){
this.params = p;
this.listener = listener;
}
}
</span>
<span style="font-size:18px;">package com.niuzhihua.core.protocol;
import java.net.MalformedURLException;
import java.net.URL;
import com.niuzhihua.core.config.GlobalConfig;
import com.niuzhihua.core.config.L;
/**
* 封装请求的数据和响应的数据和url等。
* url:请求地址
* 请求的数据:发送的字段,可以根据协议规定发送。如json xml 。。。等。
* 响应的数据:有个object变量存放返回的数据。可以再修改,这里之演示。
*/
public abstract class Params implements DataPackUtil{
/**
* 用来组装协议数据,可以根据具体的业务变化,不一定要用这个类,这里只是模拟.
*/
public static StringBuilder packDataTool = new StringBuilder();
public String getDefaultURL(){
if(!isNullURL()){
return url.toString();
}else{
return "";
}
}
/**
* 判断url是否 为空
* @return
*/
public boolean isNullURL(){
if(url!=null && !"".equals(url)){
return true;
}else{
return false;
}
}
private URL url ;
public URL getUrl() {
return url;
}
public void setUrl(URL url) {
this.url = url;
}
/**
* 响应码
*/
private int responseCode =-1;
/**
* 请求数据
*/
private Object requestData ;
public int getResponseCode() {
return responseCode;
}
public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
}
public Object getRequestData() {
return requestData;
}
public void setRequestData(Object requestData) {
this.requestData = requestData;
}
public Object getResponseData() {
return responseData;
}
public void setResponseData(Object responseData) {
this.responseData = responseData;
}
/**
* 响应数据
*/
public Object responseData;
/**
* 响应异常提示
*/
public String responseErrorMsg;
public Params(){
try {
url = new URL(GlobalConfig.url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
//打包一条协议 准备发送
packDataTool.append(packBaseDate()).append(packSingleDate());
setRequestData(packDataTool.toString());
//情空当前协议数据
packDataTool.delete(0, packDataTool.length());
}
/**
* 留给每条协议扩展的 用来打包协议数据的方法
* @return
*/
protected abstract Object initsingleProtocol();
/**
* 初始化 协议的基础数据:协议的共同数据
* @return
*/
@Override
public Object packBaseDate() {
String basedata = "协议共同的基础数据初始化。。。";
L.i("协议共同的基础数据初始化。。。");
return basedata;
}
/**
* 初始化每条具体协议的数据
*/
@Override
public Object packSingleDate() {
return initsingleProtocol();
}
/**
* 解析网络返回的数据
*/
@Override
public Object unPackDate(byte[] responseData) {
L.i("解析处理。。。");
//responseData = parseData(); 在这里根据自己的协议格式来解析
L.i("解析处理完毕。。。");
return responseData;
}
/**
* 解析网络返回的数据
*/
@Override
public Object unPackDate(String responseData) {
L.i("解析处理。。。");
//responseData = parseData();
L.i("解析处理完毕。。。");
return responseData;
}
}
</span>
<span style="font-size:18px;">package com.niuzhihua.core.protocol;
public interface DataPackUtil {
/**
* 打包基础数据
* @return
*/
public Object packBaseDate();
/**
* 打包每条具体协议的数据
* @return
*/
public Object packSingleDate();
/**
* 解析数据
* @return
*/
public Object unPackDate(byte[] responseData);
public Object unPackDate(String responseData);
}
</span>
UIResponseListener: 发送数据后的接收者
<span style="font-size:18px;">package com.niuzhihua.core.interfaces;
import com.niuzhihua.core.protocol.Params;
/**
* 界面回调接口:一个界面activity实现该接口 ,当做网络请求操作时可以实现界面回调
* @author Administrator
*
*/
public interface UIResponseListener {
/**
* 当请求成功响应后回调的方法
* @param theCmd
*/
public void responseExchCallBack(Params params);
/**
* 当请求响应失败后回调的方法
* @param content
*/
public void errorExchCallBack(Params params);
/**
* 网络异常
* @param content
*/
// public void netError(String content);
}</span>
全局设置类:
<span style="font-size:18px;">package com.niuzhihua.core.config;
/**
* 用来存放全局变量。包括软件配置参数等
* @author Administrator
*
*/
public class GlobalConfig {
/**
* 请求后台的地址:客户端向一个地址发送数据,根据发送的数据不同来实现返回的数据不同
*/
public static final String url = "http://www.baidu.com/";
// public static final String url = "http://csdn.com/dd/a.html";
/**
* 网络请求线程池中线程的数量
*/
public static final int THREAD_COUNT_IN_POOL = 3;
/**
* 网络连接超时时间
*/
public static final int CONNECT_TIME_OUT = 15 * 1000;
/**
* 网络数据读取超时时间
*/
public static final int READ_TIME_OUT = 10 * 1000;
/**
* 响应成功
*/
public static final int RESPONSE_OK = 200;
/**
* 响应错误提示语
*/
public static final String RESPONSE_TIME_OUT_MSG = "数据获取超时";
public static boolean isDebug = true;
}
</span>
工具类:
<span style="font-size:18px;">package com.niuzhihua.core.net;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class StreamTool {
/**
* 读取流中的数据
* @param inStream
* @return
* @throws Exception
*/
public static byte[] read(InputStream inStream) throws Exception{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while( (len = inStream.read(buffer)) != -1){
outStream.write(buffer, 0, len);
}
inStream.close();
return outStream.toByteArray();
}
public static String bufferRead(InputStream inputStream) throws Exception{
StringBuilder answer = new StringBuilder();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String inputLine;
while ((inputLine = in.readLine()) != null) {
answer.append(inputLine);
answer.append("\n");
}
in.close();
return answer.toString();
}
}
</span>
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------