`
yuanlanjun
  • 浏览: 1184032 次
文章分类
社区版块
存档分类
最新评论

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>

分享到:
评论

相关推荐

    基于Android ContentProvider的总结详解

    1.适用场景1) ContentProvider为存储和读取数据提供了...虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferen

    Android 实例分析ContentProvider详解

    ContentProvider是android的四大组件之一,同时与SharedPreferences、IO、SQLite、网络共同构成了五种android存储技术。是android定义的一个管理访问结构化数据机制。android支持的Sqlite是不支持跨进程、跨应用访问...

    Android实例代码

    第8章、Android的数据存储和IO 8.1、使用SharedPreferences:SharedPreferences; Editor; 8.2、File存储:openFileOutput和openFileInput; 读写SD卡文件; 8.3、SQLite数据库:SQL语句; SQLiteDatabase; ...

    Android入门到精通源代码.

    第9章 Android中的数据存储 9.1 使用Preferences存储数据 9.1.1 访问Preferences的API 9.1.2 使用XML存储Preferences数据 9.2 使用文件存储数据 9.2.1 访问应用中的文件数据 9.2.2 访问设备中独立的文件数据 9.3 ...

    计算机毕业设计 - Android系统原理与开发要点详解-培训课件,保证可靠运行,计算机毕业生可参考,免费资源下载

    接着,课件详细讲解了Android开发的关键技术和要点,包括Activity和Service的生命周期管理、Intent和BroadcastReceiver的通信机制、数据存储和访问、网络编程以及UI设计等。通过结合实例和代码演示,使开发者能够...

    Android基础知识详解

    Android的系统架构 6 一、应用程序 6 二、应用程序框架 6 三、Android Runtime 7 四、系统库 7 五、Linux 内核 8 Webkit浏览器引擎简介 9 Dalvik虚拟机简介 11 什么是Dalvik虚拟机 11 Dalvik和Android系统 11 Dalvik...

    疯狂Android讲义(第2版)源代码 第6章~第9章

    第8章、Android的数据存储和IO 8.1、使用SharedPreferences:SharedPreferences; Editor; 8.2、File存储:openFileOutput和openFileInput; 读写SD卡文件; 8.3、SQLite数据库:SQL语句; SQLiteDatabase; ...

    老罗android开发视频教程全集百度网盘下载

    【第一版第九章】老罗Android开发视频--存储数据和文件(7集) 【第一版第十章】老罗Android开发视频--对话框介绍(4集) 【第一版第十一章】老罗Android开发视频--通知的使用(2集) 【第一版第十二章】老罗...

    Android4.4 访问外部存储详解及实例

    Android4.4 访问外部存储 在Android 4.4系统中,外置存储卡(SD卡)被称为二级外部存储设备(secondary storage),应用程序已无法往外置存储卡(SD卡)写入数据,并且WRITE_EXTERNAL_STORAGE只为设备上的主要外部存储...

    详解Android数据存储之Android 6.0运行时权限下文件存储的思考

    在我们做App开发的过程中基本上都会用到文件存储,所以文件存储对于我们来说是相当熟悉了,不过自从Android 6.0发布之后,基于运行时权限机制访问外置sdcard是需要动态申请权限,所以以往直接sdcard根目录上直接新建...

    疯狂Android讲义源码

     第8章 Android的数据存储和IO 306  8.1 使用SharedPreferences 307  8.1.1 SharedPreferences与Editor  简介 307  8.1.2 SharedPreferences的存储  位置和格式 308  8.1.3 读、写其他应用Shared  ...

    Google Android SDK开发范例大全(第3版) 1/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 4/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 3/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Google Android SDK开发范例大全(第3版) 5/5

    主要以范例集的方式来讲述Android的知识点,详细介绍了开发Android的人机交互界面、Android常用的开发控件、Android手机收发短信等通信服务、开发Android手机的自动服务功能和娱乐多媒体功能以及整合Android与Google...

    Android中巧妙的实现缓存详解

    SQLite是Android里常用的一种数据存储方式,在访问数据库数据时需要通过SQLiteOpenHelper。 一份好的数据库连接代码应该能解决以下几个问题:  a) 构建实例比较费资源  b) 数据库连接最好能复用  c) onUpdate等...

Global site tag (gtag.js) - Google Analytics