Android之Http协议编程03-多线程下载
2014-03-31 18:36
369 查看
在讲解http多线程下载时,我们要想清楚一个问题,那就是我们为什么要用多线程,其实这个问题,我也不太懂,因为这个涉及到操作系统里面的知识,原谅我操作系统没有学好,因此我只能说出个大概来,有什么讲错的地方,希望能够指出。我们电脑的cpu其中有个时间片轮辗转算法,来寻找cpu该执行哪个进程。它的大概意思就是每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。而多线程指的是相对于操作系统而言,可以同时执行同一个程序的不同部分的能力,其实同时是不可能的,除非是不止一个cpu,让它们进行并行运算。因此这样相对于一个线程来讲,一个程序在相同的时间内被完成的任务就比较多了,就好比一个人做事,和很多个人做事,在相同的时间内哪方做的事比较多(前提他们每个人做事效率一样)。我想这就是为什么要使用多线程的原因吧,那就是加快程序的运行速度。想了解更多这方面的知识请自行查看操作系统相关知识,接下来让我们了解下Http多线程下载需了解的知识。
1.Http Range
Http1.1以后它为我们提供了一个header field Range,有了这个Range这个之后,我们就可以获取http请求文件中的任意部分,
这也是Http多线程下载的核心。
Range 的规范定义如下:
ranges-specifier = byte-ranges-specifier
byte-ranges-specifier = bytes-unit “=” byte-range-set
byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
byte-range-spec = first-byte-pos “-” [last-byte-pos]
first-byte-pos = 1*DIGIT
last-byte-pos = 1*DIGIT
但是我们在实际请求中一般是采用:
Range: bytes=123-567 获取123字节到567字节的数据。
2.RandomAccessFile 的使用
JDK为我们提供了一个RandomAccessFile,我一般把它叫做随机文件访问类,它的用处是让我们能够随意的对文件的各个部分进行读写,具体的用法请参考API。
3.计算每条线程量和每条线程的开始位置和结束位置
多线下载,我们就需要计算出每条线程的下载量,每条线程的该在文件的哪个位置开始下载和下载到哪里结束,我下面画了一张图来帮助理解,假设:一个文件长度为8个字节,我们开三条线程来下载这个文件,那么每条线程的下载量就为:
int block=8%3==0?8/3:8/3+1;
每条下载3个字节,当然可能有疑问这样子是9个字节,当然这个是没有影响的,但我们第三条线程下载的长度已经超出了文件的长度,http就只让我们下载到文件的最大长度而已。
而开始下载的位置和结束下载的位置,我们可以这样算出:
第一条线程:
int startPosition = 0*block;
int endPosition = (0+1)*block-1;
其他两条线程一样是这样算出,只不过是0换成了1和2:
最后我们就可以推导出计算每条线程的下载量和每条线程的开始位置和结束位置的一般公式:
int block = len%theadCount==0?len/threadCount:len/threadCount+1;
int startPosition = threadId*block;
int endPosition = (thread+1)*block - 1;
下面给出我的代码,任务是点击一个按钮开始使用三条线程从csdn上下载logo到SDCard,然后点击另一个按钮把刚才下载进SDCard的图片显示在ImageView上。
main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="多线程下载"
android:onClick="Click"
/>
<Button
android:id="@+id/read"
android:layout_below="@id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="读取SDCard里的图片"
android:onClick="Click"
/>
<ImageView
android:id="@+id/imageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/read"
/>
</RelativeLayout>
MultiDownLoad.java
package com.example.multithreaddownload;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import android.os.Environment;
public class MultiDownLoad {
private long block;//每条线程下载的数据长度
private URL url;//下载url
private File localFile;//本地文件
public MultiDownLoad() {
}
public void downLoad(String path,int threadCount){
try {
url = new URL(path);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setConnectTimeout(5000);
connection.setDoInput(true);
connection.setRequestMethod("GET");
if(connection.getResponseCode() == 200){
int length = connection.getContentLength();
block = length%threadCount==0?length/threadCount:length/threadCount+1;//计算每条线程的下载量
String fileName = parsePath(path);//从path字符串中截取出文件名
//在SDCard建立一个文件,其大小和名称和下载文件的大小和名称一样的空文件。
localFile = new File(Environment.getExternalStorageDirectory(), fileName);
//随机文件访问类,它具有随意在文件的任意位置进行读写的功能。
RandomAccessFile random = new RandomAccessFile(localFile, "rwd");//建立一个文件,并对它具有读写删的功能
random.setLength(length);//设置文件的大小
random.close();
//开启threadCount条线程来下载文件
for(int i = 0;i < threadCount; i++){
new DownLoadThread(i).start();
}
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private String parsePath(String path) {
return path.substring(path.lastIndexOf("/")+1);//截取出最后一个/后的字符串也就是文件名
}
private class DownLoadThread extends Thread{
private int threadId;//线程id
private long startPosition;//开始的下载位置
private long endPosition;//结束的下载位置
public DownLoadThread(int threadId) {
this.threadId = threadId;
startPosition = block*threadId;
endPosition = block*(threadId+1)-1;
}
@Override
public void run() {
try {
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setConnectTimeout(5000);
connection.setDoInput(true);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes="+startPosition+"-"+endPosition);//设置从哪里开始读取,读取到哪里结束。
RandomAccessFile randomAccessFile = new RandomAccessFile(localFile, "rwd");
randomAccessFile.seek(startPosition);
InputStream inputStream = connection.getInputStream();
writeFile(randomAccessFile,inputStream);//将数据写入本地文件的方法
System.out.println("线程 "+(threadId+1)+" 下载完成!!!");
} catch (ProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void writeFile(RandomAccessFile randomAccessFile,
InputStream inputStream) {
System.out.println("开始写数据了");
byte[] buffer = new byte[1024];
int len = 0;
try {
while((len = inputStream.read(buffer)) != -1){
randomAccessFile.write(buffer, 0, len);//写入
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
MainActivty:
package com.example.multithreaddownload;
import java.io.File;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
private Button button = null;
private Button read = null;
private ImageView imageView = null;
private String path = "http://csdnimg.cn/www/images/csdnindex_logo.gif";
private String bitmapPath = Environment.getExternalStorageDirectory()+File.separator+"csdnindex_logo.gif";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
private void init() {
button = (Button)findViewById(R.id.button);
read = (Button)findViewById(R.id.read);
imageView = (ImageView)findViewById(R.id.imageView);
}
public void Click(View view){
int id = view.getId();
switch (id) {
case R.id.button:
new Thread(new MyRunnable()).start();
//Android 4.0以后访问网络时需要异步处理,也就是在AsyncTask或者Thread中进行
//否则会抛android.os.NetworkOnMainThreadException异常
//new MultiDownLoad().downLoad(path, 3);//开启三条线程进行下载
break;
case R.id.read:
showBitmap();//显示图片
break;
default:
break;
}
}
private void showBitmap() {
Bitmap bitmap = readSDCardBitmap(bitmapPath);//读取SDCard里的图片
if(bitmap != null){
imageView.setImageBitmap(bitmap);
}
}
private Bitmap readSDCardBitmap(String bitmapPath2) {
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){//判断SDCard是否存在
File file = new File(bitmapPath2);
if(file.exists()){//判断文件是否存在
Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath2);
return bitmap;
}
}
return null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
new MultiDownLoad().downLoad(path, 3);//开启三条线程进行下载
}
}
}
添加网络权限:
<!-- 添加网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" ></uses-permission>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" ></uses-permission>
<uses-permission android:name="android.permission.RESTART_PACKAGES" ></uses-permission>
logCat信息:
Sdcard的logo图片:
效果图如下:
Ps:后次讲解Http协议文本数据的上传。
1.Http Range
Http1.1以后它为我们提供了一个header field Range,有了这个Range这个之后,我们就可以获取http请求文件中的任意部分,
这也是Http多线程下载的核心。
Range 的规范定义如下:
ranges-specifier = byte-ranges-specifier
byte-ranges-specifier = bytes-unit “=” byte-range-set
byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
byte-range-spec = first-byte-pos “-” [last-byte-pos]
first-byte-pos = 1*DIGIT
last-byte-pos = 1*DIGIT
但是我们在实际请求中一般是采用:
Range: bytes=123-567 获取123字节到567字节的数据。
2.RandomAccessFile 的使用
JDK为我们提供了一个RandomAccessFile,我一般把它叫做随机文件访问类,它的用处是让我们能够随意的对文件的各个部分进行读写,具体的用法请参考API。
3.计算每条线程量和每条线程的开始位置和结束位置
多线下载,我们就需要计算出每条线程的下载量,每条线程的该在文件的哪个位置开始下载和下载到哪里结束,我下面画了一张图来帮助理解,假设:一个文件长度为8个字节,我们开三条线程来下载这个文件,那么每条线程的下载量就为:
int block=8%3==0?8/3:8/3+1;
每条下载3个字节,当然可能有疑问这样子是9个字节,当然这个是没有影响的,但我们第三条线程下载的长度已经超出了文件的长度,http就只让我们下载到文件的最大长度而已。
而开始下载的位置和结束下载的位置,我们可以这样算出:
第一条线程:
int startPosition = 0*block;
int endPosition = (0+1)*block-1;
其他两条线程一样是这样算出,只不过是0换成了1和2:
最后我们就可以推导出计算每条线程的下载量和每条线程的开始位置和结束位置的一般公式:
int block = len%theadCount==0?len/threadCount:len/threadCount+1;
int startPosition = threadId*block;
int endPosition = (thread+1)*block - 1;
下面给出我的代码,任务是点击一个按钮开始使用三条线程从csdn上下载logo到SDCard,然后点击另一个按钮把刚才下载进SDCard的图片显示在ImageView上。
main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="多线程下载"
android:onClick="Click"
/>
<Button
android:id="@+id/read"
android:layout_below="@id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="读取SDCard里的图片"
android:onClick="Click"
/>
<ImageView
android:id="@+id/imageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="@id/read"
/>
</RelativeLayout>
MultiDownLoad.java
package com.example.multithreaddownload;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import android.os.Environment;
public class MultiDownLoad {
private long block;//每条线程下载的数据长度
private URL url;//下载url
private File localFile;//本地文件
public MultiDownLoad() {
}
public void downLoad(String path,int threadCount){
try {
url = new URL(path);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setConnectTimeout(5000);
connection.setDoInput(true);
connection.setRequestMethod("GET");
if(connection.getResponseCode() == 200){
int length = connection.getContentLength();
block = length%threadCount==0?length/threadCount:length/threadCount+1;//计算每条线程的下载量
String fileName = parsePath(path);//从path字符串中截取出文件名
//在SDCard建立一个文件,其大小和名称和下载文件的大小和名称一样的空文件。
localFile = new File(Environment.getExternalStorageDirectory(), fileName);
//随机文件访问类,它具有随意在文件的任意位置进行读写的功能。
RandomAccessFile random = new RandomAccessFile(localFile, "rwd");//建立一个文件,并对它具有读写删的功能
random.setLength(length);//设置文件的大小
random.close();
//开启threadCount条线程来下载文件
for(int i = 0;i < threadCount; i++){
new DownLoadThread(i).start();
}
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private String parsePath(String path) {
return path.substring(path.lastIndexOf("/")+1);//截取出最后一个/后的字符串也就是文件名
}
private class DownLoadThread extends Thread{
private int threadId;//线程id
private long startPosition;//开始的下载位置
private long endPosition;//结束的下载位置
public DownLoadThread(int threadId) {
this.threadId = threadId;
startPosition = block*threadId;
endPosition = block*(threadId+1)-1;
}
@Override
public void run() {
try {
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setConnectTimeout(5000);
connection.setDoInput(true);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes="+startPosition+"-"+endPosition);//设置从哪里开始读取,读取到哪里结束。
RandomAccessFile randomAccessFile = new RandomAccessFile(localFile, "rwd");
randomAccessFile.seek(startPosition);
InputStream inputStream = connection.getInputStream();
writeFile(randomAccessFile,inputStream);//将数据写入本地文件的方法
System.out.println("线程 "+(threadId+1)+" 下载完成!!!");
} catch (ProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void writeFile(RandomAccessFile randomAccessFile,
InputStream inputStream) {
System.out.println("开始写数据了");
byte[] buffer = new byte[1024];
int len = 0;
try {
while((len = inputStream.read(buffer)) != -1){
randomAccessFile.write(buffer, 0, len);//写入
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
MainActivty:
package com.example.multithreaddownload;
import java.io.File;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
private Button button = null;
private Button read = null;
private ImageView imageView = null;
private String path = "http://csdnimg.cn/www/images/csdnindex_logo.gif";
private String bitmapPath = Environment.getExternalStorageDirectory()+File.separator+"csdnindex_logo.gif";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
private void init() {
button = (Button)findViewById(R.id.button);
read = (Button)findViewById(R.id.read);
imageView = (ImageView)findViewById(R.id.imageView);
}
public void Click(View view){
int id = view.getId();
switch (id) {
case R.id.button:
new Thread(new MyRunnable()).start();
//Android 4.0以后访问网络时需要异步处理,也就是在AsyncTask或者Thread中进行
//否则会抛android.os.NetworkOnMainThreadException异常
//new MultiDownLoad().downLoad(path, 3);//开启三条线程进行下载
break;
case R.id.read:
showBitmap();//显示图片
break;
default:
break;
}
}
private void showBitmap() {
Bitmap bitmap = readSDCardBitmap(bitmapPath);//读取SDCard里的图片
if(bitmap != null){
imageView.setImageBitmap(bitmap);
}
}
private Bitmap readSDCardBitmap(String bitmapPath2) {
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){//判断SDCard是否存在
File file = new File(bitmapPath2);
if(file.exists()){//判断文件是否存在
Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath2);
return bitmap;
}
}
return null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
new MultiDownLoad().downLoad(path, 3);//开启三条线程进行下载
}
}
}
添加网络权限:
<!-- 添加网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" ></uses-permission>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" ></uses-permission>
<uses-permission android:name="android.permission.RESTART_PACKAGES" ></uses-permission>
logCat信息:
Sdcard的logo图片:
效果图如下:
Ps:后次讲解Http协议文本数据的上传。
相关文章推荐
- linux网络基础笔记之router
- 3步解决 IIS HTTP 500 - 内部服务器错误
- 杨勇点评:到底是谁害怕监控网络化
- Windows XP网络共享访问总是弹出输入Guest密码对话框的解决
- 发生系统错误 67,找不到网络名
- [ZT]squid中实现https的透明代理
- [ZT]用OpenLdap建立网络数据库
- 如何恢复趋势科技网络版隔离区中的文件
- AutoCAD 2006网络版授权文件配置
- SQL Server安装问题网络整理版
- http Reponse
- 网络系统容错分析与NOVELLⅡ级镜像容错实施
- Linux网络安全
- android httpclient与webview cookie同步
- IIS 7.0: "HTTP Error 403.1 - Forbidden"
- 不安全上网行为威胁网络安全
- Linux网络防火墙【3】 Linux内核网络netfilter module 举例
- Ruby设置HTTP响应头
- 如何使用HttpClient来发送带客户端证书的请求,以及如何忽略掉对服务器端证书的校验
- ASIHTTPRequest系列(三):文件上传