Android实现多线程断点下载

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

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

Android实现多线程断点下载

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. 开始界面的布局

基本效果图如下:

Android实现多线程断点下载

用到两个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. 程序运行结果如图:

Android实现多线程断点下载Android实现多线程断点下载

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

Android实现多线程断点下载

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

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

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



Android实现多线程断点下载,布布扣,bubuko.com

Android实现多线程断点下载

上一篇:android笔记3——项目文件结构说明


下一篇:android---利用android-async-http开源项目实现网络图片查看器