出于完善我个人的理论了解面,再加上google在6.0之后取消了HttpClient,所以我特地抽了点时间针对HttpUrlConnection做了一点连接,并写了个工具类,方便以后可以用上,那么话不多说,直接上代码,我再代码中注释已经做得很详细了
基于对于代码结构的我个人的结构优化思维,所以并不是直接将下载的代码写好,而是搭建了一下大概的结构,大致来说分为了 模型层、数据层、控制层
首先是模型层
/**
* 用来存放网络返回数据的类
* @author yjh
*
*/
public class UrlResponseBean {
//id
private String mId;
//响应结果:成功、网络出错、服务器出错
private int mResponseResult;
//具体的响应返回码
private int mResponseCode;
//响应成功后返回的内容,这里返回流
// private InputStream mIs;
//获取网的网络数据,已经转化为输出流了
private ByteArrayOutputStream mBaos;
//响应成功后返回的数据
private String mResponseData;
private HttpURLConnection mConnectBean;
public int getResponseResult() {
return mResponseResult;
}
public void setResponseResult(int responseResult) {
this.mResponseResult = responseResult;
}
public int getResponseCode() {
return mResponseCode;
}
public void setResponseCode(int responseCode) {
this.mResponseCode = responseCode;
}
public String getId() {
return mId;
}
public void setId(String id) {
this.mId = id;
}
public String getResponseData() {
return mResponseData;
}
public void setResponseData(String responseData) {
this.mResponseData = responseData;
}
protected HttpURLConnection getConnectBean() {
return mConnectBean;
}
protected void setConnectBean(HttpURLConnection connectBean) {
this.mConnectBean = connectBean;
}
public ByteArrayOutputStream getBaos() {
return mBaos;
}
public void setBaos(ByteArrayOutputStream mBaos) {
this.mBaos = mBaos;
}
}
然后是数据层:
public class UrlConnectTool {
private static final int TIMEOUT_IN_MILLIONS = 5000;
/**
* 在2.3以上,想要卡主进程运行访问网络时,需要设置这个
*/
@SuppressLint("NewApi")
public void downloadInit(){
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
/**
* 卡主线程的get下载方式
* 这里返回的bean里面是下载结果以及下载内容
*/
public UrlResponseBean doGet(String urlStr) {
UrlResponseBean bean = new UrlResponseBean();
URL url = null;
HttpURLConnection conn = null;
try {
url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
//这里在写的时候我就一直想着在设置好超时之后,当超时时是怎么个反馈方式,网上大都是设置完了就不管了,也没有说超时
//时是怎么个响应方式,我好不容易看到个说是会在抛出的错误里面返回的,但是也需要验证,但我没有可以用于测试
//超时的网址!需要测试!
conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
conn.setRequestMethod("GET");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Charset", "UTF-8");//设置编码格式
int connectCode = conn.getResponseCode();
//这里获取之后,如果需要统计,则可以在这里统计连接之后的错误信息码
bean.setResponseCode(connectCode);
bean.setConnectBean(conn);
//在这里,对于控制层来说,只需要知道是成功,还是网络不好(让用户调整网络状态),或者是连接失败(让用户刷新)
switch ((connectCode/100)) {
case 2://表示成功
bean.setResponseResult(UrlResponseConsts.DOWNLOAD_SUCCESS);
bean.setBaos(inputToByteArrayOutputStream(conn.getInputStream()));
// bean.setIs(conn.getInputStream());
return bean;
case 4://表示访问网址出错
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
case 5://表示服务器出错
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
}
} catch (ProtocolException e) {
// 如果没有建立连接并执行getInputStream
// 在调用getResponseCode的时候,该函数会自动执行getInputStream并获取Response Code
// 所以你在此函数后再设置Request Type就会造成Connection already established错误。
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
//这个抛出的错我是针对连接超时时做的处理
catch (SocketTimeoutException e) {
//连接超时,Socket读数据的超时,https://twitter.com/ 我用的推特来测试出来了
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
//这个抛出的错我是针对连接超时时做的处理
catch (ConnectTimeoutException e) {
//响应超时,通过网络与服务器建立连接的超时
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
catch (IOException e) {
//错误1:连接失败,没有网络 在我用推特主页测试的时候,此时我关闭了网络,这里的报错为java.net.UnknownHostException
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
finally {
try {
if (conn.getInputStream() != null)
conn.getInputStream().close();
} catch (IOException e) {
}
conn.disconnect();
}
//此时是既不是成功也不是失败,应该属于服务器问题
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
}
/**
* 采用post方式访问网络
*/
public UrlResponseBean doPost(String urlStr,String param){
PrintWriter out = null;
// BufferedReader in = null;
UrlResponseBean bean = new UrlResponseBean();
HttpURLConnection conn = null;
try
{
URL realUrl = new URL(urlStr);
// 打开和URL之间的连接
conn = (HttpURLConnection) realUrl
.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestMethod("POST");
//设置文件类型
// application/x-www-form-urlencoded: 窗体数据被编码为名称/值对。这是标准的编码格式。
//multipart/form-data: 窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
//text/plain: 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符。
//form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和
//multipart/form-data,默认为application/x-www-form-urlencoded。
//当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),
//然后把这个字串append到url后面,用?分割,加载这个新的url。 当action为post时候,浏览器把form数据封装到http body中,然后发送到server。
//如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。
//浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "utf-8");
//设置不使用缓存
conn.setUseCaches(false);
// 发送POST请求必须设置如下两行
//设置容许输出,设置了之后才能用post
conn.setDoOutput(true);
// 设置是否从httpUrlConnection读入,默认情况下是true;
conn.setDoInput(true);
conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
if (param != null && !param.trim().equals(""))
{
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
}
int connectCode = conn.getResponseCode();
//这里获取之后,如果需要统计,则可以在这里统计连接之后的错误信息码
bean.setResponseCode(connectCode);
bean.setConnectBean(conn);
//在这里,对于控制层来说,只需要知道是成功,还是网络不好(让用户调整网络状态),或者是连接失败(让用户刷新)
switch ((connectCode/100)) {
case 2://表示成功
bean.setResponseResult(UrlResponseConsts.DOWNLOAD_SUCCESS);
bean.setBaos(inputToByteArrayOutputStream(conn.getInputStream()));
// bean.setIs(conn.getInputStream());
return bean;
case 4://表示访问网址出错
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
case 5://表示服务器出错
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
}
} catch (ProtocolException e) {
// 如果没有建立连接并执行getInputStream
// 在调用getResponseCode的时候,该函数会自动执行getInputStream并获取Response Code
// 所以你在此函数后再设置Request Type就会造成Connection already established错误。
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
//这个抛出的错我是针对连接超时时做的处理
catch (SocketTimeoutException e) {
//连接超时,Socket读数据的超时,https://twitter.com/ 我用的推特来测试出来了
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
//这个抛出的错我是针对连接超时时做的处理
catch (ConnectTimeoutException e) {
//响应超时,通过网络与服务器建立连接的超时
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
catch (IOException e) {
//错误1:连接失败,没有网络 在我用推特主页测试的时候,此时我关闭了网络,这里的报错为java.net.UnknownHostException
e.printStackTrace();
bean.setResponseResult(UrlResponseConsts.NET_ERROR);
return bean;
}
// 使用finally块来关闭输出流、输入流
finally
{
try {
if (out != null)
{
out.close();
}
if (conn.getInputStream() != null)
conn.getInputStream().close();
} catch (IOException e) {
}
if(null != conn)
conn.disconnect();
}
//此时是既不是成功也不是失败,应该属于服务器问题
bean.setResponseResult(UrlResponseConsts.SERVER_ERROR);
return bean;
}
private ByteArrayOutputStream inputToByteArrayOutputStream(InputStream is) throws IOException{
if (null != is) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len = -1;
byte[] buf = new byte[128];
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
baos.flush();
return baos;
}
return null;
}
}
下一个是控制层:
public class UrlConnectCenter {
private UrlConnectTool mConnectTool;
private DataKeepTool mDataTool;
public UrlConnectCenter() {
mConnectTool = new UrlConnectTool();
mDataTool = new DataKeepTool();
}
/**
* 卡滞主线程的网络连接,这里处理好post和get方式以及返回来的结果,过滤下载结果 这里是UrlConnectTool之后的第二层处理
*/
public UrlResponseBean startConnectUi(UrlConnectDescription description) {
systemAdjustment();
UrlResponseBean response = new UrlResponseBean();
// 这里获取到访问后的结果
if (!description.getIsPost()) {// get方式
String connectUrl = description.getConnectUrl();
// get方式时拼起来
if(null != description.getConnectData())
connectUrl +="?"
+ description.getConnectData();
response = mConnectTool.doGet(connectUrl);
} else {// post方式
response = mConnectTool.doPost(description.getConnectUrl(), description.getConnectData());
}
// 设置下载的id
response.setId(description.getId());
// 下载成功时
if (response.getResponseResult() == UrlResponseConsts.DOWNLOAD_SUCCESS) {
// 在这一层处理好数据,是作为数据返回还是文件存储等
try {
if (null == description.getFilePath()) {// 将流转化为字符
String backInfo = mDataTool.getDataBackInfo(response.getBaos());
response.setResponseData(backInfo);
} else {
// 将流转化为文件并保存
mDataTool.getDataSaveFile(response.getBaos(),
description.getFilePath());
}
} catch (IOException e) {
//这里报错就是流转化时的流报错
e.printStackTrace();
}
// 用完之后将流关闭
try {
response.getBaos().close();
} catch (IOException e) {
e.printStackTrace();
}
}
return response;
}
/**
* 在2.3之前可以直接在主进程之中卡主线程下载
* 但是2.3之后需要设置过后才能在主线程下载
*/
private void systemAdjustment(){
if (android.os.Build.VERSION.SDK_INT > 9) {
mConnectTool.downloadInit();
}
}
}
控制层中也有一个bean,用于存放任务的描述
/**
* 网络连接描述,用于需要发起网络连接时填充所需数据之后使用
* @author yjh
*
*/
public class UrlConnectDescription {
//id
private String mId;
//需要访问的网址
private String mConnectUrl;
//是否是post方式
private boolean mIsPost = false;
//文件下载的路径,当需要下载为文件时
private String mFilePath;
//网址之后需要添加的参数,这里单独传入,以便于内部处理post或者get
private String mConnectData;
public String getId() {
return mId;
}
public void setId(String mId) {
this.mId = mId;
}
public String getConnectUrl() {
return mConnectUrl;
}
public void setConnectUrl(String mConnectUrl) {
this.mConnectUrl = mConnectUrl;
}
public boolean getIsPost() {
return mIsPost;
}
public void setIsPost(boolean mIsPost) {
this.mIsPost = mIsPost;
}
public String getFilePath() {
return mFilePath;
}
public void setFilePath(String mFilePath) {
this.mFilePath = mFilePath;
}
public String getConnectData() {
return mConnectData;
}
public void setConnectData(String mConnectData) {
this.mConnectData = mConnectData;
}
}
然后有一个工具类,用于转换流成为String 内容以及保存为文件
/**
* 处理数据
*
* @author yjh
*
*/
public class DataKeepTool {
/**
* 将访问后返回的数据转化为String类型并返回
*
* @param conn
* @param is
* @param baos
* @return
* @throws IOException
*/
public String getDataBackInfo(ByteArrayOutputStream boas) throws IOException {
if (null != boas) {
//采用这种方式防止出现乱码
byte[] lens = boas.toByteArray();
return new String(lens);
}
return null;
}
/**
* 用于将流保存为文件
*
* @param is
*/
public void getDataSaveFile(ByteArrayOutputStream baos, String filePath) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(filePath));
baos.writeTo(fos);
fos.close();
baos.flush();
baos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
}
}
}
代码到这边基本就够用了,还有一个的话是常亮类,这个类内容很简单,就三个访问状态的常量而已
由于在代码中注释已经写得很多了,我再这里就不再做多的描述了
这里还差的最后一个文件上传的话我再找时间补充完整