01—android入门_多线程下载
1、多线程下载的简介
线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。
注意:实现多线程的条件是服务器支持单一IP多线程下载,如果不支持的话,很有可能封IP或者是只有一个线程能连接成功,多余线程被屏蔽。部分软件提供"用代理下载"方式,这种方式不会封IP。
2、多线程下载的原理
通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。
如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。例如原来有10个用户都通过单一线程与服务器相连,服务器的总带宽假设为56Kbps,则每个用户(每个线程)分到的带宽是5.6Kbps,即0.7K字节/秒。如果你同时打开两个线程与服务器连接,那么共有11个线程与服务器连接,而你获得的带宽将是56/11*2=10.2Kbps,约1.27K字节/秒,将近原来的两倍。你同时打开的线程越多,你所获取的带宽就越大(原来是这样,以后每次我都通过1K个线程连接:P)。当然,这种情况下占用的机器资源也越多。有些号称“疯狂下载”的下载工具甚至可以同时打开100个线程连接服务器。
3、多线程下载的实现过程
1)得到服务器下载文件的大小,然后在本地设置一个临时文件,服务的文件大小一致
fileSize=11;
2)开启线程的数量,每个线程下载的大小-开始位置与结束位置
threadNum=3;
threadSize=fileSize/threadNum=3;
startIndex=(threadNum-1)*threadSize;
endIndex=threadNum*threadSize-1;
3)怎么设置开始线程的位置
raf.seek指定的位置去操作。
4、多线程下载的分析
4.1在客户端创建出来一个文件,这个文件的大小和服务器上文件大小完全相同
注意:content-length代表的就是文件的大小
RandomAccessFile指定文件的大小
4.2开辟线程数量(每个线程下载的大小,每个线程下载的开始位置和结束位置)
第一个线程: (1-1)*threadSize-1
第二个线程:(2-1)*threadSize-1
依次:(n-1)*threadSize-1
注意:每个线程下载的数据需要放置到对应的文件块里面。
randomaccessfile.seek()指定文件的写操作从那个位置开始。
5、多线程下载的实现
5.1搭建布局
在相对布局中实现,请仔细阅读:
<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"
tools:context="${packageName}.${activityClass}" >
<TextView
android:id="@+id/tv_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:text="@string/tv_fileName" />
<EditText android:id="@+id/et_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/tv_url"
android:inputType="textUri"
android:text="@string/down_url"/>
<ProgressBar
android:id="@+id/pb_down"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/et_url" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/pb_down"
android:onClick="downLoadFile"
android:text="@string/down_btn" />
<TextView
android:id="@+id/tv_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignParentLeft="true"
android:text="@string/tip_pb" />
</RelativeLayout>
5.2添加访问网络的权限
<uses-permissionandroid:name="android.permission.INTERNET"/>
<uses-permissionandroid:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5.3根据Button控件中的onClick属性实现downLoadFile()方法,下载文件,判断文件的大小,请仔细阅读:
package www.youkuaiyun.com.activity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import www.youkuaiyun.com.utils.StreamTools;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class DownloadActivity extends Activity {
// 线程开启的数量
private int threadNum = 3;
private int threadRunning = 3;// 正在运行的线程
private EditText et_url;
private ProgressBar progressBar;
private TextView tv_pb;
private int currentProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
et_url = (EditText) findViewById(R.id.et_url);
progressBar = (ProgressBar) findViewById(R.id.pb_down);
tv_pb = (TextView) findViewById(R.id.tv_pb);
File sdDir=Environment.getExternalStorageDirectory();
File pbFile=new File(sdDir,"pb.txt");
InputStream is=null;
try {
if(pbFile.exists()){
is = new FileInputStream(pbFile);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(is!=null){
String value=StreamTools.streamToStr(is);
String arr[]=value.split(";");
progressBar.setMax(Integer.valueOf(arr[0]));//最大值
currentProgress=Integer.valueOf(arr[1]);//当前值
progressBar.setProgress(currentProgress);
//String percent=arr[1];
tv_pb.setText("当前进度是:"+arr[2]);//显示进度条 百分比
}
}
// 下载文件(判断文件的大小)
public void downLoadFile(View v) {
// 获取下载的路径
final String spec = et_url.getText().toString();
if (TextUtils.isEmpty(spec)) {
Toast.makeText(this, "下载的地址不能为空", Toast.LENGTH_LONG).show();
} else {
new Thread() {
public void run() {
// 访问网络的地址
// String spec =
// "http://172.16.237.124:8080/video/3GTan.png";
try {
// 根据下载的地址,构建URL对象
URL url = new URL(spec);
// 通过URL对象的openConnection()方法打开链接,返回一个链接对象
HttpURLConnection httpURLConnection = (HttpURLConnection) url
.openConnection();
// 设置请求的头
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(5000);
httpURLConnection.setReadTimeout(5000);
// 判断是否响应成功
if (httpURLConnection.getResponseCode() == 200) {
int fileLength = httpURLConnection
.getContentLength();
// 设置进度条的最大值
progressBar.setMax(fileLength);
if (Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED)) {
// System.out.println("文件的大小:" + fileLength);
// 保存文件
// String path = "D:\\android.avi";
File sdFile = Environment
.getExternalStorageDirectory();
String fileName = spec.substring(spec
.lastIndexOf("/") + 1);
File file = new File(sdFile, fileName);
// mode模型:r rw rws rwd
RandomAccessFile accessFile = new RandomAccessFile(
file, "rwd");
// 设定文件的大小
accessFile.setLength(fileLength);
accessFile.close();
// 首先计算出每个线程 下载的大小 开始位置 结束位置
int threadSize = fileLength / threadNum;// 每个线程下载的大小
for (int threadId = 1; threadId <= 3; threadId++) {
int startIndex = (threadId - 1)
* threadSize;// 开始位置的计算
int endIndex = threadId * threadSize - 1;// 结束位置的计算
// 最后一个线程
if (threadId == threadNum) {
endIndex = fileLength - 1;//
}
System.out.println("当前线程---" + threadId
+ "---开始位置:" + startIndex
+ "---结束位置:" + endIndex
+ "---每个线程的大小:" + threadSize);
// 开启线程下载调用start()
new DownLoadThread(threadId, startIndex,
endIndex, spec).start();
}
} else {
DownloadActivity.this
.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(
DownloadActivity.this,
"SDCard不可用",
Toast.LENGTH_LONG)
.show();
}
});
}
} else {
DownloadActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DownloadActivity.this,
"服务器端返回错误", Toast.LENGTH_LONG)
.show();
}
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
class DownLoadThread extends Thread {
// 成员变量
private int threadId;
private int startIndex;
private int endIndex;
private String path;
// 构造器
public DownLoadThread(int threadId, int startIndex, int endIndex,
String path) {
super();
this.threadId = threadId;// 线程序号
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;// 线程下载路径
}
@Override
public void run() {
// 可以通过每个线程去下载文件
try {
File sdFile = Environment.getExternalStorageDirectory();
// 首先要从本地文件上读取已经下载的文件开始位置
File recordFile = new File(sdFile, threadId + ".txt");
if (recordFile.exists()) {
// 读取文件的内容
InputStream is = new FileInputStream(recordFile);
String value = StreamTools.streamToStr(is);
int recordIndex = Integer.parseInt(value);//
startIndex = recordIndex;// 记录的位置复制给开始位置
}
URL url = new URL(path);// 通过path路径构建URL对象
// 通过URL对象openConnection()打开链接对象,返回HttpURLConnection对象
HttpURLConnection httpURLConnection = (HttpURLConnection) url
.openConnection();
// 设置请求的头
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setConnectTimeout(5000);
// 设置你下载文件的开始位置与结束位置
httpURLConnection.setRequestProperty("Range", "bytes="
+ startIndex + "-" + endIndex);
// 获取的状态码
int code = httpURLConnection.getResponseCode();
// System.out.println(code);
// 判断是否成功
if (code == 206) {
// 获取每个线程返回的流对象
InputStream is = httpURLConnection.getInputStream();
// 定义写入文件的路径
// String path = "D:\\android.avi";
String fileName = path.substring(path.lastIndexOf("/") + 1);
// 根据路径创建文件
File file = new File(sdFile, fileName);
// 根据路径创建它的RandomAccessFile对象
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(startIndex);
// 定义读取的长度
int length = 0;
// 定义缓冲区
byte buffer[] = new byte[1];
int total = 0;
// 循环读取
while ((length = is.read(buffer)) != -1) {
// System.out.println("当前线程--"+threadId+"---已经下载的大小是:"+total);
// System.out.println("当前线程--"+threadId+"---当前下载的位置是:"+(startIndex+total));//断点下载?
RandomAccessFile threadfile = new RandomAccessFile(
new File(sdFile, threadId + ".txt"), "rwd");
threadfile.writeBytes((startIndex + total) + "");
threadfile.close();
raf.write(buffer, 0, length);// raf.seek(startIndex);已经定位了
total += length;// 已经下载的大小
//解决同步的问题
synchronized (DownloadActivity.this) {
currentProgress += length;
progressBar.setProgress(currentProgress);
//100l中l为long
final String percent=currentProgress * 100l/ progressBar.getMax()+"%";
DownloadActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
tv_pb.setText("当前的进度是:" + percent);
}
});
RandomAccessFile pbfile=new RandomAccessFile(new File(sdFile,"pb.txt"), "rwd");
pbfile.writeBytes(progressBar.getMax()+";"+currentProgress+";"+percent);
pbfile.close();
}
}
raf.close();
is.close();
// System.out.println("当前线程---" + threadId + "---下载完毕");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DownloadActivity.this,
"当前线程---" + threadId + "---下载完毕",
Toast.LENGTH_LONG).show();
}
});
deleteRecordFiles();
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(DownloadActivity.this, "服务器端下载错误",
Toast.LENGTH_LONG).show();
}
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void deleteRecordFiles() {
File sdFile = Environment.getExternalStorageDirectory();
threadRunning--;
if (threadRunning == 0) {
for (int i = 0; i <= 3; i++) {
File recordFile = new File(sdFile, i + ".txt");
if (recordFile.exists()) {
recordFile.delete();
}
//删除pb.txt文件
File pbFile=new File(sdFile,"pb.txt");
if(pbFile.exists()){
pbFile.delete();
}
}
}
}
}
5.4最后的结果为:
6、容易出现的bug说明
1)进度条变化的太快,没有按照1%的递增进行
解决://定义缓冲区
byte buffer[] =newbyte[1024*1024];
可以把1024*1024改的小一些,如1024。
2)进度条在下载过程中出现负的百分比
解决:String percent=currentProgress * 100/ progressBar.getMax()+"";
可以把100改成100l,其中l表示long。
3)同步的问题
解决:
synchronized (DownloadActivity.this) {
currentProgress += length;
progressBar.setProgress(currentProgress);
//100l中l为long
final String percent=currentProgress * 100l/ progressBar.getMax()+"%";
DownloadActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
tv_pb.setText("当前的进度是:" + percent);
}
});
RandomAccessFile pbfile=new RandomAccessFile(new File(sdFile,"pb.txt"), "rwd");
pbfile.writeBytes(progressBar.getMax()+";"+currentProgress+";"+percent);
pbfile.close();
}
4)中断下载之后能否继续下载文件的问题
解决:
File sdDir=Environment.getExternalStorageDirectory();
File pbFile=new File(sdDir,"pb.txt");
InputStream is=null;
try {
if(pbFile.exists()){
is = new FileInputStream(pbFile);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(is!=null){
String value=StreamTools.streamToStr(is);
String arr[]=value.split(";");
progressBar.setMax(Integer.valueOf(arr[0]));//最大值
currentProgress=Integer.valueOf(arr[1]);//当前值
progressBar.setProgress(currentProgress);
//String percent=arr[1];
tv_pb.setText("当前进度是:"+arr[2]);//显示进度条 百分比
}