这里有两个功能点。
1、下载
2、下载暂停后可以在暂停位置下载。
所以暂定涉及到的技术是,http网络请求,多线程,sqlite数据库缓存下载位置。
代码流的处理流程:从主activity按钮激发下载行为。委托DownloadTask子线程管理下载事务。DownloadTask调用下载器FileDownlodered完成下载文件。FileDownlodered调用多个DownloadThread线程的方式(多线程)从服务器下载文件块。DownloadThread在下载的时候实时把当前线程下载的情况记录到sqlite数据库中,记录下载位置。当在暂停后在启动时候直接从暂停位置开始下载。
activity ->DownloadTask ->FileDownlodered ->DownloadThread ->db;
主activity的按钮事件响应代码
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.startDownload:
String path = pathText.getText().toString();// 获取下载路径
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// 当sd卡存在时候
// getExternalStorgeDirectory();
File saveFile = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
//获取到sd卡的文本目录。也是我们下载文件的目录绝对地址。
getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
download(path, saveFile);// 依据资源path,和文件在本地存放的目录地址作为参数进行下载文件
try {
Log.i(TAG, path);
Log.i(TAG, saveFile.getCanonicalPath().toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.i(TAG, "sd卡存在,开始下载");
} else {
Log.e(TAG, getResources().getString(R.string.sdcarderror));
}
downloadButton.setEnabled(false);
stopButton.setEnabled(true);
break;
case R.id.stopDownload:
exit();
downloadButton.setEnabled(true);
stopButton.setEnabled(false);
break;
default:
break;
}
}
//这是下载方法的逻辑,委托了DownloadTask子线程进行下载管理。ui线程不可以用于延时的操作。
public void download(String path, File saveDir) {
// TODO Auto-generated method stub
task = new DownloadTask(path, saveDir);
new Thread(task).start();
}
下面是DownloadTask子线程对下载任务的管理逻辑。也是委托给FileDownlodered类进行实际的下载操作。这个类的主要功能是新建一个下载任务,并实现FileDownlodered下载类回调过来的反馈信息处理。
private class DownloadTask implements Runnable {
private final String path;
private final File saveDir;
private FileDownlodered fileDownloader;
public DownloadTask(String path, File saveDir) {
this.path = path;
this.saveDir = saveDir;
}
public void exit() {
if (fileDownloader != null) {
fileDownloader.exit();
}
}
// 下载任务含一个下载监听器。
DownloadProcessListener downloadProcessListener = new DownloadProcessListener() {
//这个监听器是监听来自下载器FileDownlodered反馈过来的下载进度信息,然后通过handler进制更新ui
@Override
public void onDownloadSize(int downloadSize) {
// TODO Auto-generated method stub
Message msg = new Message();
msg.what = PROCESSING;
msg.getData().putInt("size", downloadSize);
uiHandler.sendMessage(msg);
}
};
@Override
public void run() {
// TODO Auto-generated method stub
try {
// new一个下载器,下载器必
//须放在这里,放在构造器里面因为设计到网络请求方面延时操作可能会报ui线程延时的异常。
fileDownloader = new FileDownlodered(getApplicationContext(), path,
saveDir, 8);
progressBar.setMax(fileDownloader.getFileSize());
Log.i(TAG, Thread.currentThread().getName() + "进入下载");
// 下载器开始下载
Log.i(TAG, String.valueOf(fileDownloader
.download(downloadProcessListener)));
//这个download方法是下载器真正执行下载功能的方法,
//
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
uiHandler.sendMessage(uiHandler.obtainMessage(FAILURE));
}
}
}
FileDownlodered的download方法
这个方法主要的功能是
public int download(DownloadProcessListener listener) {
try {
RandomAccessFile randOut = new RandomAccessFile(this.saveFile,
"rwd");
if (this.fileSize > 0)
randOut.setLength(this.fileSize);
randOut.close();
URL url = new URL(this.downloadUrl);
// 如果data map中存放的数据记录数没能跟线程数一样。那么说明不同同一次下载操作,data记录的
// 数据要清零。
if (this.data.size() != this.threads.length) {
this.data.clear();
for (int i = 0; i < this.threads.length; i++)
this.data.put(i + 1, 0);
this.downloadSize = 0;
}
Log.i(TAG, "data长度 " + String.valueOf(this.data.size()));
for (int i = 0; i < this.threads.length; i++) {
int downloadedLength = this.data.get(i + 1);
if (downloadedLength < this.block
&& this.downloadSize < this.fileSize) {
// 表名这个线程的任务还没开始
this.threads[i] = new DownloadThread(this, url,
this.saveFile, this.block, this.data.get(i + 1),
i + 1);
this.threads[i].setPriority(7); this.threads[i].start();
} else {
this.threads[i] = null;// 表明线程已完成下载任务
}
}
fileService.delete(this.downloadUrl);
fileService.save(this.downloadUrl, this.data);
boolean notfinished = true;
while (notfinished) {
Thread.sleep(90);
notfinished = false;
for (int i = 0; i < this.threads.length; i++) {
if (this.threads[i] != null// 当每个线程都没下载完成的时候进入,
&& !this.threads[i].isFinished()) {
notfinished = true;
if (this.threads[i].getDownloadedLength() == -1) {
// 判定每个线程是否发生了故障,如果发生了故障重启下载
this.threads[i] = new DownloadThread(this, url,
this.saveFile, this.block,
this.data.get(i + 1), i + 1);
this.threads[i].setPriority(7);
this.threads[i].start();
}
}
}
if (listener != null)
listener.onDownloadSize(this.downloadSize);
}
if (downloadSize == this.fileSize)// 如果下载的文件大小和从http头部获取的资源大小一样说明下载完成了。
//那么将所有线程记录的信息删除。
this.fileService.delete(this.downloadUrl);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return this.downloadSize;
}
DownloadThread的run方法是直接httpconnection到远程服务器那边,然后用RandomAccessFile类可随机访问改写文件的类进行多线程下载。
@Override
public void run() {
// TODO Auto-generated method stub
// 每个线程下载各自的每一块信息。
if (downloadedLength < block) {
try {//新建一个httpurl链接
HttpURLConnection connection = (HttpURLConnection) downUrl
.openConnection();
connection.setConnectTimeout(5 * 1000);
connection.setRequestMethod("GET");
connection
.setRequestProperty(
"Accept",
"image/gif, image/jpeg,image/pjpeg,image/pjpeg,application/x-shockwave-flash,"
+ "application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application,"
+ "application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*");
connection.setRequestProperty("Accept-Language", "zh-CN");
connection.setRequestProperty("Charset", "UTF-8");
int startPos = block * (threadId - 1) + downloadedLength;// 在每个线程里面自己计算下载的起始位置和终止位置。
int endPos = block * threadId - 1;
//利用了http协议的range字段可以设置下载的位置。 connection.setRequestProperty("Range", "bytes=" + startPos
+ "-" + endPos);
connection.setRequestProperty("Connection", "Keep-Alive");
InputStream inStream = connection.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
Print("Thread: " + this.threadId
+ " start to download from position:\t" + startPos);
RandomAccessFile threadFile = new RandomAccessFile(
this.saveFile, "rwd");
threadFile.seek(startPos);
while (!this.downloader.getExited()
&& (length = inStream.read(buffer, 0, 1024)) != -1) {
//判定条件:依据downloader.getExited()是否给予了暂停信号,并当前下载块是否到了流的末尾。
threadFile.write(buffer, 0, length);
downloadedLength += length;
downloader.update(this.threadId, this.downloadedLength);//更新下载数据到sqlite数据库中,用于暂停后恢复下载位置用。
downloader.append(length);
}
threadFile.close();
inStream.close();
if (downloader.getExited())
Print("Thread: " + this.threadId + " has been paused.");
else
// 如果下载器没有暂停,
Print("Thread: " + this.threadId + " download finish");
this.finished = true;// 无任是真的下载完成还是用于中断下载,都给出下载完的信息。
} catch (Exception e) {// 链接远程资源发生故障,
// TODO: handle exception
this.downloadedLength = -1;
Print("Thread: " + this.threadId + ":" + e.toString());
}
}
}