Android 开发工具类 27_多线程下载大文件
生活随笔
收集整理的這篇文章主要介紹了
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.javaDownloadThread.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 } DownloadThreadFileService.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.javaDBOpenHelper.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?
?
轉載于:https://www.cnblogs.com/renzimu/p/4540282.html
總結
以上是生活随笔為你收集整理的Android 开发工具类 27_多线程下载大文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HttpHandler动态生成图片
- 下一篇: MFC UI按钮多线程