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