android 数据存储和访问方式五:网络详解

数据存储和访问方式五:网络


一、从网络上获取数据(图片、网页、XML、JSON等)
1.从网络获取一张图片,然后显示在手机上
①public byte [] getImageFromNet(){
try {
URL url = new URL("http://img10.360buyimg.com/n1/4987/9dceed99-e710-4ca8-b7f1-4e9dc01a0f75.jpg");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
InputStream inStream = conn.getInputStream();
byte [] data = readInputStream(inStream);//获取图片的二进制数据
//FileOutputStream outStream = new FileOutputStream("360buy.jpg");
//outStream.write(data);
//outStream.close();
return data;
} catch (Exception e) {
e.printStackTrace();
}
}
private byte [] readInputStream(InputStream inStream) throws IOException {
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len = inStream.read(buffer)) != -1){
byteOutputStream.write(buffer, 0, len);
}
inStream.close();
byte [] data = byteOutputStream.toByteArray();
byteOutputStream.close();
return data;
}
②使用ImageView组件显示图片。
③生成位图并设置到ImageView中
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageView.setImageBitmap(bitmap);
④在AndroidManifest.xml文件添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET"/>

2.从网络获取指定网页的html代码,然后显示在手机上
①public String getHtmlCodeFromNet(){
try {
URL url = new URL("http://www.163.com");
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
InputStream inStream = conn.getInputStream();
byte [] data = readInputStream(inStream);
String htmlString = new String(data, "gb2312");
System.out.println(htmlString);
return htmlString;
} catch (Exception e) {
e.printStackTrace();
}
}
②使用TextView组件显示网页代码
ScrollView 滚动条
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView"
/>
</ScrollView>
③在AndroidManifest.xml文件添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET"/>

3.从服务器上获取最新的视频资讯信息,该信息以XML格式返回给Android客户端,然后列表显示在手机上。
>>最新资讯
喜羊羊与灰太狼 时长:60
盗梦空间 时长:120
生化危机 时长:100

①开发web端,在此采用Struts 2技术
②设计显示界面,使用ListView
③开发Android手机视频资讯客户端
注意:不能使用127.0.0.1或者localhost访问在本机开发的web应用
部分代码:
public List<Video> getXMLLastVideos(String urlPath) throws Exception{
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
InputStream inStream = conn.getInputStream();
return parseXML(inStream);
}

private List<Video> parseXML(InputStream inStream) throws Exception {
List<Video> videos = null;
Video video = null;
XmlPullParser parser = Xml.newPullParser();
parser.setInput(inStream, "UTF-8");
int eventType = parser.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT){
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
videos = new ArrayList<Video>();
break;
case XmlPullParser.START_TAG:
String name = parser.getName();
if("video".equals(name)){
video = new Video();
video.setId(new Integer(parser.getAttributeValue(0)));
}
if(video != null){
if("title".equals(name)){
video.setTitle(parser.nextText());
}else if("timeLength".equals(name)){
video.setTimeLength(new Integer(parser.nextText()));
}
}
break;
case XmlPullParser.END_TAG:
String pname = parser.getName();
if("video".equals(pname)){
videos.add(video);
video = null;
}
break;
default:
break;
}
eventType = parser.next();
}
return videos;
}
④在AndroidManifest.xml文件添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET"/>


4.从服务器上获取最新的视频资讯信息,该信息以JSON格式返回给Android客户端,然后列表显示在手机上。
服务器端需要返回的JSON数据:
[{id:1,title:"aaa1",timeLength:50},{id:2,title:"aaa2",timeLength:50},{id:3,title:"aaa3",timeLength:50}]

public List<Video> getJSONLastVideos(String urlPath) throws Exception{
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
InputStream inStream = conn.getInputStream();
byte [] data = StreamTools.readInputStream(inStream);
String json = new String(data);
JSONArray array = new JSONArray(json);
List<Video> videos = new ArrayList<Video>();
for(int i = 0;i < array.length(); i++){
JSONObject item = array.getJSONObject(i);
int id = item.getInt("id");
String title = item.getString("title");
int timeLength = item.getInt("timeLength");
videos.add(new Video(id, title, timeLength));
}
return videos;
}


二、通过HTTP协议提交文本数据(GET/POST)
GET、POST、HttpClient
1.通过GET方式提交参数给服务器:注意处理乱码(Android系统默认编码是UTF-8),提交的数据最大2K。
①服务器端代码
HttpServletRequest request = ServletActionContext.getRequest();
//服务器端编码处理,先以ISO-8859-1编码得到二进制数据,然后使用UTF-8对数据重新编码
byte [] data = request.getParameter("title").getBytes("ISO-8859-1");
String titleString = new String(data, "UTF-8");
System.out.println("this.title==" + titleString);
System.out.println("this.timeLength==" + this.timeLength);
②客户端代码
public boolean sendGetRequest(String path, Map<String, String> params, String enc) throws Exception{
StringBuilder sb = new StringBuilder(path);
sb.append('?');
if(params != null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
sb.append(entry.getKey())
.append('=')
//对客户端发送GET请求的URL重新编码
.append(URLEncoder.encode(entry.getValue(), enc))
.append('&');
}
sb.deleteCharAt(sb.length()-1);
}
URL url = new URL(sb.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
if(conn.getResponseCode() == 200){
return true;
}
return false;
}
2.通过POST方式提交参数给服务器:
①<form method="post"/> 浏览器会把提交的数据转换成Http协议

②分析Http协议(使用HttpWatch)
第一部分:发送给服务器的
请求头部分(**********表示Http协议中必须提供的部分)
POST /videoweb/managerPost.action HTTP/1.1---(请求方式 请求路径 使用Http协议是1.1)
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel,
application/vnd.ms-powerpoint, application/msword, application/QVOD, application/QVOD, */* ---(浏览器接收的数据类型)
Referer: http://127.0.0.1:8081/videoweb/index.jsp---(请求来源,即从哪个页面发出请求的)
Accept-Language: zh-cn,en-US;q=0.5
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; .NET CLR 2.0.50727) ---(用户的浏览器类型)
Content-Type: application/x-www-form-urlencoded ---(POST请求的内容类型)**********
Accept-Encoding: gzip, deflate
Host: 127.0.0.1:8081---(POST请求的服务器主机名和端口)**********
Content-Length: 46---(POST请求的内容长度,即实体数据部分的长度)**********
Connection: Keep-Alive---(长连接)
Cache-Control: no-cache
Cookie: JSESSIONID=EFD762A0997BE1191DABFC311B345EE7
实体数据部分
title=aaa&timeLength=22&sub=%E6%8F%90%E4%BA%A4

第二部分:客户端接收到的
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 275
Date: Sun, 06 Mar 2011 10:57:55 GMT

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
淇濆瓨瀹屾垚锛?
</body>
</html>

③服务器端代码
HttpServletRequest request = ServletActionContext.getRequest();
request.setCharacterEncoding("UTF-8");
System.out.println("doPostRequest");
System.out.println("this.title==" + this.title);
System.out.println("this.timeLength==" + this.timeLength);
④客户端代码
public boolean sendPostRequest(String path, Map<String, String> params, String enc) throws Exception{
//分析http协议
//发出post请求时,浏览器会自动为实体数据部分进行重新编码。由于我们使用的是Android,没有用IE浏览器,因此需要手动对URL重新编码。
//username=%E5%BC%A0%E4%B8%89&sub=%E7%99%BB%E9%99%86
StringBuilder sb = new StringBuilder();
if(params != null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
sb.append(entry.getKey())
.append('=')
//对客户端post请求的URL手动重新编码
.append(URLEncoder.encode(entry.getValue(), enc))
.append('&');
}
sb.deleteCharAt(sb.length()-1);
}
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5 * 1000);
//如果通过post提交数据,必须设置允许对外输出数据。
conn.setDoOutput(true);
//Content-Type: application/x-www-form-urlencoded
//Content-Length: 46 获取实体数据的二进制长度
byte [] data = sb.toString().getBytes();
//设置请求属性
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
OutputStream outputStream = conn.getOutputStream();
outputStream.write(data);
outputStream.flush();
outputStream.close();
if(conn.getResponseCode() == 200){
return true;
}
return false;
}
3.使用HttpClient开源项目提交参数给服务器
①服务器端代码
HttpServletRequest request = ServletActionContext.getRequest();
request.setCharacterEncoding("UTF-8");
System.out.println("this.title==" + this.title);
System.out.println("this.timeLength==" + this.timeLength);
②客户端代码
public boolean sendRequestByHttpClient(String path, Map<String, String> params, String enc) throws Exception{
//名值对
List<NameValuePair> paramPairs = new ArrayList<NameValuePair>();
if(params != null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
paramPairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
//对实体数据进行重新编码
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramPairs, enc);
//相当于form
HttpPost post = new HttpPost(path);
post.setEntity(entity);
//相当于客户端浏览器
DefaultHttpClient client = new DefaultHttpClient();
//执行请求
HttpResponse response = client.execute(post);
if(response.getStatusLine().getStatusCode() == 200){
return true;
}
return false;
}


三、通过HTTP协议上传文件数据
分析上传文件的HTTP协议
Content-Type: multipart/form-data; boundary=---------------------------7db1861b605fa
实体数据分隔线:用于分隔每一个请求参数
示例:
(1)定义部分:boundary=---------------------------7db1861b605fa
(2)实体数据部分: -----------------------------7db1861b605fa(多出两个--)
-----------------------------7db1861b605fa--(最后的--表示实体数据部分结束)
服务器对上传文件大小有限制,一般最大是2M(文件过大时不建议使用HTTP协议)。

/**
* 上传文件
*/
public class FormFile {
/* 上传文件的数据 */
private byte[] data;
private InputStream inStream;
private File file;
/* 文件名称 */
private String filename;
/* 请求参数名称*/
private String parameterName;
/* 内容类型 */
private String contentType = "application/octet-stream";
//上传小容量的文件建议使用此构造方法
public FormFile(String filename, byte[] data, String parameterName, String contentType) {
this.data = data;
this.filename = filename;
this.parameterName = parameterName;
if(contentType != null)
this.contentType = contentType;
}
//上传大容量的文件建议使用此构造方法
public FormFile(String filename, File file, String parameterName, String contentType) {
this.filename = filename;
this.parameterName = parameterName;
this.file = file;
try {
this.inStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(contentType != null)
this.contentType = contentType;
}

..................................

}
public static boolean post(String path, Map<String, String> params, FormFile[] files) throws Exception {
// 定义客户端socket对象
Socket socket = null;
// 定义字节输入流对象
OutputStream outStream = null;
// 定义字符输入流对象
BufferedReader reader = null;
try{
// 定义数据分隔线
final String BOUNDARY = "---------------------------7db1861b605fb";
// 定义数据结束标志
final String ENDLINE = "--" + BOUNDARY + "--\r\n";

// 获取实体数据内容及其总长度
// 定义保存文本类型实体数据的字符串
StringBuilder textEntity = new StringBuilder();
int textDataLength = 0;
// 1、获取文本类型参数的实体数据及长度
for (Map.Entry<String, String> entry : params.entrySet()) {
textEntity.append("--");
textEntity.append(BOUNDARY);
textEntity.append("\r\n");
textEntity.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");
textEntity.append(entry.getValue());
textEntity.append("\r\n");
}
byte [] textData = textEntity.toString().getBytes(enc);
textDataLength = textData.length;

int fileDataLength = 0;
// 定义保存文件类型实体数据的字符串
StringBuilder fileEntity = new StringBuilder();
byte [] fileData = null;
// 2、获取文件类型参数的实体数据及长度
for (FormFile uploadFile : files) {
fileEntity.append("--");
fileEntity.append(BOUNDARY);
fileEntity.append("\r\n");
fileEntity.append("Content-Disposition: form-data;name=\""
+ uploadFile.getParameterName() + "\";filename=\""
+ uploadFile.getFilename() + "\"\r\n");
fileEntity.append("Content-Type: " + uploadFile.getContentType() + "\r\n\r\n");
fileData = fileEntity.toString().getBytes(enc);
fileDataLength += fileData.length;
fileDataLength += "\r\n".getBytes(enc).length;
if (uploadFile.getInStream() != null) {
fileDataLength += uploadFile.getFile().length();
} else {
fileDataLength += uploadFile.getData().length;
}
}
// 计算传输给服务器的实体数据总长度
int dataLength = textDataLength + fileDataLength + ENDLINE.getBytes(enc).length;
System.out.println("dataLength: " + dataLength);

// 编写HTTP协议发送数据
URL url = new URL(destpath);
int port = url.getPort() == -1 ? 80 : url.getPort();
System.out.println("url.getHost(): " + url.getHost());
System.out.println("port: " + port);
// 创建Socket连接
socket = new Socket(InetAddress.getByName(url.getHost()), port);
// 获取输入流对象
outStream = socket.getOutputStream();
/** 下面完成HTTP请求头的发送 start **/
StringBuilder requestHead = new StringBuilder();
requestHead.append("POST " + url.getPath() + " HTTP/1.1\r\n");
requestHead.append("Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, " +
"application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, " +
"application/vnd.ms-powerpoint, application/msword, */*\r\n");
requestHead.append("Accept-Language: zh-CN\r\n");
requestHead.append("Content-Type: multipart/form-data; boundary=" + BOUNDARY + "\r\n");
requestHead.append("Host: " + url.getHost() + ":" + port + "\r\n");
requestHead.append("Content-Length: " + dataLength + "\r\n");
requestHead.append("Connection: Keep-Alive\r\n");
// 根据HTTP协议在HTTP请求头后面需要再写一个回车换行
requestHead.append("\r\n".getBytes(enc));
outStream.write(requestHead.toString().getBytes(enc));
/** 上面完成HTTP请求头的发送 end **/

/** 下面发送实体数据 start **/
// 发送所有文本类型的实体数据
outStream.write(textData);
// 发送所有文件类型的实体数据
for (FormFile uploadFile : files) {
// 发送文件类型实体数据
outStream.write(fileData);

// 发送文件数据
if (uploadFile.getInStream() != null) {
byte[] buffer = new byte[1024];
int len = 0;
while ((len = uploadFile.getInStream().read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
uploadFile.getInStream().close();
} else {
outStream.write(uploadFile.getData(), 0, uploadFile.getData().length);
}
outStream.write("\r\n".getBytes(enc));
}
// 发送数据结束标志,表示数据已经结束
outStream.write(ENDLINE.getBytes(enc));
outStream.flush();
/** 上面发送实体数据 end **/

// 判断数据发送是否成功
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String content = reader.readLine();
System.out.println("content: " + content);
// 读取web服务器返回的数据,判断请求码是否为200,如果不是200,代表请求失败
if (content.indexOf("200") == -1) {
return false;
}
} catch(Exception e){
throw e;
} finally {
if(outStream != null){
outStream.close();
}
if(reader != null){
reader.close();
}
if(socket != null){
socket.close();
}
}
return true;
}
<!-- 访问网络的权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


四、通过HTTP协议发送XML数据(作为实体数据,不是作为请求参数),并调用WebService
调用WebService时需要发送XML实体数据
1、发送XML数据给服务器
public boolean sendXML(String path, String xml) throws Exception{
byte [] data = xml.getBytes();
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5 * 1000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
OutputStream outputStream = conn.getOutputStream();
outputStream.write(data);
outputStream.flush();
outputStream.close();
if(conn.getResponseCode() == 200){
return true;
}
return false;
}

2、发送SOAP数据给服务器调用WebService,实现手机号归属地查询
SOAP协议基于XML格式
http://www.webxml.com.cn/
http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx

SOAP 1.2 请求示例。所显示的[]需替换为实际值。
POST /WebServices/MobileCodeWS.asmx HTTP/1.1
Host: webservice.webxml.com.cn
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<getMobileCodeInfo xmlns="http://WebXml.com.cn/">
<mobileCode>[string]</mobileCode>
<userID>[string]</userID>
</getMobileCodeInfo>
</soap12:Body>
</soap12:Envelope>

SOAP 1.2 响应示例。所显示的[]需替换为实际值。
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length

<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<getMobileCodeInfoResponse xmlns="http://WebXml.com.cn/">
<getMobileCodeInfoResult>[string]</getMobileCodeInfoResult>
</getMobileCodeInfoResponse>
</soap12:Body>
</soap12:Envelope>


/**
* 发送SOAP数据给服务器调用WebService,实现手机号归属地查询
* @param path
* @param soapXML
* @return
* @throws Exception
*/
public String getMobileCodeInfo(String path, InputStream inStream, String mobileCode) throws Exception{
String soapXML = readSoapXML(inStream, mobileCode);
byte [] data = soapXML.getBytes();
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5 * 1000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
OutputStream outputStream = conn.getOutputStream();
outputStream.write(data);
outputStream.flush();
outputStream.close();
if(conn.getResponseCode() == 200){
//byte [] responseData = StreamTools.readInputStream(conn.getInputStream());
//String responseXML = new String(responseData);
//return responseXML;

return parseResponseXML(conn.getInputStream());
}
return null;
}
private String parseResponseXML(InputStream inStream) throws Exception{
XmlPullParser parser = Xml.newPullParser();
parser.setInput(inStream, "UTF-8");
int eventType = parser.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT){
switch (eventType) {
case XmlPullParser.START_TAG:
String name = parser.getName();
if("getMobileCodeInfoResult".equals(name)){
return parser.nextText();
}
break;
}
eventType = parser.next();
}
return null;
}
/**
* 读取MobileCodeWS.xml(要符合SOAP协议),并且替换其中的占位符
* @param inStream
* @param mobileCode 真实手机号码
* @return
* @throws Exception
*/
public String readSoapXML(InputStream inStream, String mobileCode) throws Exception{
byte [] data = StreamTools.readInputStream(inStream);
String soapXML = new String(data);
Map<String, String> params = new HashMap<String, String>();
params.put("mobileCode", mobileCode);
//替换掉MobileCodeWS.xml中占位符处的相应内容
return replace(soapXML, params);
}
private String replace(String soapXML, Map<String, String> params) throws Exception{
String result = soapXML;
if(params != null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
String regex = "\\*"+ entry.getKey();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(result);
if(matcher.find()){
result = matcher.replaceAll(entry.getValue());
}
}
}
return result;
}


五、通过HTTP协议实现多线程断点下载
使用多线程下载文件可以提高文件下载的速度,更快完成文件的下载。多线程下载文件之所以快,是因为其抢占的服务器资源多。假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A用户使用了99条线程下载文件,那么相当于占用了99个用户的资源,如果一秒内CPU分配给每条线程的平均执行时间是10ms,A用户在服务器中一秒内就得到了990ms的执行时间,而其他用户在一秒内只有10ms的执行时间。好比一个水龙头,每秒出水量相等的情况下,放990毫秒的水肯定比放10毫秒的水要多。
1、多线程下载的实现过程:
①、首先得到下载文件的长度,然后设置本地文件的长度。
//获取下载文件的长度
int fileLength = HttpURLConnection.getContentLength();
//在本地硬盘创建和需下载文件长度相同的文件。
RandomAccessFile file = new RandomAccessFile("QQ2011.exe", "rwd");
//设置本地文件的长度和下载文件长度相同
file.setLength(fileLength);
file.close();

②、根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
a:每条线程下载的数据长度: 文件长度 % 线程数 == 0 ? 文件长度 / 线程数 : 文件长度 / 线程数 + 1
例如:文件的长度为6M,线程数为3,那么每条线程下载的数据长度为2M。
b: 每条线程从文件的什么位置开始下载到什么位置结束:
开始位置:线程id(从0开始) * 每条线程下载的数据长度
结束位置:(线程id + 1) * 每条线程下载的数据长度 - 1

③、使用HTTP的请求头字段Range指定每条线程从文件的什么位置开始下载,到什么位置下载结束。
例如指定从文件的2M位置开始下载,下载到位置(4M-1byte)结束:
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");

④、保存文件。使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQ2011.exe", "rwd");
//指定从文件的什么位置开始写入数据
threadfile.seek(2097152);

⑤示例
/**
* 多线程下载
* 从路径中获取文件名称
* @param path 下载路径
* @return
*/
public static String getFilename(String path){
return path.substring(path.lastIndexOf('/') + 1);
}
/**
* 多线程下载
* 下载文件
* @param path 下载路径
* @param threadNum 线程数
*/
public void download(String path, int threadNum) throws Exception{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
//获取要下载文件的长度
int filelength = conn.getContentLength();
//从路径中获取文件名称
String filename = getFilename(path);
File saveFile = new File(filename);
RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
//设置本地文件的长度和下载文件长度相同
accessFile.setLength(filelength);
accessFile.close();
//计算每条线程下载的数据长度
int block = filelength % threadNum == 0 ? filelength / threadNum : filelength / threadNum + 1;
for(int threadid = 0 ; threadid < threadNum ; threadid++){
new DownloadThread(url, saveFile, block, threadid).start();
}
}

/**
* 多线程下载
* 下载线程
*/
private final class DownloadThread extends Thread{
private URL url;//下载文件的url
private File saveFile;//本地文件
private int block;//每条线程下载的数据长度
private int threadid;//线程id

public DownloadThread(URL url, File saveFile, int block, int threadid) {
this.url = url;
this.saveFile = saveFile;
this.block = block;
this.threadid = threadid;
}

@Override
public void run() {
//计算开始位置公式:线程id * 每条线程下载的数据长度
//计算结束位置公式:(线程id + 1)* 每条线程下载的数据长度 - 1
int startposition = threadid * block;
int endposition = (threadid + 1 ) * block - 1;
try {
RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
//设置从什么位置开始写入数据
accessFile.seek(startposition);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);
InputStream inStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
while( (len = inStream.read(buffer)) != -1 ){
accessFile.write(buffer, 0, len);
}
inStream.close();
accessFile.close();
System.out.println("线程id:" + threadid + " 下载完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、如何实现多线程断点下载呢?
需要把每条线程下载数据的最后位置保存起来。
main.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/downloadpath"
/>

<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="http://dl_dir.qq.com/qqfile/qq/QQ2011/QQ2011Beta2.exe"
android:id="@+id/downloadpath"
/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download"
android:id="@+id/download"
/>
<!-- 进度条 -->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="20px"
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/downloadbar"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"//设置内容居中
android:id="@+id/result"
/>
</LinearLayout>

strings.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, DownloadActivity!</string>
<string name="app_name">多线程文件下载器</string>
<string name="downloadpath">下载路径</string>
<string name="download">下载</string>
<string name="sdcarderror">SDCard不存在或者写保护</string>
<string name="success">下载完成</string>
<string name="failure">下载失败</string>
</resources>

DownloadActivity:
public class DownloadActivity extends Activity {
private EditText downloadpathText;
private TextView resultView;
private ProgressBar progressBar;
// 当Handler被创建时会关联到创建它的当前线程(UI线程)的消息队列中,Handler类用于往消息队列发送消息,
// 消息队列中的消息由当前线程内部进行处理。
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//runOnUiThread();
progressBar.setProgress(msg.getData().getInt("size"));
float num = (float)progressBar.getProgress() / (float)progressBar.getMax();
int result = (int)(num * 100);
resultView.setText(result + "%");
if(progressBar.getProgress() == progressBar.getMax()){
Toast.makeText(DownloadActivity.this, R.string.success, 1).show();
}
break;
case -1:
Toast.makeText(DownloadActivity.this, R.string.failure, 1).show();
break;
}
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

downloadpathText = (EditText) this.findViewById(R.id.downloadpath);
progressBar = (ProgressBar) this.findViewById(R.id.downloadbar);
resultView = (TextView) this.findViewById(R.id.result);
Button downloadButton = (Button) this.findViewById(R.id.download);
downloadButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String downloadpath = downloadpathText.getText().toString();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
download(downloadpath, Environment.getExternalStorageDirectory());
}else{
Toast.makeText(DownloadActivity.this, R.string.sdcarderror, 1).show();
}

}
});
}
// 该方法可能会执行几秒钟(下载2、3M的文件),也有可能执行几十分钟或者更长时间(下载几百M的文件),因此会阻塞当前实例所在的主线程(UI线程)。
/* 当应用程序启动时,系统会为应用程序创建一个主线程(main)或者叫UI线程,它负责分发事件到不同的组件,包括绘画事件,完成你的应用程序与Android UI组件交互。例如,当您触摸屏幕上的一个按钮时,UI线程会把触摸事件分发到组件上,更改状态并加入事件队列,UI线程会分发请求和通知到各个组件,完成相应的动作。单线程模型的性能是非常差的,除非你的应用程序相当的简单,特别是当所有的操作都在主线程中执行,比如访问网络或数据库之类的耗时操作将会导致用户界面锁定,所有的事件将不能分发,应用程序就像死了一样,更严重的是当超过5秒时,系统就会弹出(ANR)“应用程序无响应”的对话框。如果你想看看什么效果,可以写一个简单的应用程序,在一个Button的OnClickListener中写上Thread.sleep(2000),运行程序你就会看到在应用程序回到正常状态前按钮会保持按下状态2秒,当这种情况发生时,您就会感觉到应用程序反映相当的慢。总之,我们需要保证主线程(UI线程)不被锁住,如果有耗时的操作,我们需要把它放到一个【单独的后台线程】中执行。对于显示控件的界面更新只是由UI线程负责,如果是在非UI线程更新控件的属性值,更新后的显示界面不会反映到屏幕上。怎么办? */
private void download(final String downloadpath, final File savedir) {
new Thread(new Runnable() {
public void run() {
// 在Android中不建议开启太多下载线程,因此在此处开启3个下载线程
FileDownloader loader = new FileDownloader(DownloadActivity.this, downloadpath, savedir, 3);
// 设置进度条的最大刻度为文件的长度
progressBar.setMax(loader.getFileSize());
try {
loader.download(new DownloadProgressListener() {
// 实时获知文件已经下载的数据长度
public void onDownloadSize(int size) {
// 让进度条的当前刻度等于已下载文件的数据长度
//progressBar.setProgress(loader.getFileSize());
//float num = (float)progressBar.getProgress() / (float)progressBar.getMax();
//int result = (int)(num * 100);
//resultView.setText(result + "%");
Message msg = new Message();
// 定义消息ID
msg.what = 1;
msg.getData().putInt("size", size);
// 发送消息(发送到UI线程绑定的消息队列)
handler.sendMessage(msg);
}
});
} catch (Exception e) {
//Message msg = new Message();
//msg.what = -1;
//handler.sendMessage(msg);
handler.obtainMessage(-1).sendToTarget();
}
}
}).start();
}
}


六、通过TCP/IP(Socket)协议实现断点续传上传文件(实现多用户并发访问)
断点续传
大容量文件
多用户、并发访问
1、服务器在指定端口监听。
2、客户端连接至服务器的指定端口。
3、客户端发送协议给服务器(第一次):
Content-length=69042560;filename=***.exe;sourceid=
4、服务器判断sourceid是否存在,然后判断是否存在该文件的上传记录,如果不存在sourceid,则生成sourceid,发送给客户端。
服务器发送协议给客户端(第一次)
sourceid=244242411345677;position=0
5、在客户端将sourceid与filename进行关联绑定,然后从position指定的位置开始上传。
6、如果不是第一次上传,获取上传文件的绝对路径,在客户端上传记录中寻找对应的sourceid,然后发送协议给服务器。
Content-length=897869;filename=***.exe;sourceid=244242411345677
7、服务器根据sourceid,在上传的断点记录中查找是否存在该记录,如果存在,获取最后上传的位置,发送协议给客户端。
sourceid=244242411345677;position=223
8、客户端从position位置继续上传文件。

ExecutorService:线程池
PushbackInputStream拥有一个PushBack缓冲区,通过PushbackInputStream读出数据后,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。
简单地说,该流可以把刚读过的字节退回到输入流中,以便重新再读一遍。


main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/filename"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="SPlayer.exe"
android:id="@+id/filename"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/upload"
android:id="@+id/upload"
/>
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="20px"
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/uploadbar"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/result"
/>
</LinearLayout>

strings.xml:
<resources>
<string name="hello">Hello World, UploadActivity!</string>
<string name="app_name">大视频文件断点上传</string>
<string name="filename">文件名称</string>
<string name="upload">上传</string>
<string name="sdcarderror">SDCard不存在或者写保护</string>
<string name="success">上传完成</string>
<string name="failure">上传失败</string>
<string name="fileNotExsit">文件不存在</string>
</resources>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值