Android 开发工具类 27_多线程下载大文件

时间:2021-05-17 14:37:48

多线程下载大文件时序图

Android 开发工具类 27_多线程下载大文件

FileDownloader.java

 package com.wangjialin.internet.service.downloader;

 import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import android.content.Context;
import android.util.Log; import com.wangjialin.internet.service.FileService; public class FileDownloader { private static final String TAG = "FileDownloader"; //设置标签,方便Logcat日志记录
private static final int RESPONSEOK = 200; //响应码为200,即访问成功
private Context context; //应用程序的上下文对象
private FileService fileService; //获取本地数据库的业务Bean
private boolean exited; //停止下载标志
private int downloadedSize = 0; //已下载文件长度
private int fileSize = 0; //原始文件长度
private DownloadThread[] threads; //根据线程数设置下载线程池
private File saveFile; //数据保存到的本地文件
private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); //缓存各线程下载的长度
private int block; //每条线程下载的长度
private String downloadUrl; //下载路径 /**
* 获取线程数
*/
public int getThreadSize() {
return threads.length; //根据数组长度返回线程数
} /**
* 退出下载
*/
public void exit(){
this.exited = true; //设置退出标志为true
}
public boolean getExited(){
return this.exited;
}
/**
* 获取文件大小
* @return
*/
public int getFileSize() {
return fileSize; //从类成员变量中获取下载文件的大小
} /**
* 累计已下载大小
* @param size
*/
protected synchronized void append(int size) { //使用同步关键字解决并发访问问题
downloadedSize += size; //把实时下载的长度加入到总下载长度中
} /**
* 更新指定线程最后下载的位置
* @param threadId 线程id
* @param pos 最后下载的位置
*/
protected synchronized void update(int threadId, int pos) {
this.data.put(threadId, pos); //把制定线程ID的线程赋予最新的下载长度,以前的值会被覆盖掉
this.fileService.update(this.downloadUrl, threadId, pos); //更新数据库中指定线程的下载长度
}
/**
* 构建文件下载器
* @param downloadUrl 下载路径
* @param fileSaveDir 文件保存目录
* @param threadNum 下载线程数
*/
public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) { try {
this.context = context; //对上下文对象赋值
this.downloadUrl = downloadUrl; //对下载的路径赋值
fileService = new FileService(this.context); //实例化数据操作业务Bean,此处需要使用Context,因为此处的数据库是应用程序私有
URL url = new URL(this.downloadUrl); //根据下载路径实例化URL
if(!fileSaveDir.exists()) fileSaveDir.mkdirs(); //如果指定的文件不存在,则创建目录,此处可以创建多层目录
this.threads = new DownloadThread[threadNum]; //根据下载的线程数创建下载线程池
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //建立一个远程连接句柄,此时尚未真正连接
conn.setConnectTimeout(5*1000); //设置连接超时时间为5秒
conn.setRequestMethod("GET"); //设置请求方式为GET
conn.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, */*"); //设置客户端可以接受的媒体类型
conn.setRequestProperty("Accept-Language", "zh-CN"); //设置客户端语言
conn.setRequestProperty("Referer", downloadUrl); //设置请求的来源页面,便于服务端进行来源统计
conn.setRequestProperty("Charset", "UTF-8"); //设置客户端编码
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); //设置用户代理
conn.setRequestProperty("Connection", "Keep-Alive"); //设置Connection的方式
conn.connect(); //和远程资源建立真正的连接,但尚无返回的数据流
printResponseHeader(conn); //答应返回的HTTP头字段集合 if (conn.getResponseCode()==RESPONSEOK) { //此处的请求会打开返回流并获取返回的状态码,用于检查是否请求成功,当返回码为200时执行下面的代码
this.fileSize = conn.getContentLength();//根据响应获取文件大小
if (this.fileSize <= 0) throw new RuntimeException("Unkown file size "); //当文件大小为小于等于零时抛出运行时异常 String filename = getFileName(conn);//获取文件名称
this.saveFile = new File(fileSaveDir, filename);//根据文件保存目录和文件名构建保存文件
Map<Integer, Integer> logdata = fileService.getData(downloadUrl);//获取下载记录 if(logdata.size()>0){//如果存在下载记录
for(Map.Entry<Integer, Integer> entry : logdata.entrySet()) //遍历集合中的数据
data.put(entry.getKey(), entry.getValue());//把各条线程已经下载的数据长度放入data中
} if(this.data.size()==this.threads.length){//如果已经下载的数据的线程数和现在设置的线程数相同时则计算所有线程已经下载的数据总长度
for (int i = 0; i < this.threads.length; i++) { //遍历每条线程已经下载的数据
this.downloadedSize += this.data.get(i+1); //计算已经下载的数据之和
}
print("已经下载的长度"+ this.downloadedSize + "个字节"); //打印出已经下载的数据总和
} this.block = (this.fileSize % this.threads.length)==0? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1; //计算每条线程下载的数据长度
}else{
print("服务器响应错误:" + conn.getResponseCode() + conn.getResponseMessage()); //打印错误
throw new RuntimeException("server response error "); //抛出运行时服务器返回异常
}
} catch (Exception e) {
print(e.toString()); //打印错误
throw new RuntimeException("Can't connection this url"); //抛出运行时无法连接的异常
}
}
/**
* 获取文件名
*/
private String getFileName(HttpURLConnection conn) {
String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1); //从下载路径的字符串中获取文件名称 if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称
for (int i = 0;; i++) { //无限循环遍历
String mine = conn.getHeaderField(i); //从返回的流中获取特定索引的头字段值
if (mine == null) break; //如果遍历到了返回头末尾这退出循环
if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){ //获取content-disposition返回头字段,里面可能会包含文件名
Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase()); //使用正则表达式查询文件名
if(m.find()) return m.group(1); //如果有符合正则表达规则的字符串
}
}
filename = UUID.randomUUID()+ ".tmp";//由网卡上的标识数字(每个网卡都有唯一的标识号)以及 CPU 时钟的唯一数字生成的的一个 16 字节的二进制作为文件名
}
return filename;
} /**
* 开始下载文件
* @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null
* @return 已下载文件大小
* @throws Exception
*/
public int download(DownloadProgressListener listener) throws Exception{ //进行下载,并抛出异常给调用者,如果有异常的话 try {
RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rwd"); //The file is opened for reading and writing. Every change of the file's content must be written synchronously to the target device.
if(this.fileSize>0) randOut.setLength(this.fileSize); //设置文件的大小
randOut.close(); //关闭该文件,使设置生效
URL url = new URL(this.downloadUrl); //A URL instance specifies the location of a resource on the internet as specified by RFC 1738
if(this.data.size() != this.threads.length){ //如果原先未曾下载或者原先的下载线程数与现在的线程数不一致
this.data.clear(); //Removes all elements from this Map, leaving it empty.
for (int i = 0; i < this.threads.length; i++) { //遍历线程池
this.data.put(i+1, 0);//初始化每条线程已经下载的数据长度为0
}
this.downloadedSize = 0; //设置已经下载的长度为0
}
for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载
int downloadedLength = this.data.get(i+1); //通过特定的线程ID获取该线程已经下载的数据长度
if(downloadedLength < this.block && this.downloadedSize < this.fileSize){//判断线程是否已经完成下载,否则继续下载
this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); //初始化特定id的线程
this.threads[i].setPriority(7); //设置线程的优先级,Thread.NORM_PRIORITY = 5 Thread.MIN_PRIORITY = 1 Thread.MAX_PRIORITY = 10
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(900);
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.downloadedSize);//通知目前已经下载完成的数据长度
}
if(downloadedSize == this.fileSize) fileService.delete(this.downloadUrl);//下载完成删除记录
} catch (Exception e) {
print(e.toString()); //打印错误
throw new Exception("File downloads error"); //抛出文件下载异常
}
return this.downloadedSize;
}
/**
* 获取Http响应头字段
* @param http HttpURLConnection对象
* @return 返回头字段的LinkedHashMap
*/
public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {
Map<String, String> header = new LinkedHashMap<String, String>(); //使用LinkedHashMap保证写入和遍历的时候的顺序相同,而且允许空值存在
for (int i = 0;; i++) { //此处为无限循环,因为不知道头字段的数量
String fieldValue = http.getHeaderField(i); //getHeaderField(int n)用于返回 第n个头字段的值。 if (fieldValue == null) break; //如果第i个字段没有值了,则表明头字段部分已经循环完毕,此处使用Break退出循环
header.put(http.getHeaderFieldKey(i), fieldValue); //getHeaderFieldKey(int n)用于返回 第n个头字段的键。
}
return header;
}
/**
* 打印 Http 头字段
* @param http HttpURLConnection对象
*/
public static void printResponseHeader(HttpURLConnection http){
Map<String, String> header = getHttpResponseHeader(http); //获取Http响应头字段
for(Map.Entry<String, String> entry : header.entrySet()){ //使用For-Each循环的方式遍历获取的头字段的值,此时遍历的循序和输入的顺序相同
String key = entry.getKey()!=null ? entry.getKey()+ ":" : ""; //当有键的时候这获取键,如果没有则为空字符串
print(key+ entry.getValue()); //答应键和值的组合
}
} /**
* 打印信息
* @param msg 信息字符串
*/
private static void print(String msg){
Log.i(TAG, msg); //使用LogCat的Information方式打印信息
}
}

FileDownloader.java

DownloadThread.java

 package com.wangjialin.internet.service.downloader;

 import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL; import android.util.Log; /**
* 下载线程,根据具体下载地址、保持到的文件、下载块的大小、已经下载的数据大小等信息进行下载
* @author Wang Jialin
*
*/
public class DownloadThread extends Thread { private static final String TAG = "DownloadThread"; //定义TAG,方便日子的打印输出
private File saveFile; //下载的数据保存到的文件
private URL downUrl; //下载的URL
private int block; //每条线程下载的大小
private int threadId = -1; //初始化线程id设置
private int downloadedLength; //该线程已经下载的数据长度
private boolean finished = false; //该线程是否完成下载的标志
private FileDownloader downloader; //文件下载器 public DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downloadedLength, int threadId) {
this.downUrl = downUrl;
this.saveFile = saveFile;
this.block = block;
this.downloader = downloader;
this.threadId = threadId;
this.downloadedLength = downloadedLength;
} @Override
public void run() { if(downloadedLength < block){//未下载完成
try {
HttpURLConnection http = (HttpURLConnection) downUrl.openConnection(); //开启HttpURLConnection连接
http.setConnectTimeout(5 * 1000); //设置连接超时时间为5秒钟
http.setRequestMethod("GET"); //设置请求的方法为GET
http.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, */*"); //设置客户端可以接受的返回数据类型
http.setRequestProperty("Accept-Language", "zh-CN"); //设置客户端使用的语言问中文
http.setRequestProperty("Referer", downUrl.toString()); //设置请求的来源,便于对访问来源进行统计
http.setRequestProperty("Charset", "UTF-8"); //设置通信编码为UTF-8
int startPos = block * (threadId - 1) + downloadedLength;//开始位置
int endPos = block * threadId -1;//结束位置
http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围,如果超过了实体数据的大小会自动返回实际的数据大小
http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); //客户端用户代理
http.setRequestProperty("Connection", "Keep-Alive"); //使用长连接 InputStream inStream = http.getInputStream(); //获取远程连接的输入流
byte[] buffer = new byte[1024]; //设置本地数据缓存的大小为1M
int offset = 0; //设置每次读取的数据量
print("Thread " + this.threadId + " starts to download from position "+ startPos); //打印该线程开始下载的位置
RandomAccessFile threadFile = new RandomAccessFile(this.saveFile, "rwd"); //If the file does not already exist then an attempt will be made to create it and it require that every update to the file's content be written synchronously to the underlying storage device.
threadFile.seek(startPos); //文件指针指向开始下载的位置 while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) { //但用户没有要求停止下载,同时没有到达请求数据的末尾时候会一直循环读取数据
threadFile.write(buffer, 0, offset); //直接把数据写到文件中
downloadedLength += offset; //把新下载的已经写到文件中的数据加入到下载长度中
downloader.update(this.threadId, downloadedLength); //把该线程已经下载的数据长度更新到数据库和内存哈希表中
downloader.append(offset); //把新下载的数据长度加入到已经下载的数据总长度中
}//该线程下载数据完毕或者下载被用户停止 threadFile.close(); //Closes this random access file stream and releases any system resources associated with the stream.
inStream.close(); //Concrete implementations of this class should free any resources during close
if(downloader.getExited())
{
print("Thread " + this.threadId + " has been paused");
}
else
{
print("Thread " + this.threadId + " download finish");
} this.finished = true; //设置完成标志为true,无论是下载完成还是用户主动中断下载
} catch (Exception e) { //出现异常
this.downloadedLength = -1; //设置该线程已经下载的长度为-1
print("Thread "+ this.threadId+ ":"+ e); //打印出异常信息
}
}
}
/**
* 打印信息
* @param msg 信息
*/
private static void print(String msg){
Log.i(TAG, msg); // 使用 Logcat 的 Information 方式打印信息
} /**
* 下载是否完成
* @return
*/
public boolean isFinished() {
return finished;
} /**
* 已经下载的内容大小
* @return 如果返回值为-1,代表下载失败
*/
public long getDownloadedLength() {
return downloadedLength;
}
}

DownloadThread

FileService.java

 package com.wangjialin.internet.service;

 import java.util.HashMap;
import java.util.Map; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; /**
* 业务Bean,实现对数据的操作
* @author Wang Jialin
*
*/
public class FileService {
private DBOpenHelper openHelper; // 声明数据库管理器 public FileService(Context context) {
openHelper = new DBOpenHelper(context); //根据上下文对象实例化数据库管理器
}
/**
* 获取特定URI的每条线程已经下载的文件长度
* @param path
* @return
*/
public Map<Integer, Integer> getData(String path){ SQLiteDatabase db = openHelper.getReadableDatabase(); //获取可读的数据库句柄,一般情况下在该操作的内部实现中其返回的其实是可写的数据库句柄
Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path}); //根据下载路径查询所有线程下载数据,返回的Cursor指向第一条记录之前
Map<Integer, Integer> data = new HashMap<Integer, Integer>(); //建立一个哈希表用于存放每条线程的已经下载的文件长度
while(cursor.moveToNext()){ //从第一条记录开始开始遍历Cursor对象
data.put(cursor.getInt(0), cursor.getInt(1)); //把线程id和该线程已下载的长度设置进data哈希表中
data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow("downlength")));
}
cursor.close(); //关闭cursor,释放资源
db.close(); //关闭数据库
return data; //返回获得的每条线程和每条线程的下载长度
}
/**
* 保存每条线程已经下载的文件长度
* @param path 下载的路径
* @param map 现在的id和已经下载的长度的集合
*/
public void save(String path, Map<Integer, Integer> map){ SQLiteDatabase db = openHelper.getWritableDatabase(); //获取可写的数据库句柄
db.beginTransaction(); //开始事务,因为此处要插入多批数据
try{
for(Map.Entry<Integer, Integer> entry : map.entrySet()){ //采用For-Each的方式遍历数据集合
db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",
new Object[]{path, entry.getKey(), entry.getValue()}); //插入特定下载路径特定线程ID已经下载的数据
}
db.setTransactionSuccessful(); //设置事务执行的标志为成功
}finally{ //此部分的代码肯定是被执行的,如果不杀死虚拟机的话
db.endTransaction(); //结束一个事务,如果事务设立了成功标志,则提交事务,否则会滚事务
}
db.close(); //关闭数据库,释放相关资源
} /**
* 实时更新每条线程已经下载的文件长度
* @param path
* @param map
*/
public void update(String path, int threadId, int pos){
SQLiteDatabase db = openHelper.getWritableDatabase(); //获取可写的数据库句柄
db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",
new Object[]{pos, path, threadId}); //更新特定下载路径下特定线程已经下载的文件长度
db.close(); //关闭数据库,释放相关的资源
} /**
* 当文件下载完成后,删除对应的下载记录
* @param path
*/
public void delete(String path){
SQLiteDatabase db = openHelper.getWritableDatabase(); //获取可写的数据库句柄
db.execSQL("delete from filedownlog where downpath=?", new Object[]{path}); //删除特定下载路径的所有线程记录
db.close(); //关闭数据库,释放资源
} }

FileService.java

DBOpenHelper.java

 package com.wangjialin.internet.service;

 import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; /**
* SQLite管理器,实现创建数据库和表,但版本变化时实现对表的数据库表的操作
* @author think
*
*/
public class DBOpenHelper extends SQLiteOpenHelper { private static final String DBNAME = "eric.db"; //设置数据库的名称
private static final int VERSION = 1; //设置数据库的版本 /**
* 通过构造方法
* @param context 应用程序的上下文对象
*/
public DBOpenHelper(Context context) {
super(context, DBNAME, null, VERSION);
} @Override
public void onCreate(SQLiteDatabase db) { //建立数据表
db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //当版本变化时系统会调用该回调方法 db.execSQL("DROP TABLE IF EXISTS filedownlog"); //此处是删除数据表,在实际的业务中一般是需要数据备份的
onCreate(db); //调用onCreate方法重新创建数据表,也可以自己根据业务需要创建新的的数据表
} }

DBOpenHelper.java

DownloadProgressListener

 package com.wangjialin.internet.service.downloader;

 /**
* 下载进度监听器
* @author Wang Jialin
*
*/
public interface DownloadProgressListener {
/**
* 下载进度监听方法 获取和处理下载点数据的大小
* @param size 数据大小
*/
public void onDownloadSize(int size);
}

DownloadProgressListener

main.xml

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<!-- 下载路径提示文字 -->
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/path"
/>
<!-- 下载路径输入框,此处为了方便测试,我们设置了默认的路径,可以根据需要在用户界面处修改 -->
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="http://192.168.1.103:8080/ServerForMultipleThreadDownloader/CNNRecordingFromWangjialin.mp3"
android:id="@+id/path"
/> <!-- 水平LinearLayout布局,包裹下载按钮和暂停按钮 -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<!-- 下载按钮,用于触发下载事件 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button"
android:id="@+id/downloadbutton"
/>
<!-- 暂停按钮,在初始状态下为不可用 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stopbutton"
android:enabled="false"
android:id="@+id/stopbutton"
/>
</LinearLayout> <!-- 水平进度条,用图形化的方式实时显示进步信息 -->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="18dp"
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/progressBar"
/> <!-- 文本框,用于显示实时下载的百分比 -->
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/resultView"
/> </LinearLayout>

main.xml

DownloaderActivity.java

 package com.wangjialin.internet.Downloader;

 import java.io.File;

 import com.wangjialin.internet.Downloader.R;
import com.wangjialin.internet.service.downloader.DownloadProgressListener;
import com.wangjialin.internet.service.downloader.FileDownloader; import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; public class DownloaderActivity extends Activity { private static final int PROCESSING = 1;
private static final int FAILURE = -1; private EditText pathText;
private TextView resultView;
private Button downloadButton;
private Button stopbutton;
private ProgressBar progressBar; private Handler handler = new UIHander(); private final class UIHander extends Handler{
/**
* 系统会自动调用的回调方法,用于处理消息事件
* Message 一般会包含消息的标志和消息的内容以及消息的处理器 Handler
*/
public void handleMessage(Message msg){
switch(msg.what){
case PROCESSING: // 下载时
// 从消息中获取已经下载的数据长度
int size = msg.getData().getInt("size");
// 设置进度条的进度
progressBar.setProgress(size);
// 计算已经下载的百分比,浮点数计算
float num = (float)progressBar.getProgress()/(float)progressBar.getMax();
// 把获得的浮点数计算结果转换为整数
int result = (int)(num * 100);
// 把下载的百分比显示在界面控件上
resultView.setText(result + "%");
if(progressBar.getProgress() == progressBar.getMax()){
Toast.makeText(getApplicationContext(), R.string.success, Toast.LENGTH_LONG).show();
}
break;
case -1: // 下载失败
Toast.makeText(getApplicationContext(), R.string.error, Toast.LENGTH_LONG).show();
break;
}
}
} /* (non-Javadoc)
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState); setContentView(R.layout.main);
pathText = (EditText)this.findViewById(R.id.path);
resultView = (TextView)this.findViewById(R.id.resultView);
downloadButton = (Button)this.findViewById(R.id.downloadbutton);
stopbutton = (Button)this.findViewById(R.id.stopbutton);
progressBar = (ProgressBar)this.findViewById(R.id.progressBar);
ButtonClickListener listener = new ButtonClickListener();
downloadButton.setOnClickListener(listener);
stopbutton.setOnClickListener(listener);
} /**
* 按钮监听器实现类
*
*/
private final class ButtonClickListener implements View.OnClickListener{ @Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.downloadbutton:
String path = pathText.getText().toString();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
// 获取 SDCard 根目录
Environment.getExternalStorageDirectory();
File saveDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
getExternalFilesDir(Environment.DIRECTORY_MOVIES);
// 下载文件
download(path, saveDir);
}else{
// 当 SDCard 不存在时
Toast.makeText(getApplicationContext(), R.string.sdcarderror, Toast.LENGTH_LONG).show();
}
downloadButton.setEnabled(false);
stopbutton.setEnabled(true);
break; case R.id.stopbutton:
exit();
downloadButton.setEnabled(true);
stopbutton.setEnabled(false);
break;
}
} private DownloadTask task; // 声明下载执行者
/**
* 退出下载
*/ public void exit(){
// 如果有下载对象,退出下载
if(task != null) task.exit();
} /**
* 下载资源,声明下载执行者并开辟线程开始下载
* 此方法运行在主线程
*/
private void download(String path, File saveDir){
// 实例化下载任务
task = new DownloadTask(path, saveDir);
// 开始下载
new Thread(task).start();
} /**
* 一定要在主线程更新 UI 控件的值,这样才能在界面上显示出来
* 不能再子线程更新 UI 控件的值
*/
private final class DownloadTask implements Runnable{
private String path;
private File saveDir;
// 文件下载器(下载线程的容器)
private FileDownloader loader;
/**
* 构造方法,实现变量初始化
*/
public DownloadTask(String path, File saveDir){
this.path = path;
this.saveDir = saveDir;
} /**
* 退出下载
*/
public void exit(){
// 如果下载器存在的话则退出下载
if(loader != null) loader.exit();
} // 开始下载,并设置下载的监听器
DownloadProgressListener downloadProgressListener = new DownloadProgressListener(){
/**
* 下载的文件长度会不断地被传入该回调方法
*/
public void onDownloadSize(int size){
Message msg = new Message();
msg.what = PROCESSING; // 设置 id 为 1
msg.getData().putInt("size", size);
handler.sendMessage(msg);
}
}; /**
* 下载线程的执行方法
*/
@Override
public void run() {
// TODO Auto-generated method stub
try{
// 初始化下载
loader = new FileDownloader(getApplicationContext(),path,saveDir,3);
progressBar.setMax(loader.getFileSize());
loader.download(downloadProgressListener);
}catch(Exception e){
// 下载失败时向消息队列发送消息
e.printStackTrace();
handler.sendMessage(handler.obtainMessage(FAILURE)); } } }
} }

DownloaderActivity

Android 开发工具类 27_多线程下载大文件