参考:https://github.com/hehonghui/simple_net_framework
一、了解整个框架的布局
结构图:
关系分析:
①、Request类
作用:设置请求数据(url,header,body等),解析返回的数据,并设置数据完成接口
②、RequestQueen类
作用:启动与关闭执行线程、并能够添加Request到队列中。
③、NetWorkThread类
作用:设置异步环境,在此执行网络数据交互,并分发完成后的数据。
④、HttpStack类
作用:网络交互的真正执行类。
⑤、ResponseDelivery
作用:分发完成后的数据。
⑥、Response类
作用:封装服务器发过来的数据
⑦、HttpEntity类
Response分为header、与entity两个部分,HttpEntity掌管Response的实体。
二、构建Request类
1、Request类需要完成的任务
①、需要有URL,请求的编码,请求的ContentType,请求的类型(GET,PUT)。(就是固定的Http请求头的格式)
不了解请求头的参照 :网络——深入了解Http
②、能够添加额外的请求头
③、如果非GET类型,要能够附带请求数据(Params)
④、能够设置请求在队列中的优先级(重写equal(),hashcode(),compareTo)
⑤、能够解析特定数据并分发。
2、开始制作
第一步:获取URL路径
public abstract class Request<T>{
//第一步:获取url
private String mUrl;
public Request(String url){
mUrl = url;
}
}
//设置set、get方法,以后就不写了
public void setUrl(String url){
mUrl = url;
}
public String getUrl(){
return mUrl;
}
}
第二步:根据请求头的格式,设置请求头和需要上传的数据
①、请求类型 ②、用容器存储请求头 ③、如果存在POST、PUT这样的请求,需要设置请求的Body与body的Content-Type及编码
//创建泛型,来设置请求类型,防止用户输入错误
public enum HttpMethod{
GET("GET"),
POST("POST"),
PUT("PUT"),
DELETE("DELETE");
private String brief;
private HttpMethod(String brief){
this.brief = brief;
}
@Override
public String toString() {
return brief;
}
}
//设置默认类型为GET
private HttpMethod mMethod = HttpMethod.GET;
//用容器存储请求头与需要提交的body,add()与get()就不写了。
private final Map<String,String> mHeaders = new HashMap<>();
private final Map<String,String> mParams = new HashMap<>();
/**设置编码*/
//首先默认的编码与Content-Type
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
private static final String HEADER_CONTENT_TYPE = "Content-Type";
//设置编码,set()与get()不显示
private String mParamsEncoding = DEFAULT_PARAMS_ENCODING;
//返回Content-Type的内容
public String getBodyContentType(){
return "application/x-www-form-unlencoded;charset="+getParamsEncoding();
}
补:处理上传的数据。上传数据不能以键值对上传,我们需要一种转换格式,为xx=kk&mm=nn这样的格式
public String getBodyParams(){
final Set<Map.Entry<String,String>> paramsEntry = getParams().entrySet();
final StringBuilder sb = new StringBuilder();
//设置上传的格式
for(Map.Entry<String,String> entry : paramsEntry){
sb.append(entry.getKey()+"="+entry.getValue()+"&");
}
//由于最后会多一个&需要进行处理
String data = sb.toString();
if (data == ""){
return data;
}
else {
//向前移动一位截取
return data.substring(0,data.length()-1);
}
}
第三步:设置Request类在队列中的优先级
由:优先级和序列号控制
<pre name="code" class="java">public abstract class Request<T> implements Comparable<Request<T>>{
<span style="white-space:pre"> </span>//设置优先级枚举
<span style="white-space:pre"> </span>public enum Priority{
<span style="white-space:pre"> </span> LOW,
<span style="white-space:pre"> </span> NORMAL,
<span style="white-space:pre"> </span> HIGHT,
<span style="white-space:pre"> </span> IMMEDIATE;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>//设置优先级 并设置set()与get()
<span style="white-space:pre"> </span>private Priority mPriority = Priority.NORMAL;
<span style="white-space:pre"> </span>private int mSerialNum = 0;
<span style="white-space:pre"> </span>//通过继承comparable进行比较
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>public int compareTo(Request<T> another) {
Priority myPriority = getPriority();
<span style="white-space:pre"> </span> Priority anotherPriority = another.getPriority();
//两组序列相减,比大小
<span style="white-space:pre"> </span> return myPriority == anotherPriority ?
getSerialNum() - another.getSerialNum() :
myPriority.ordinal() - anotherPriority.ordinal();
}
}
第四步:处理接收后的Response并分发。
//设置抽象方法,让子类实现解析过程
public abstract T parseResponse(Response response);
//设置当完成之后的回调监听
public interface RequestListener<V>{
//返回获取并解析后的数据,及状态码
public void complete(V response,int code);
}
//设置分发的方法
public void deliverResponse(Response response){
T result = parseResponse(response);
int code = response.getStatusCode();
if (mReqListener != null){
mReqListener.complete(result,code);
}
}
注:现在还未写Response方法,所以就先将逻辑写上。
第五步:继承该抽象类,设置完整的解析类
public class JsonRequest extends Request<JSONObject> {
public JsonRequest(String url, RequestListener<JSONObject> listener) {
super(url, listener);
}
@Override
public JSONObject parseResponse(Response response) {
//获取response的数据并解析
JSONObject jsonObject = new JSONObject();
byte[] bytes = response.getRowData();
try {
String data = bytes.toString();
jsonObject = new JSONObject(data);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}
}
三、构建Response类
需求:
1、存储头部,和内容
2、返回状态码,和状态信息
3、返回头部和内容数据
为了方便,继承BasicHttpResponse类。
该类属于org,apache.http包中,由于API23删除了该包,需要自己从AndroidSDK文件夹中获取Jar包。
public class Response extends BasicHttpResponse {
/**
* 构造方法为状态栏信息
* ProtocolVersion表示:HTTP/1.1
* code表示:服务器返回的状态码
* reason表示:伴随状态码的信息
* */
public Response(ProtocolVersion ver, int code, String reason) {
super(ver, code, reason);
}
//父类自带addHeader与getHeader
@Override
public void addHeader(Header header) {
super.addHeader(header);
}
//父类自带addEntity与getEntity
@Override
public void setEntity(HttpEntity entity) {
super.setEntity(entity);
}
public int getStatusCode(){
return getStatusLine().getStatusCode();
}
public String getStatusMessage(){
return getStatusLine().getReasonPhrase();
}
//将entity设置为byte返回
public byte[] getRowData(){
try {
byte [] data = EntityUtils.toByteArray(getEntity());
return data;
} catch (IOException e) {
e.printStackTrace();
}
return new byte[1024];
}
}
四、构建HttpStack类
既然两个基础类都做好了,那么就开始数据传输。
由于在API 9 的时候,使用的是HttpClient,9以上的时候使用的是HttpUrlConnection,为方便扩展,将HttpStack设置为接口。
public interface HttpStack {
public Response performRequest();
}
在创建
HttpUrlConnStack前,我们还需要创建一个类,专门用来设置Connection的Config
叫做:HttpUrlConnConfig
public class HttpUrlConnConfig {
public static int connTimeOut = 10000;
public static int soTimeOut = 10000;
}
需求:
1、创建UrlConnection,并创建配置器
2、将Request参数,完整设置到UrlConnection中
3、获取流的数据,并转化为Response
*/
public class HttpUrlConnStack implements HttpStack {
private static final String TAG = "HttpUrlConnStack";
@Override
public Response performRequest(Request<?> request) {
HttpURLConnection conn = null;
try {
conn = createUrlConnection(request);
setRequestHeaders(conn,request);
setRequestParams(conn,request);
Response response = fetchResponse(conn);
return response;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 创建连接器
* @param request
* @return
* @throws IOException
*/
private HttpURLConnection createUrlConnection(Request<?> request) throws IOException{
URL url = new URL(request.getUrl());
URLConnection conn = url.openConnection();
conn.setConnectTimeout(HttpUrlConnConfig.connTimeOut);
conn.setReadTimeout(HttpUrlConnConfig.soTimeOut);
conn.setDoInput(true);
return (HttpURLConnection)conn;
}
private void setRequestHeaders(HttpURLConnection conn,Request<?> request){
Set<Map.Entry<String,String>> headerEntry = request.getHeaders().entrySet();
for(Map.Entry<String,String> entry : headerEntry){
conn.setRequestProperty(entry.getKey(),entry.getValue());
}
}
private void setRequestParams(HttpURLConnection conn,Request<?> request) throws IOException{
conn.setRequestMethod(request.getHttpMethod().toString());
String params = request.getBodyParams();
if (params != ""){
conn.setDoOutput(true);
conn.setRequestProperty(Request.HEADER_CONTENT_TYPE,request.getBodyContentType());
OutputStream os = conn.getOutputStream();
os.write(params.getBytes());
os.close();
}
}
private Response fetchResponse(HttpURLConnection conn){
ProtocolVersion version = new ProtocolVersion("HTTP",1,1);
try {
int code = conn.getResponseCode();
String msg = conn.getResponseMessage();
Response response = new Response(version,code,msg);
//设置Response的头部
addResponseHeader(conn,response);
//设置Response的内容
response.setEntity(entityFromConn(conn));
return response;
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG,"捕获Response错误");
}
return null;
}
private void addResponseHeader(HttpURLConnection conn,Response response){
Set<Map.Entry<String,List<String>>> headers =
conn.getHeaderFields().entrySet();
for(Map.Entry<String,List<String>> entry : headers){
String key = entry.getKey();
for(String value : entry.getValue()){
Header header = new BasicHeader(key,value);
response.addHeader(header);
}
}
}
private HttpEntity entityFromConn(HttpURLConnection conn) throws IOException{
BasicHttpEntity entity = new BasicHttpEntity();
InputStream is = conn.getInputStream();
entity.setContent(is);
entity.setContentLength(conn.getContentLength());
entity.setContentType(conn.getContentType());
entity.setContentEncoding(conn.getContentEncoding());
is.close();
return entity;
}
}
五、构建NetWorkThread
需求:
1、从队列中获取Request,然后将其传递给HttpStack执行
2.、获取Response然后分发数据
public class NetWorkThread extends Thread {
//所有线程共享,所以设置为static
private static BlockingQueue<Request<?>> mQueue;
private static HttpStack mHttpStack;
//创建分发器
private static RequestDelivery mDelivery = new RequestDelivery();
private boolean isStop = false;
public NetWorkThread (BlockingQueue<Request<?>> queue, HttpStack httpStack){
mQueue = queue;
mHttpStack = httpStack;
}
@Override
public void run() {
super.run();
try {
while (!isStop){
Request<?> request = mQueue.take();
Response response = mHttpStack.performRequest(request);
//创建分发器
mDelivery.deliverResponse(request,response);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void quit(){
isStop = true;
interrupt();
}
}
分发器:
public class RequestDelivery implements Executor{
//获取主线程的Handler
private Handler mHander = new Handler(Looper.getMainLooper());
public void deliverResponse(final Request<?> request, final Response response){
Runnable runnable = new Runnable() {
@Override
public void run() {
request.deliverResponse(response);
}
};
execute(runnable);
}
@Override
public void execute(Runnable command) {
mHander.post(command);
}
}
六、构建RequestQueue队列
需求:
1、创建、终止线程
2、添加Requst到队列中
①、初始化RequestQueue
public class RequestQueue {
//创建队列
private static final PriorityBlockingQueue<Request<?>> QUEUE =
new PriorityBlockingQueue<>();
private static RequestQueue sRequestQueue;
//设置使用哪种方式处理网络传输,(HttpStack未实现)
private static HttpStack sStack;
//统一使用一条队列存储数据,所以采用单例模式
private RequestQueue(HttpStack httpStack){
sStack = httpStack;
}
public RequestQueue getInstance(HttpStack httpStack){
if (sRequestQueue == null){
sRequestQueue = new RequestQueue(httpStack);
}
return sRequestQueue;
}
public void setHttpStack(HttpStack httpStack){
sStack = httpStack;
}
}
②、创建与停止线程
private static final int DEFAULT_THREAD_SIZE = Runtime.getRuntime().availableProcessors()+1;
//暂未创建,知道是线程就可以了(<span style="font-family: Arial, Helvetica, sans-serif;">NetWorkThread 暂未创建</span><span style="font-family: Arial, Helvetica, sans-serif;">)</span>
private static final NetWorkThread[] mThreads = new NetWorkThread[DEFAULT_THREAD_SIZE];
//创建处理线程
public void startExecutor(){
for(int i=0 ; i<mThreads.length; ++i){
mThreads[i] = new NetWorkThread();
}
}
//终止线程
public void stop(){
for(int i=0; i<mThreads.length; ++i){
NetWorkThread thread = mThreads[i];
if (thread != null){
thread.quit();
mThreads[i] = null;
}
}
}
//开启线程
public void start(){
stop();
startExecutor();
}
③、添加Request数据
private static final AtomicInteger mSerialNum = new AtomicInteger();
public void addRequest(Request<?> request){
//设置序列号
request.setSerialNum(mSerialNum.getAndIncrement());
QUEUE.add(request);
}
使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建队列
RequestQueue queue = RequestQueue.getInstance(new HttpUrlConnStack());
//启动队列
queue.start();
//创建Request请求
JsonRequest request = new JsonRequest("https://www.baidu.com/", new Request.RequestListener<JSONObject>() {
@Override
public void complete(JSONObject response, int code) {
Log.d("MainActivity","哈哈");
}
});
queue.addRequest(request);
}
}
七、如何仿表单混合上传文件和参数
①、创建MultipartEntity类
首先看一下,普通表单请求的代码:
POST login.php HTTP1.1
Accept-Encoding:gzip
Content-Type:multipart/form-data; boundary=ABCD //Content-Type:表示这是一个表单 boundary:表示分隔符
Content-Length:22350
Host:http://write.blog.youkuaiyun.com
Connection:Keep-Alive
--ABCD //通过--加上boundary的值:表示这是表单的一个数据
Content-Disposition:form-data;name="username" //key值
Content-Type:text/plain; charset=UTF-8 //key的类型
Content-Transfer-Encoding:8bit //key的大小
//空白行,必须加
chen //value的值
--ABCD
Content-Disposition:form-data;name="images"
filename="storage/emulated/0/Camera/picture/tempeter.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding:binary
(这里是图片的二进制数据)
--ABCD-- //表示终止
那么先从头部开始我们需要些哪些内容
Content-Type:multipart/form-data; boundary=ABCD
Content-Length:22350
这两部分是需要我们书写的。
表单部分我们需要书写哪些内容
普通参数的上传格式
--ABCD
Content-Disposition:form-data;name="username"
Content-Type:text/plain; charset=UTF-8
Content-Transfer-Encoding:8bit
(这里是数据)
文件的上传格式:
--ABCD
Content-Disposition:form-data;name="images";
filename="storage/emulated/0/Camera/picture/tempeter.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding:binary
所以说,我们只要仿照这份数据本文,传给服务器就可以。
原理:
1、首先设置请求的头
2、将这份表单中的相同部分和不同部分设置成参数
3、将生成的数据存储在ByteArrayoutputStream中
public class MultipartEntity implements HttpEntity {
/**
* 第一步:
* 1、将可以写的参数都写出来
* 2、创建随机的Boundary
* 3、创建存储数据的容器ByteArrayStream
*/
//提供默认的参数编码
public static final String CHARTSET_UTF = "UTF-8";
//可获取的字符串,用于生成随机的Boundary
private static final char[] ALL_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
//头部的参数名
private static final String HEADER_CONTENT_TYPE = "Content-Type:";
private static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition:";
private static final String HEADER_TRANSFER_ENCODING = "Content-Transfer-Encoding:";
//用来换行用的
private static final String CHANGE_ROW = "\r\n";
//Type的参数值
private static final String TYPE_MULTIPART = "multipart/form-data";
private static final String TYPE_TEXT_PLAIN = "text/plain";
private static final String TYPE_OCTET_STREAM = "application/octet-stream";
//Disposition的参数值
private static final String DISPOSITION_FORM = "form-data";
//Transfer_Encoding的参数指
private static final String ENCODING_TEXT = "8bit";
private static final String ENCODING_FILE = "binary";
//一直用的键值对
private static final String DISPOSITION = HEADER_CONTENT_DISPOSITION+DISPOSITION_FORM+";";
private final ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();
private final String mBoundary;
public MultipartEntity() {
mBoundary = createBoundary();
}
private String createBoundary(){
StringBuilder sb = new StringBuilder();
Random random = new Random();
for(int i=0; i<30; ++i){
sb.append(ALL_CHARS[random.nextInt(ALL_CHARS.length)]);
}
return sb.toString();
}
/**
* 第三步:创建添加String,File键值对的方法
* 然后写入到ByteOutputStream中
*/
public void addStringPart(String name,String value,String charset){
writeToStream(name,null,value,TYPE_TEXT_PLAIN,charset,ENCODING_TEXT);
}
public void addFilePart(String name,String fileName,String filePath){
writeToStream(name,fileName,filePath,TYPE_OCTET_STREAM,null,ENCODING_FILE);
}
private void writeToStream(String name,String fileName,String value,
String type,String charset,String transferEncoding){
try {
//表单的请求头
mOutputStream.write(getFirstBoundary());
mOutputStream.write(getContentDisposition(name,fileName));
mOutputStream.write(getContentType(type,charset));
mOutputStream.write(getTransferEncoding(transferEncoding));
//换行
mOutputStream.write(CHANGE_ROW.getBytes());
//填写数据
if (fileName == null){
mOutputStream.write((value+CHANGE_ROW).getBytes());
}
else {
//未考虑到File地址不正确的情况
File file = new File(value);
FileInputStream fis = new FileInputStream(file);
byte [] bytes = new byte[4096];
int len = 0;
while ((len = fis.read(bytes)) != -1){
mOutputStream.write(bytes,0,bytes.length);
}
mOutputStream.flush();
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private byte[] getFirstBoundary(){
return ("--"+mBoundary+CHANGE_ROW).getBytes();
}
private byte[] getContentDisposition(String name,String fileName){
//这是Text文本
if (fileName == null){
//输出就是 Content-Disposition:form-data;name="xxx"
return (DISPOSITION+"name=\""+name+"\""+CHANGE_ROW).getBytes();
}
else {
//输出就是Content-Disposition:form-data;name="images";filename="xxx"
return (DISPOSITION+"name=\""+name+"\";fileName=\""+fileName+"\""+CHANGE_ROW).getBytes();
}
}
private byte[] getContentType(String type,String charset){
//这是text文本
if (charset != null){
return (HEADER_CONTENT_TYPE+type+";charset="+charset+CHANGE_ROW).getBytes();
}
else {
return (HEADER_CONTENT_TYPE+type+CHANGE_ROW).getBytes();
}
}
private byte[] getTransferEncoding(String transferEncoding){
return (HEADER_TRANSFER_ENCODING+transferEncoding+CHANGE_ROW).getBytes();
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public boolean isChunked() {
return false;
}
/**
* 第二步:设置请求头
*
*/
@Override
public long getContentLength() {
return mOutputStream.toByteArray().length;
}
@Override
public Header getContentType() {
return new BasicHeader(HEADER_CONTENT_TYPE, TYPE_MULTIPART +";boundary="+mBoundary);
}
@Override
public Header getContentEncoding() {
return null;
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
return null;
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
//设置结束符
mOutputStream.write(("--"+mBoundary+"--").getBytes());
byte [] bytes = mOutputStream.toByteArray();
outputStream.write(bytes);
mOutputStream.close();
}
@Override
public boolean isStreaming() {
return false;
}
@Override
public void consumeContent() throws IOException {
}
}
--ABCD //通过--加上boundary的值:表示这是表单的一个数据
Content-Disposition:form-data;name="username" //key值
Content-Type:text/plain; charset=UTF-8 //key的类型
Content-Transfer-Encoding:8bit