Android实现多线程断点下载

本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度。也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载,当下载完成后,自动删除本地文件。

1. 定义布局文件需要用到的属性名及内容

2. 设置用户的Internet权限和关于SD卡的权限

<span style="font-size:14px;"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.INTERNET"/></span>

3. 开始界面的布局

基本效果图如下:

用到两个TextView控件,一个EditText控件,一个Button控件,一个ProgressBar控件

需要注意的是:进度条用<ProgressBar />控件,设置sytle属性:style="?android:attr/progressBarStyleHorizontal"

4.MainActivity的主要程序如下,代码中有注释详解:

package www.csdn.net.download;

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.csdn.net.utils.StreamTools;
import android.R.integer;
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);
			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() {
					// HttpURLConnection
					try {
						// 根据下载的地址构建URL对象
						URL url = new URL(spec);
						// 通过URL对象的openConnection()方法打开连接,返回一个连接对象
						HttpURLConnection httpURLConnection = (HttpURLConnection) url
								.openConnection();
						// 设置请求的头
						httpURLConnection.setRequestMethod("GET");
						httpURLConnection.setReadTimeout(5000);
						httpURLConnection.setConnectTimeout(5000);
						// 判断是否响应成功
						if (httpURLConnection.getResponseCode() == 200) {
							// 获取下载文件的长度
							int fileLength = httpURLConnection
									.getContentLength();
							//设置进度条的最大值
							progressBar.setMax(fileLength);
							//判断sd卡是否管用
							if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
								// 保存文件
								// 外部存储设备的路径
								File sdFile = Environment
										.getExternalStorageDirectory();
								//获取文件的名称
								String fileName = spec.substring(spec.lastIndexOf("/")+1);
								//创建保存的文件
								File file = new File(sdFile, fileName);
								//创建可以随机访问对象
								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);
									// 开启线程下载
									new DownLoadThread(threadId, startIndex,
											endIndex, spec).start();
								}
							}else {
								DownloadActivity.this.runOnUiThread(new Runnable() {
									public void run() {
										Toast.makeText(DownloadActivity.this, "SD卡不管用", Toast.LENGTH_LONG).show();
									}
								});
							}
						}else {
							//在主线程中运行
							DownloadActivity.this.runOnUiThread(new Runnable() {
								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;

		/**
		 * 构造函数
		 *
		 * @param threadId
		 *            线程的序号
		 * @param startIndex
		 *            线程开始位置
		 * @param endIndex
		 * @param 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;
				}

				// 通过path路径构建URL对象
				URL url = new URL(path);
				// 通过URL对象的openConnection()方法打开连接,返回一个连接对象
				HttpURLConnection httpURLConnection = (HttpURLConnection) url
						.openConnection();
				// 设置请求的头
				httpURLConnection.setRequestMethod("GET");
				httpURLConnection.setReadTimeout(5000);
				// 设置下载文件的开始位置结束位置
				httpURLConnection.setRequestProperty("Range", "bytes="
						+ startIndex + "-" + endIndex);
				// 获取的状态码
				int code = httpURLConnection.getResponseCode();
				// 判断是否成功
				if (code == 206) {
					// 获取每个线程返回的流对象
					InputStream is = httpURLConnection.getInputStream();
					//获取文件的名称
					String fileName = path.substring(path.lastIndexOf("/")+1);
					// 根据路径创建文件
					File file = new File(sdFile, fileName);
					// 根据文件创建RandomAccessFile对象
					RandomAccessFile raf = new RandomAccessFile(file, "rwd");
					raf.seek(startIndex);
					// 定义读取的长度
					int len = 0;
					// 定义缓冲区
					byte b[] = new byte[1024 * 1024];
					int total = 0;
					// 循环读取
					while ((len = is.read(b)) != -1) {
						RandomAccessFile threadFile = new RandomAccessFile(
								new File(sdFile, threadId + ".txt"), "rwd");
						threadFile.writeBytes((startIndex + total) + "");
						threadFile.close();
						raf.write(b, 0, len);
						// 已经下载的大小
						total += len;
						//解决同步问题
						synchronized (DownloadActivity.this) {
							currentProgress += len;
							progressBar.setProgress(currentProgress);
							//计算百分比的操作 l表示long型
							final String percent = currentProgress*100l/progressBar.getMax()+"%";
							DownloadActivity.this.runOnUiThread(new Runnable() {
								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();
					runOnUiThread(new Runnable() {
						public void run() {
							Toast.makeText(DownloadActivity.this, "当前线程--" + threadId + "--下载完毕", Toast.LENGTH_LONG).show();
						}
					});
					deleteRecordFiles();
				} else {
					runOnUiThread(new Runnable() {
						public void run() {
							Toast.makeText(DownloadActivity.this, "服务器端下载错误", Toast.LENGTH_LONG).show();
						}
					});
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

	}

	// synchronized避免线程同步
	public synchronized void deleteRecordFiles() {
		File sdFile = Environment.getExternalStorageDirectory();
		threadRunning--;
		if (threadRunning == 0) {
			for (int i = 1; i <= 3; i++) {
				File recordFile = new File(sdFile, i + ".txt");
				if (recordFile.exists()) {
					// 删除文件
					recordFile.delete();
				}
				File pbFile = new File(sdFile,"pb.txt");
				if (pbFile.exists()) {
					pbFile.delete();
				}
			}
		}
	}
}

对于流的输出可以封装一个StreamTools方法,在主程序中可以应用,代码如下:

package www.csdn.net.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class StreamTools {

	public static String streamToStr(InputStream is){
		String value = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			// 定义读取的长度
			int len = 0;
			// 定义缓冲区
			byte b[] = new byte[1024];
			// 循环读取
			while ((len = is.read(b)) != -1) {
				baos.write(b, 0, len);
			}
			baos.close();
			is.close();
			value = new String(baos.toByteArray());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return value;
	}

}

5. 程序运行结果如图:

sd卡中出现的临时文件,当下载完成会自动删除:

6. 出现的bug原因可能有:

Internet权限没加,服务器没启动,访问下载路径有错,没有获取控件对象等。

如果文件下载中,进度条显示的进度是负数,可能原因是文件大小进行百分比计算时超出内存空间,解决办法:在定义百分比的时候,在100后面加上l,表示long型,即String percent = currentProgress*100l/progressBar.getMax()+"%"。

Android实现多线程断点下载

时间: 2024-10-11 07:50:45

Android实现多线程断点下载的相关文章

Android/java http多线程断点下载(附源码)

先看下项目结构: http多线程断点下载涉及到 数据库,多线程和http请求等几个模块,东西不是很多,想弄清楚也不是很困难,接下来我和大家分享下我的做法. 一.先看MainActivity.java 成员变量,主要是一些下载过程的变量和handler private String path = "http://192.168.1.3:8080/wanmei/yama.apk"; private String sdcardPath; private int threadNum = 5;

Android 多线程断点下载源码

源码下载地址   http://download.csdn.net/detail/kiduo08/7709391 Android 多线程断点下载源码

多线程断点下载原理(java代码实例演示)

其实多线程断点下载原理,很简单的,那么我们就来先了解下,如何实现多线程的断点下载,首先:你必须明白第一点,那么就是,什么是多线程下载,该知识点可以查看本博客上一篇文章,Android之多线程下载原理,断点下载呢,其实就是在这个的基础之上添加了一些东西,那么添加了什么东西了,现在来做一个详细的了解. 1.在下载的过程中,边下载,变用一个文件来记录下载的位置,也就是下载了多少的数据 1.创建文件 2.记录下载多少数据 3.存储数据 2.第二次下载的时候,就去读取文件中是否存有数据,读取上次下载的位置

后台多任务多线程断点下载

忘了上图: 多线程断点下载其实不是很难,主要就是三个方面: 1.根据文件的大小和下载线程的数量,确定每个下载线程要下载的分割文件的大小: 2.记录每个下载线程已经下载完成的进度: 3.将每个线程下载的分割的文件合并到一个文件中. 那么怎么将远程的一个文件分割成三部分来下载呢?其实在HTTP协议中,有一个Range字段,用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位,格式为:Range: bytes x - y,eg:Range: bytes=0-100 下载从第0 -

33、多线程断点下载的实现&amp;界面的更新

1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 and

java多线程断点下载原理(代码实例演示)

原文:http://www.open-open.com/lib/view/open1423214229232.html 其实多线程断点下载原理,很简单的,那么我们就来先了解下,如何实现多线程的断点下载,首先:你必须明白第一点,那么就是,什么是多线程下载,该知识点可以查看本博客上一篇文章,Android之多线程下载原理,断点下载呢,其实就是在这个的基础之上添加了一些东西,那么添加了什么东西了,现在来做一个详细的了解. 1.在下载的过程中,边下载,变用一个文件来记录下载的位置,也就是下载了多少的数据

iOS开发网络篇—大文件的多线程断点下载(转)

http://www.cnblogs.com/wendingding/p/3947550.html iOS开发网络篇—多线程断点下载 说明:本文介绍多线程断点下载.项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件.因为实现过程较为复杂,所以下面贴出完整的代码. 实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分). 项目中用到的主要

iOS开发网络篇—多线程断点下载

iOS开发网络篇—多线程断点下载 说明:本文介绍多线程断点下载.项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件.因为实现过程较为复杂,所以下面贴出完整的代码. 实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分). 项目中用到的主要类如下: 完成的实现代码如下: 主控制器中的代码: 1 #import "YYViewControl

iOS开发网络请求——大文件的多线程断点下载

iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了.网络文件传输对移动客户端而言主要分为文件的上传和下载.作为开发者从技术角度会将文件分为小文件和大文件.小文件因为文件大小比较小导致传输所需时间少传输就快,因此不太容易影响用户体验,可用的技术就多.而大文件因为文件大小比较大导致传输时间长,因此就需要考虑到各种用户体验,比如避免在上传下载文件过程中阻塞主线程影响用户体验,就需要使用到多线程技术:为了给用户友好的进度提示,因此又需要开发中跟踪数据上传和下载数据的变化:为