zoukankan      html  css  js  c++  java
  • Android 开发工具类 27_多线程下载大文件

    多线程下载大文件时序图

    FileDownloader.java

      1 package com.wangjialin.internet.service.downloader;
      2 
      3 import java.io.File;
      4 import java.io.RandomAccessFile;
      5 import java.net.HttpURLConnection;
      6 import java.net.URL;
      7 import java.util.LinkedHashMap;
      8 import java.util.Map;
      9 import java.util.UUID;
     10 import java.util.concurrent.ConcurrentHashMap;
     11 import java.util.regex.Matcher;
     12 import java.util.regex.Pattern;
     13 
     14 import android.content.Context;
     15 import android.util.Log;
     16 
     17 import com.wangjialin.internet.service.FileService;
     18 
     19 public class FileDownloader {
     20     
     21     private static final String TAG = "FileDownloader";    //设置标签,方便Logcat日志记录
     22     private static final int RESPONSEOK = 200;    //响应码为200,即访问成功
     23     private Context context;    //应用程序的上下文对象
     24     private FileService fileService;    //获取本地数据库的业务Bean
     25     private boolean exited;    //停止下载标志
     26     private int downloadedSize = 0;    //已下载文件长度
     27     private int fileSize = 0;    //原始文件长度
     28     private DownloadThread[] threads;    //根据线程数设置下载线程池
     29     private File saveFile;    //数据保存到的本地文件
     30     private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();    //缓存各线程下载的长度
     31     private int block;    //每条线程下载的长度
     32     private String downloadUrl;    //下载路径
     33     
     34     /**
     35      * 获取线程数
     36      */
     37     public int getThreadSize() {
     38         return threads.length;    //根据数组长度返回线程数
     39     }
     40     
     41     /**
     42      * 退出下载
     43      */
     44     public void exit(){
     45         this.exited = true;    //设置退出标志为true
     46     }
     47     public boolean getExited(){
     48         return this.exited;
     49     }
     50     /**
     51      * 获取文件大小
     52      * @return
     53      */
     54     public int getFileSize() {
     55         return fileSize;    //从类成员变量中获取下载文件的大小
     56     }
     57     
     58     /**
     59      * 累计已下载大小
     60      * @param size
     61      */
     62     protected synchronized void append(int size) {    //使用同步关键字解决并发访问问题
     63         downloadedSize += size;    //把实时下载的长度加入到总下载长度中
     64     }
     65     
     66     /**
     67      * 更新指定线程最后下载的位置
     68      * @param threadId 线程id
     69      * @param pos 最后下载的位置
     70      */
     71     protected synchronized void update(int threadId, int pos) {
     72         this.data.put(threadId, pos);    //把制定线程ID的线程赋予最新的下载长度,以前的值会被覆盖掉
     73         this.fileService.update(this.downloadUrl, threadId, pos);    //更新数据库中指定线程的下载长度
     74     }
     75     /**
     76      * 构建文件下载器
     77      * @param downloadUrl 下载路径
     78      * @param fileSaveDir 文件保存目录
     79      * @param threadNum 下载线程数
     80      */
     81     public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {
     82         
     83         try {
     84             this.context = context;    //对上下文对象赋值
     85             this.downloadUrl = downloadUrl;    //对下载的路径赋值
     86             fileService = new FileService(this.context);    //实例化数据操作业务Bean,此处需要使用Context,因为此处的数据库是应用程序私有
     87             URL url = new URL(this.downloadUrl);    //根据下载路径实例化URL
     88             if(!fileSaveDir.exists()) fileSaveDir.mkdirs();    //如果指定的文件不存在,则创建目录,此处可以创建多层目录
     89             this.threads = new DownloadThread[threadNum];    //根据下载的线程数创建下载线程池                
     90             HttpURLConnection conn = (HttpURLConnection) url.openConnection();    //建立一个远程连接句柄,此时尚未真正连接
     91             conn.setConnectTimeout(5*1000);    //设置连接超时时间为5秒
     92             conn.setRequestMethod("GET");    //设置请求方式为GET
     93             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, */*");    //设置客户端可以接受的媒体类型
     94             conn.setRequestProperty("Accept-Language", "zh-CN");    //设置客户端语言
     95             conn.setRequestProperty("Referer", downloadUrl);     //设置请求的来源页面,便于服务端进行来源统计
     96             conn.setRequestProperty("Charset", "UTF-8");    //设置客户端编码
     97             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)");    //设置用户代理
     98             conn.setRequestProperty("Connection", "Keep-Alive");    //设置Connection的方式
     99             conn.connect();    //和远程资源建立真正的连接,但尚无返回的数据流
    100             printResponseHeader(conn);    //答应返回的HTTP头字段集合
    101             
    102             if (conn.getResponseCode()==RESPONSEOK) {    //此处的请求会打开返回流并获取返回的状态码,用于检查是否请求成功,当返回码为200时执行下面的代码
    103                 this.fileSize = conn.getContentLength();//根据响应获取文件大小
    104                 if (this.fileSize <= 0) throw new RuntimeException("Unkown file size ");    //当文件大小为小于等于零时抛出运行时异常
    105                         
    106                 String filename = getFileName(conn);//获取文件名称    
    107                 this.saveFile = new File(fileSaveDir, filename);//根据文件保存目录和文件名构建保存文件
    108                 Map<Integer, Integer> logdata = fileService.getData(downloadUrl);//获取下载记录
    109                 
    110                 if(logdata.size()>0){//如果存在下载记录
    111                     for(Map.Entry<Integer, Integer> entry : logdata.entrySet())    //遍历集合中的数据
    112                         data.put(entry.getKey(), entry.getValue());//把各条线程已经下载的数据长度放入data中
    113                 }
    114                 
    115                 if(this.data.size()==this.threads.length){//如果已经下载的数据的线程数和现在设置的线程数相同时则计算所有线程已经下载的数据总长度
    116                     for (int i = 0; i < this.threads.length; i++) {    //遍历每条线程已经下载的数据
    117                         this.downloadedSize += this.data.get(i+1);    //计算已经下载的数据之和
    118                     }
    119                     print("已经下载的长度"+ this.downloadedSize + "个字节");    //打印出已经下载的数据总和
    120                 }
    121 
    122                 this.block = (this.fileSize % this.threads.length)==0? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;    //计算每条线程下载的数据长度
    123             }else{
    124                 print("服务器响应错误:" + conn.getResponseCode() + conn.getResponseMessage());    //打印错误
    125                 throw new RuntimeException("server response error ");    //抛出运行时服务器返回异常
    126             }
    127         } catch (Exception e) {
    128             print(e.toString());    //打印错误
    129             throw new RuntimeException("Can't connection this url");    //抛出运行时无法连接的异常
    130         }
    131     }
    132     /**
    133      * 获取文件名
    134      */
    135     private String getFileName(HttpURLConnection conn) {
    136         String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);    //从下载路径的字符串中获取文件名称
    137         
    138         if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称
    139             for (int i = 0;; i++) {    //无限循环遍历
    140                 String mine = conn.getHeaderField(i);    //从返回的流中获取特定索引的头字段值
    141                 if (mine == null) break;    //如果遍历到了返回头末尾这退出循环
    142                 if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){    //获取content-disposition返回头字段,里面可能会包含文件名
    143                     Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());    //使用正则表达式查询文件名
    144                     if(m.find()) return m.group(1);    //如果有符合正则表达规则的字符串
    145                 }
    146             }
    147             filename = UUID.randomUUID()+ ".tmp";//由网卡上的标识数字(每个网卡都有唯一的标识号)以及 CPU 时钟的唯一数字生成的的一个 16 字节的二进制作为文件名
    148         }
    149         return filename;
    150     }
    151     
    152     /**
    153      *  开始下载文件
    154      * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null
    155      * @return 已下载文件大小
    156      * @throws Exception
    157      */
    158     public int download(DownloadProgressListener listener) throws Exception{    //进行下载,并抛出异常给调用者,如果有异常的话
    159         
    160         try {
    161             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.
    162             if(this.fileSize>0) randOut.setLength(this.fileSize);    //设置文件的大小
    163             randOut.close();    //关闭该文件,使设置生效
    164             URL url = new URL(this.downloadUrl);    //A URL instance specifies the location of a resource on the internet as specified by RFC 1738
    165             if(this.data.size() != this.threads.length){    //如果原先未曾下载或者原先的下载线程数与现在的线程数不一致
    166                 this.data.clear();    //Removes all elements from this Map, leaving it empty.
    167                 for (int i = 0; i < this.threads.length; i++) {    //遍历线程池
    168                     this.data.put(i+1, 0);//初始化每条线程已经下载的数据长度为0
    169                 }
    170                 this.downloadedSize = 0;    //设置已经下载的长度为0
    171             }
    172             for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载
    173                 int downloadedLength = this.data.get(i+1);    //通过特定的线程ID获取该线程已经下载的数据长度
    174                 if(downloadedLength < this.block && this.downloadedSize < this.fileSize){//判断线程是否已经完成下载,否则继续下载    
    175                     this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);    //初始化特定id的线程
    176                     this.threads[i].setPriority(7);    //设置线程的优先级,Thread.NORM_PRIORITY = 5 Thread.MIN_PRIORITY = 1 Thread.MAX_PRIORITY = 10
    177                     this.threads[i].start();    //启动线程
    178                 }else{
    179                     this.threads[i] = null;    //表明在线程已经完成下载任务
    180                 }
    181             }
    182             fileService.delete(this.downloadUrl);    //如果存在下载记录,删除它们,然后重新添加
    183             fileService.save(this.downloadUrl, this.data);    //把已经下载的实时数据写入数据库
    184             boolean notFinished = true;//下载未完成
    185             while (notFinished) {// 循环判断所有线程是否完成下载
    186                 Thread.sleep(900);
    187                 notFinished = false;//假定全部线程下载完成
    188                 for (int i = 0; i < this.threads.length; i++){
    189                     if (this.threads[i] != null && !this.threads[i].isFinished()) {//如果发现线程未完成下载
    190                         notFinished = true;//设置标志为下载没有完成
    191                         if(this.threads[i].getDownloadedLength() == -1){//如果下载失败,再重新在已经下载的数据长度的基础上下载
    192                             this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);    //重新开辟下载线程
    193                             this.threads[i].setPriority(7);    //设置下载的优先级
    194                             this.threads[i].start();    //开始下载线程
    195                         }
    196                     }
    197                 }                
    198                 if(listener!=null) listener.onDownloadSize(this.downloadedSize);//通知目前已经下载完成的数据长度
    199             }
    200             if(downloadedSize == this.fileSize) fileService.delete(this.downloadUrl);//下载完成删除记录
    201         } catch (Exception e) {
    202             print(e.toString());    //打印错误
    203             throw new Exception("File downloads error");    //抛出文件下载异常
    204         }
    205         return this.downloadedSize;
    206     }
    207     /**
    208      * 获取Http响应头字段
    209      * @param http    HttpURLConnection对象
    210      * @return    返回头字段的LinkedHashMap
    211      */
    212     public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {
    213         Map<String, String> header = new LinkedHashMap<String, String>();    //使用LinkedHashMap保证写入和遍历的时候的顺序相同,而且允许空值存在
    214         for (int i = 0;; i++) {    //此处为无限循环,因为不知道头字段的数量
    215             String fieldValue = http.getHeaderField(i);    //getHeaderField(int n)用于返回 第n个头字段的值。
    216 
    217             if (fieldValue == null) break;    //如果第i个字段没有值了,则表明头字段部分已经循环完毕,此处使用Break退出循环
    218             header.put(http.getHeaderFieldKey(i), fieldValue);    //getHeaderFieldKey(int n)用于返回 第n个头字段的键。
    219         }
    220         return header;
    221     }
    222     /**
    223      * 打印 Http 头字段
    224      * @param http HttpURLConnection对象
    225      */
    226     public static void printResponseHeader(HttpURLConnection http){
    227         Map<String, String> header = getHttpResponseHeader(http);    //获取Http响应头字段
    228         for(Map.Entry<String, String> entry : header.entrySet()){    //使用For-Each循环的方式遍历获取的头字段的值,此时遍历的循序和输入的顺序相同
    229             String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";    //当有键的时候这获取键,如果没有则为空字符串
    230             print(key+ entry.getValue());    //答应键和值的组合
    231         }
    232     }
    233     
    234     /**
    235      * 打印信息
    236      * @param msg    信息字符串
    237      */
    238     private static void print(String msg){
    239         Log.i(TAG, msg);    //使用LogCat的Information方式打印信息
    240     }
    241 }
    FileDownloader.java

    DownloadThread.java

      1 package com.wangjialin.internet.service.downloader;
      2 
      3 import java.io.File;
      4 import java.io.InputStream;
      5 import java.io.RandomAccessFile;
      6 import java.net.HttpURLConnection;
      7 import java.net.URL;
      8 
      9 import android.util.Log;
     10 
     11 /**
     12  * 下载线程,根据具体下载地址、保持到的文件、下载块的大小、已经下载的数据大小等信息进行下载
     13  * @author Wang Jialin
     14  *
     15  */
     16 public class DownloadThread extends Thread {
     17     
     18     private static final String TAG = "DownloadThread";    //定义TAG,方便日子的打印输出
     19     private File saveFile;    //下载的数据保存到的文件
     20     private URL downUrl;    //下载的URL
     21     private int block;    //每条线程下载的大小
     22     private int threadId = -1;    //初始化线程id设置
     23     private int downloadedLength;    //该线程已经下载的数据长度
     24     private boolean finished = false;    //该线程是否完成下载的标志
     25     private FileDownloader downloader;    //文件下载器
     26     
     27     public DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downloadedLength, int threadId) {
     28         this.downUrl = downUrl;
     29         this.saveFile = saveFile;
     30         this.block = block;
     31         this.downloader = downloader;
     32         this.threadId = threadId;
     33         this.downloadedLength = downloadedLength;
     34     }
     35     
     36     @Override
     37     public void run() {
     38         
     39         if(downloadedLength < block){//未下载完成
     40             try {
     41                 HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();    //开启HttpURLConnection连接
     42                 http.setConnectTimeout(5 * 1000);    //设置连接超时时间为5秒钟
     43                 http.setRequestMethod("GET");    //设置请求的方法为GET
     44                 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, */*");    //设置客户端可以接受的返回数据类型
     45                 http.setRequestProperty("Accept-Language", "zh-CN");    //设置客户端使用的语言问中文
     46                 http.setRequestProperty("Referer", downUrl.toString());     //设置请求的来源,便于对访问来源进行统计
     47                 http.setRequestProperty("Charset", "UTF-8");    //设置通信编码为UTF-8
     48                 int startPos = block * (threadId - 1) + downloadedLength;//开始位置
     49                 int endPos = block * threadId -1;//结束位置
     50                 http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围,如果超过了实体数据的大小会自动返回实际的数据大小
     51                 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)");    //客户端用户代理
     52                 http.setRequestProperty("Connection", "Keep-Alive");    //使用长连接
     53                 
     54                 InputStream inStream = http.getInputStream();    //获取远程连接的输入流
     55                 byte[] buffer = new byte[1024];    //设置本地数据缓存的大小为1M
     56                 int offset = 0;    //设置每次读取的数据量
     57                 print("Thread " + this.threadId + " starts to download from position "+ startPos);    //打印该线程开始下载的位置
     58                 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. 
     59                 threadFile.seek(startPos);    //文件指针指向开始下载的位置
     60                 
     61                 while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) {    //但用户没有要求停止下载,同时没有到达请求数据的末尾时候会一直循环读取数据
     62                     threadFile.write(buffer, 0, offset);    //直接把数据写到文件中
     63                     downloadedLength += offset;    //把新下载的已经写到文件中的数据加入到下载长度中
     64                     downloader.update(this.threadId, downloadedLength);    //把该线程已经下载的数据长度更新到数据库和内存哈希表中
     65                     downloader.append(offset);    //把新下载的数据长度加入到已经下载的数据总长度中
     66                 }//该线程下载数据完毕或者下载被用户停止
     67                 
     68                 threadFile.close();    //Closes this random access file stream and releases any system resources associated with the stream.
     69                 inStream.close();    //Concrete implementations of this class should free any resources during close
     70                 if(downloader.getExited())
     71                 {
     72                     print("Thread " + this.threadId + " has been paused");
     73                 }
     74                 else
     75                 {
     76                     print("Thread " + this.threadId + " download finish");
     77                 }
     78                 
     79                 this.finished = true;    //设置完成标志为true,无论是下载完成还是用户主动中断下载
     80             } catch (Exception e) {    //出现异常
     81                 this.downloadedLength = -1;    //设置该线程已经下载的长度为-1
     82                 print("Thread "+ this.threadId+ ":"+ e);    //打印出异常信息
     83             }
     84         }
     85     }
     86     /**
     87      * 打印信息
     88      * @param msg    信息
     89      */
     90     private static void print(String msg){
     91         Log.i(TAG, msg);    // 使用 Logcat 的 Information 方式打印信息
     92     }
     93     
     94     /**
     95      * 下载是否完成
     96      * @return
     97      */
     98     public boolean isFinished() {
     99         return finished;
    100     }
    101     
    102     /**
    103      * 已经下载的内容大小
    104      * @return 如果返回值为-1,代表下载失败
    105      */
    106     public long getDownloadedLength() {
    107         return downloadedLength;
    108     }
    109 }
    DownloadThread

    FileService.java

     1 package com.wangjialin.internet.service;
     2 
     3 import java.util.HashMap;
     4 import java.util.Map;
     5 
     6 import android.content.Context;
     7 import android.database.Cursor;
     8 import android.database.sqlite.SQLiteDatabase;
     9 
    10 
    11 /**
    12  * 业务Bean,实现对数据的操作
    13  * @author Wang Jialin
    14  *
    15  */
    16 public class FileService {
    17     private DBOpenHelper openHelper;    // 声明数据库管理器
    18 
    19     public FileService(Context context) {
    20         openHelper = new DBOpenHelper(context);    //根据上下文对象实例化数据库管理器
    21     }
    22     /**
    23      * 获取特定URI的每条线程已经下载的文件长度
    24      * @param path
    25      * @return
    26      */
    27     public Map<Integer, Integer> getData(String path){
    28         
    29         SQLiteDatabase db = openHelper.getReadableDatabase();    //获取可读的数据库句柄,一般情况下在该操作的内部实现中其返回的其实是可写的数据库句柄
    30         Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path});    //根据下载路径查询所有线程下载数据,返回的Cursor指向第一条记录之前
    31         Map<Integer, Integer> data = new HashMap<Integer, Integer>();    //建立一个哈希表用于存放每条线程的已经下载的文件长度
    32         while(cursor.moveToNext()){    //从第一条记录开始开始遍历Cursor对象
    33             data.put(cursor.getInt(0), cursor.getInt(1));    //把线程id和该线程已下载的长度设置进data哈希表中
    34             data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow("downlength")));
    35         }
    36         cursor.close();    //关闭cursor,释放资源
    37         db.close();    //关闭数据库
    38         return data;    //返回获得的每条线程和每条线程的下载长度
    39     }
    40     /**
    41      * 保存每条线程已经下载的文件长度
    42      * @param path    下载的路径
    43      * @param map 现在的id和已经下载的长度的集合
    44      */
    45     public void save(String path,  Map<Integer, Integer> map){
    46         
    47         SQLiteDatabase db = openHelper.getWritableDatabase();    //获取可写的数据库句柄
    48         db.beginTransaction();    //开始事务,因为此处要插入多批数据
    49         try{
    50             for(Map.Entry<Integer, Integer> entry : map.entrySet()){    //采用For-Each的方式遍历数据集合
    51                 db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",
    52                         new Object[]{path, entry.getKey(), entry.getValue()});    //插入特定下载路径特定线程ID已经下载的数据
    53             }
    54             db.setTransactionSuccessful();    //设置事务执行的标志为成功
    55         }finally{    //此部分的代码肯定是被执行的,如果不杀死虚拟机的话
    56             db.endTransaction();    //结束一个事务,如果事务设立了成功标志,则提交事务,否则会滚事务
    57         }
    58         db.close();    //关闭数据库,释放相关资源
    59     }
    60     
    61     /**
    62      * 实时更新每条线程已经下载的文件长度
    63      * @param path
    64      * @param map
    65      */
    66     public void update(String path, int threadId, int pos){
    67         SQLiteDatabase db = openHelper.getWritableDatabase();    //获取可写的数据库句柄
    68         db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",
    69                 new Object[]{pos, path, threadId});    //更新特定下载路径下特定线程已经下载的文件长度
    70         db.close();    //关闭数据库,释放相关的资源
    71     }
    72     
    73     /**
    74      * 当文件下载完成后,删除对应的下载记录
    75      * @param path
    76      */
    77     public void delete(String path){
    78         SQLiteDatabase db = openHelper.getWritableDatabase();    //获取可写的数据库句柄
    79         db.execSQL("delete from filedownlog where downpath=?", new Object[]{path});    //删除特定下载路径的所有线程记录
    80         db.close();    //关闭数据库,释放资源
    81     }
    82     
    83 }
    FileService.java

    DBOpenHelper.java

     1 package com.wangjialin.internet.service;
     2 
     3 import android.content.Context;
     4 import android.database.sqlite.SQLiteDatabase;
     5 import android.database.sqlite.SQLiteOpenHelper;
     6 
     7 /**
     8  * SQLite管理器,实现创建数据库和表,但版本变化时实现对表的数据库表的操作
     9  * @author think
    10  *
    11  */
    12 public class DBOpenHelper extends SQLiteOpenHelper {
    13     
    14     private static final String DBNAME = "eric.db";    //设置数据库的名称
    15     private static final int VERSION = 1;    //设置数据库的版本
    16     
    17     /**
    18      * 通过构造方法
    19      * @param context 应用程序的上下文对象
    20      */
    21     public DBOpenHelper(Context context) {
    22         super(context, DBNAME, null, VERSION);
    23     }
    24     
    25     @Override
    26     public void onCreate(SQLiteDatabase db) {    //建立数据表
    27         db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");
    28     }
    29 
    30     @Override
    31     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {    //当版本变化时系统会调用该回调方法
    32         
    33         db.execSQL("DROP TABLE IF EXISTS filedownlog");    //此处是删除数据表,在实际的业务中一般是需要数据备份的
    34         onCreate(db);    //调用onCreate方法重新创建数据表,也可以自己根据业务需要创建新的的数据表
    35     }
    36 
    37 }
    DBOpenHelper.java

     DownloadProgressListener

     1 package com.wangjialin.internet.service.downloader;
     2 
     3 /**
     4  * 下载进度监听器
     5  * @author Wang Jialin
     6  *
     7  */
     8 public interface DownloadProgressListener {
     9     /**
    10      * 下载进度监听方法 获取和处理下载点数据的大小
    11      * @param size 数据大小
    12      */
    13     public void onDownloadSize(int size);
    14 }
    DownloadProgressListener

     main.xml

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:orientation="vertical"
     4     android:layout_width="fill_parent"
     5     android:layout_height="fill_parent"
     6     >
     7     <!-- 下载路径提示文字 -->
     8     <TextView  
     9         android:layout_width="fill_parent" 
    10         android:layout_height="wrap_content" 
    11         android:text="@string/path"
    12         />
    13     <!-- 下载路径输入框,此处为了方便测试,我们设置了默认的路径,可以根据需要在用户界面处修改 -->
    14     <EditText
    15         android:layout_width="fill_parent" 
    16         android:layout_height="wrap_content" 
    17         android:text="http://192.168.1.103:8080/ServerForMultipleThreadDownloader/CNNRecordingFromWangjialin.mp3"
    18         android:id="@+id/path"
    19         />
    20     
    21     <!-- 水平LinearLayout布局,包裹下载按钮和暂停按钮 -->
    22     <LinearLayout
    23         android:orientation="horizontal"
    24         android:layout_width="fill_parent" 
    25         android:layout_height="wrap_content" 
    26         >
    27         <!-- 下载按钮,用于触发下载事件 -->
    28         <Button
    29             android:layout_width="wrap_content" 
    30             android:layout_height="wrap_content" 
    31             android:text="@string/button"
    32             android:id="@+id/downloadbutton"
    33             />
    34         <!-- 暂停按钮,在初始状态下为不可用 -->
    35         <Button
    36             android:layout_width="wrap_content" 
    37             android:layout_height="wrap_content" 
    38             android:text="@string/stopbutton"
    39             android:enabled="false"
    40             android:id="@+id/stopbutton"
    41             />
    42     </LinearLayout>
    43     
    44     
    45     <!-- 水平进度条,用图形化的方式实时显示进步信息 -->
    46     <ProgressBar
    47         android:layout_width="fill_parent" 
    48         android:layout_height="18dp" 
    49         style="?android:attr/progressBarStyleHorizontal"
    50         android:id="@+id/progressBar"
    51         />
    52     
    53     <!-- 文本框,用于显示实时下载的百分比 -->
    54        <TextView  
    55         android:layout_width="fill_parent" 
    56         android:layout_height="wrap_content" 
    57         android:gravity="center"
    58         android:id="@+id/resultView"
    59         />
    60      
    61 </LinearLayout>
    main.xml

     DownloaderActivity.java

      1 package com.wangjialin.internet.Downloader;
      2 
      3 import java.io.File;
      4 
      5 import com.wangjialin.internet.Downloader.R;
      6 import com.wangjialin.internet.service.downloader.DownloadProgressListener;
      7 import com.wangjialin.internet.service.downloader.FileDownloader;
      8 
      9 import android.app.Activity;
     10 import android.os.Bundle;
     11 import android.os.Environment;
     12 import android.os.Handler;
     13 import android.os.Message;
     14 import android.view.View;
     15 import android.widget.Button;
     16 import android.widget.EditText;
     17 import android.widget.ProgressBar;
     18 import android.widget.TextView;
     19 import android.widget.Toast;
     20 
     21 public class DownloaderActivity extends Activity {
     22 
     23     private static final int PROCESSING = 1;
     24     private static final int FAILURE = -1;
     25     
     26     private EditText  pathText;
     27     private TextView resultView;
     28     private Button downloadButton;
     29     private Button stopbutton;
     30     private ProgressBar  progressBar;
     31     
     32     private Handler  handler = new UIHander();
     33     
     34     private final class UIHander extends Handler{
     35         /**
     36          * 系统会自动调用的回调方法,用于处理消息事件
     37          * Message 一般会包含消息的标志和消息的内容以及消息的处理器 Handler
     38          */
     39         public void handleMessage(Message msg){
     40             switch(msg.what){
     41             case PROCESSING:   // 下载时
     42                 // 从消息中获取已经下载的数据长度
     43                 int size = msg.getData().getInt("size");
     44                 // 设置进度条的进度
     45                 progressBar.setProgress(size);
     46                 // 计算已经下载的百分比,浮点数计算
     47                 float num = (float)progressBar.getProgress()/(float)progressBar.getMax();
     48                 // 把获得的浮点数计算结果转换为整数
     49                 int result = (int)(num * 100);
     50                 // 把下载的百分比显示在界面控件上
     51                 resultView.setText(result + "%");
     52                 if(progressBar.getProgress() == progressBar.getMax()){
     53                     Toast.makeText(getApplicationContext(), R.string.success, Toast.LENGTH_LONG).show();
     54                 }
     55                 break;
     56             case -1:    // 下载失败
     57                 Toast.makeText(getApplicationContext(), R.string.error, Toast.LENGTH_LONG).show();
     58                 break;
     59             }            
     60         }
     61     }
     62 
     63     /* (non-Javadoc)
     64      * @see android.app.Activity#onCreate(android.os.Bundle)
     65      */
     66     @Override
     67     protected void onCreate(Bundle savedInstanceState) {
     68         // TODO Auto-generated method stub
     69         super.onCreate(savedInstanceState);
     70         
     71         setContentView(R.layout.main);
     72         pathText = (EditText)this.findViewById(R.id.path);
     73         resultView = (TextView)this.findViewById(R.id.resultView);
     74         downloadButton = (Button)this.findViewById(R.id.downloadbutton);
     75         stopbutton = (Button)this.findViewById(R.id.stopbutton);
     76         progressBar = (ProgressBar)this.findViewById(R.id.progressBar);
     77         ButtonClickListener listener = new ButtonClickListener();
     78         downloadButton.setOnClickListener(listener);
     79         stopbutton.setOnClickListener(listener);
     80     }
     81     
     82     /**
     83      * 按钮监听器实现类
     84      * 
     85      */
     86     private final class ButtonClickListener implements View.OnClickListener{
     87 
     88         @Override
     89         public void onClick(View v) {
     90             // TODO Auto-generated method stub
     91             switch(v.getId()){
     92             case R.id.downloadbutton:
     93                 String path = pathText.getText().toString();
     94                 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
     95                     // 获取 SDCard 根目录
     96                     Environment.getExternalStorageDirectory();
     97                     File saveDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
     98                     getExternalFilesDir(Environment.DIRECTORY_MOVIES);
     99                     // 下载文件
    100                     download(path, saveDir);  
    101                 }else{
    102                     // 当 SDCard 不存在时
    103                     Toast.makeText(getApplicationContext(), R.string.sdcarderror, Toast.LENGTH_LONG).show();
    104                 }
    105                 downloadButton.setEnabled(false);
    106                 stopbutton.setEnabled(true);
    107                 break;
    108                 
    109             case R.id.stopbutton:
    110                 exit();
    111                 downloadButton.setEnabled(true);
    112                 stopbutton.setEnabled(false);
    113                 break;
    114             }
    115         }
    116                 
    117         private DownloadTask task;  // 声明下载执行者
    118         /**
    119          * 退出下载
    120          */
    121         
    122         public void exit(){
    123             // 如果有下载对象,退出下载
    124             if(task != null) task.exit();
    125         }
    126         
    127         /**
    128          * 下载资源,声明下载执行者并开辟线程开始下载
    129          * 此方法运行在主线程
    130          */
    131         private void download(String path, File saveDir){
    132             // 实例化下载任务
    133             task = new DownloadTask(path, saveDir);
    134             // 开始下载
    135             new Thread(task).start();
    136         }
    137         
    138         /**
    139          * 一定要在主线程更新 UI 控件的值,这样才能在界面上显示出来
    140          * 不能再子线程更新 UI 控件的值
    141          */
    142         private final class DownloadTask implements Runnable{
    143             private String path;
    144             private File saveDir;
    145             // 文件下载器(下载线程的容器)
    146             private FileDownloader loader;  
    147             /**
    148              * 构造方法,实现变量初始化
    149              */
    150             public DownloadTask(String path, File saveDir){
    151                 this.path = path;
    152                 this.saveDir = saveDir;
    153             }
    154 
    155             /**
    156              * 退出下载
    157              */
    158             public void exit(){
    159                 // 如果下载器存在的话则退出下载
    160                 if(loader != null) loader.exit();
    161             }
    162             
    163             // 开始下载,并设置下载的监听器 
    164             DownloadProgressListener downloadProgressListener = new DownloadProgressListener(){
    165                 /**
    166                  * 下载的文件长度会不断地被传入该回调方法
    167                  */
    168                 public void onDownloadSize(int size){
    169                     Message msg = new Message();
    170                     msg.what = PROCESSING;   // 设置 id 为 1
    171                     msg.getData().putInt("size", size);
    172                     handler.sendMessage(msg);
    173                 }
    174             };
    175  
    176             /**
    177              * 下载线程的执行方法
    178              */
    179             @Override
    180             public void run() {
    181                 // TODO Auto-generated method stub
    182                 try{
    183                     // 初始化下载
    184                     loader = new FileDownloader(getApplicationContext(),path,saveDir,3);
    185                     progressBar.setMax(loader.getFileSize());
    186                     loader.download(downloadProgressListener);
    187                 }catch(Exception e){
    188                     // 下载失败时向消息队列发送消息
    189                     e.printStackTrace();
    190                     handler.sendMessage(handler.obtainMessage(FAILURE));
    191                     
    192                 }
    193                 
    194             }
    195                         
    196         }
    197     }
    198 
    199 }
    DownloaderActivity

  • 相关阅读:
    运动第六课时
    java获取json数组格式中的值
    高性能网站建设进阶指南解说 新风宇宙
    检查素数的正则表达式 新风宇宙
    A*算法(游戏中寻路算法)特别奉献php实现源码? 新风宇宙
    几个值得放在common中的函数 新风宇宙
    以x%的概率执行某段代码 新风宇宙
    战场每步操作记录的存放方法 新风宇宙
    我的个人简历(最近离职找工作) 新风宇宙
    关于腾讯截取字符串问题 新风宇宙
  • 原文地址:https://www.cnblogs.com/renzimu/p/4540282.html
Copyright © 2011-2022 走看看