这周导师要求我不用框架写一个网络请求类。平时都是习惯拿来主义,突然自己写发现其中还是有很多东西的。
要求:写一个网络请求的工具类,支持GET、POST,可满足日常请求网络,提交表单,下载文件这些的使用。不使用框架。
一、GET
这个应该是最简单的,很多的api请求也是直接采用的get的形式。实现如下:
/**
* get请求
* @param url url
* */
public static String get(String url) {
HttpURLConnection conn = null;
try {
// 利用string url构建URL对象S
URL mURL = new URL(url);
conn = (HttpURLConnection) mURL.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(readTimeout);
conn.setConnectTimeout(connectTimeout);
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
return getStringFromInputStream(is);
} else {
Log.d(TAG,"response status is "+responseCode);
}
return "";
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
/**
* 从InputStream中获取内容
* @param is 输入流
* @return InputStream中内容
* */
private static String getStringFromInputStream(InputStream is) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len ;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
is.close();
String state = os.toString();
os.close();
return state;
}
HttpURLConnection中支持很多配置参数,在下面的例子中我们会用到更多,参考网上的例子,在get这一块都比较简洁,我也就这么写了。
小细节:
-
HttpURLConnection.HTTP_OK = 200,直接写200的话不是很直观。
-
注意在finally中断开连接而不是在try中,确保最终有执行。
二、post
这一块稍微多一点,如下:
/**
* post请求,同步操作
* @param urlPath url
* @param content 请求内容
* */
public static String post(String urlPath , String content) {
HttpURLConnection conn = null;
try {
// 创建一个URL对象
URL url = new URL(urlPath);
// 调用URL的openConnection()方法,获取HttpURLConnection对象
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");// 设置请求方法为post
conn.setReadTimeout(readTimeout);
conn.setConnectTimeout(connectTimeout);
//下面两行为post必须
conn.setDoOutput(true);// 设置此方法,允许向服务器输出内容
conn.setDoInput(true);
// 获得一个输出流,向服务器写数据,默认情况下,系统不允许向服务器输出内容
OutputStream out = conn.getOutputStream();// 获得一个输出流,向服务器写数据
out.write(content.getBytes());
out.flush();
out.close();
int responseCode = conn.getResponseCode();
//正确返回
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
return getStringFromInputStream(is);
} else {//状态错误
throw new NetworkErrorException("response status is "+responseCode);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();// 关闭连接
}
}
return null;
}
与get对比,可以看到多了设置input output的设置,是因为默认服务器是不允许向服务器输出内容的。不打开这个开关你的post如何传递参数呢是吧。
三、下载文件
/**
* 下载文件,同步
*
* @param url 文件下载路径
* @param filePath 下载文件保存路劲
* @param name 下载文件保存名称
*@param listener 下载监听器
* @return 下载成功返回0,否则返回1
* *///测试通过
public static int downloadFile(String url,String filePath,String name,DownloadListener listener) throws IOException {
HttpURLConnection httpURLConnection = null;
FileOutputStream fos = null;
InputStream inputStream = null;
try {
// 利用string url构建URL对象
URL mURL = new URL(url);
httpURLConnection = (HttpURLConnection) mURL.openConnection();
httpURLConnection.setConnectTimeout(connectTimeout);
int contentLength = httpURLConnection.getContentLength();
//得到输入流
inputStream = httpURLConnection.getInputStream();
//获取字节数组
write2SDFromInput(filePath,name,inputStream,contentLength,listener);
//文件保存位置
return 1;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
if (fos != null) {
fos.close();
}
if (inputStream != null) {
inputStream.close();
}
}
return 0;
}
/**
* 从下载输入流中读取文件,实时更新进度到listener中
* @param listener 下载监听器
* @param path 文件存放位置
* @param contentLength 总下载大小
* @param fileName 存放文件名字
* */
private static void write2SDFromInput(String path ,String fileName,InputStream input,int contentLength,DownloadListener listener) {
File file ;
OutputStream output = null;
//int length = input.
try {
file =new File(path ,fileName);
output = new FileOutputStream(file);
int ch ;
double totalReaded = 0;
double progress ;
while((ch=input.read())!=-1) {
output.write(ch);
totalReaded++;
progress = totalReaded / contentLength;
listener.process(progress);
}
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
if(output!=null) {
output.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这一点我出问题是在保存文件上,一开始采用了跟之前一样的 byte[] buffer = new byte[1024]; 进行获取,可能是设太大的原因,最后的文件是有问题的,比如图片最下会出现黑条,应该是字节数不对齐的问题,所以我这里采用一字节一字节读的方式。关于这点恳请大家指教。
四、文件上传
刚刚我们做的post,没有指明Content-Type,也就是参数的编码格式,使用的是默认的x-www-form-urlencoded,比如:tel=13637829200&password=123456这样直接拼接的。这种编码的优点是简洁,基本上可以适用于所有的情况,唯独在上传文件或者二进制的数据时效率低。所以在上传这块我们需要使用multipart/form-data。他的格式是这样的:
-----------------------------7cd1d6371ec
Content-Disposition: form-data; name="name"
ryan ou
-----------------------------7cd1d6371ec
Content-Disposition: form-data; name="email"
ryan@rhythmtechnology.com
-----------------------------7cd1d6371ec
Content-Disposition: form-data; name="Logo"; filename="D:\My Documents\My Pictures\Logo.jpg"
Content-Type: image/jpeg
每个field被分成小部分,而且包含一个value是"form-data"的"Content-Disposition"的头部;一个"name"属性对应field的ID。
所以,我们不仅需要指定Content-Type,还需要对参数进行处理。如下:
/**
* 发送文件post请求,使用multipart/form-data类型,传输比较大的二进制或者文本数据时效率比较好
*@param urlstr 上传路劲
*@param files 上传文件map,可空
*@param map 上传文本内容,可空
*@return 服务器响应内容
*/
public static String doFilePost(String urlstr, Map<String, String> map, Map<String, File> files) throws IOException {
String BOUNDARY = "----WebKitFormBoundaryDwvXSRMl0TBsL6kW"; // 定义数据分隔线
URL url = new URL(urlstr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 发送POST请求必须设置如下两行
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("Charsert", "UTF-8");
connection.setRequestProperty("Accept-Encoding", "gzip,deflate");
connection.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
OutputStream out = new DataOutputStream(connection.getOutputStream());
byte[] end_data = ("--" + BOUNDARY + "--\r\n").getBytes();// 定义最后数据分隔线
// 文件
if (files != null && !files.isEmpty()) {
for (Map.Entry<String, File> entry : files.entrySet()) {
File file = entry.getValue();
String fileName = entry.getKey();
StringBuilder sb = new StringBuilder();
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data;name=\"" + fileName
+ "\";filename=\"" + file.getName() + "\"\r\n");
String type = getMimeType(file.getAbsolutePath());
Log.d(TAG,"type = "+type);
sb.append("Content-Type: "+type+"\r\n\r\n");
byte[] data = sb.toString().getBytes();
out.write(data);
DataInputStream in = new DataInputStream(new FileInputStream(
file));
int bytes ;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
out.write("\r\n".getBytes()); // 多个文件时,二个文件之间加入这个
in.close();
}
}
// 数据参数
if (map != null && !map.isEmpty()) {
for (Map.Entry<String, String> entry : map.entrySet()) {
StringBuilder sb = new StringBuilder("");
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data; name=\""
+ entry.getKey() + "\"");
sb.append("\r\n");
sb.append("\r\n");
sb.append(entry.getValue());
sb.append("\r\n");
byte[] data = sb.toString().getBytes();
out.write(data);
}
}
out.write(end_data);
out.flush();
out.close();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream inStream = connection.getInputStream();
String json = getStringFromInputStream(inStream);
return json;
}
return null;
}
五、其他点
下载监听:可以在HttpURLConnection连接之后使用
httpURLConnection.getContentLength()
获得总大小,然后在处理InputStream的时候加上对比得到下载进度。
参考文档:
-
https://www.jianshu.com/p/3141d4e46240 - Android网络请求心路历程
-
http://blog.csdn.net/hxchuan000/article/details/48949061 - x-www-form-urlencoded与multipart/form-data区别
网友评论