運行效果圖:java
實現流程全解析:android
建立數據庫表,因而乎咱們建立一個數據庫的管理器類,繼承SQLiteOpenHelper類 重寫onCreate()與onUpgrade()方法,咱們建立的表字段以下: 正則表達式
DBOpenHelper.java:sql
package com.jay.example.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class DBOpenHelper extends SQLiteOpenHelper { public DBOpenHelper(Context context) { super(context, "downs.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { //數據庫的結構爲:表名:filedownlog 字段:id,downpath:當前下載的資源, //threadid:下載的線程id,downlength:線程下載的最後位置 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); } }
咱們須要建立什麼樣的方法呢?數據庫
- ①咱們須要一個根據URL得到每條線程當前下載長度的方法
- ②接着,當咱們的線程新開闢後,咱們須要往數據庫中插入與該線程相關參數的方法
- ③還要定義一個能夠實時更新下載文件長度的方法
- ④咱們線程下載完,還須要根據線程id,刪除對應記錄的方法
FileService.java緩存
package com.jay.example.db; import java.util.HashMap; import java.util.Map; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /* * 該類是一個業務bean類,完成數據庫的相關操做 * */ 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 cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path}); //創建一個哈希表用於存放每條線程已下載的文件長度 Map<Integer,Integer> data = new HashMap<Integer, Integer>(); //從第一條記錄開始遍歷Cursor對象 cursor.moveToFirst(); while(cursor.moveToNext()) { //把線程id與該線程已下載的長度存放到data哈希表中 data.put(cursor.getInt(0), cursor.getInt(1)); data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow("downlength"))); } cursor.close();//關閉cursor,釋放資源; db.close(); return data; } /** * 保存每條線程已經下載的文件長度 * @param path 下載的路徑 * @param map 如今的di和已經下載的長度的集合 */ public void save(String path,Map<Integer,Integer> map) { SQLiteDatabase db = openHelper.getWritableDatabase(); //開啓事務,由於此處須要插入多條數據 db.beginTransaction(); try{ //使用加強for循環遍歷數據集合 for(Map.Entry<Integer, Integer> entry : map.entrySet()) { //插入特定下載路徑特定線程ID已經下載的數據 db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)", new Object[]{path, entry.getKey(), entry.getValue()}); } //設置一個事務成功的標誌,若是成功就提交事務,若是沒調用該方法的話那麼事務回滾 //就是上面的數據庫操做撤銷 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(); } }
好了,數據庫管理器與操做類都完成了接着就該弄一個文件下載器類了,在該類中又要完成 什麼操做呢?要作的事就多了:服務器
①定義一堆變量,核心是線程池threads和同步集合ConcurrentHashMap,用於緩存線程下載長度的
②定義一個獲取線程池中線程數的方法;
③定義一個退出下載的方法,
④獲取當前文件大小的方法
⑤累計當前已下載長度的方法,這裏須要添加一個synchronized關鍵字,用來解決併發訪問的問題
⑥更新指定線程最後的下載位置,一樣也須要用同步
⑦在構造方法中完成文件下載,線程開闢等操做
⑧獲取文件名的方法:先截取提供的url最後的'/'後面的字符串,若是獲取不到,再從頭字段查找,仍是 找不到的話,就使用網卡標識數字+cpu的惟一數字生成一個16個字節的二進制做爲文件名
⑨開始下載文件的方法
⑩獲取http響應頭字段的方法
⑪打印http頭字段的方法
12.打印日誌信息的方法多線程
FileDownloadered.java:併發
package com.jay.example.service; 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.jay.example.db.FileService; public class FileDownloadered { private static final String TAG = "文件下載類"; //設置一個查log時的一個標誌 private static final int RESPONSEOK = 200; //設置響應碼爲200,表明訪問成功 private FileService fileService; //獲取本地數據庫的業務Bean private boolean exited; //中止下載的標誌 private Context context; //程序的上下文對象 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; return 0; } /** * 退出下載 * */ public void exit() { this.exited = true; //將退出的標誌設置爲true; } public boolean getExited() { return this.exited; } /** * 獲取文件的大小 * */ public int getFileSize() { return fileSize; } /** * 累計已下載的大小 * 使用同步鎖來解決併發的訪問問題 * */ protected synchronized void append(int size) { //把實時下載的長度加入到總的下載長度中 downloadedSize += size; } /** * 更新指定線程最後下載的位置 * @param threadId 線程id * @param pos 最後下載的位置 * */ protected synchronized void update(int threadId,int pos) { //把指定線程id的線程賦予最新的下載長度,之前的值會被覆蓋掉 this.data.put(threadId, pos); //更新數據庫中制定線程的下載長度 this.fileService.update(this.downloadUrl, threadId, pos); } /** * 構建文件下載器 * @param downloadUrl 下載路徑 * @param fileSaveDir 文件的保存目錄 * @param threadNum 下載線程數 * @return */ public FileDownloadered(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.mkdir(); //若是文件不存在的話指定目錄,這裏可建立多層目錄 this.threads = new DownloadThread[threadNum]; //根據下載的線程數量建立下載的線程池 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //建立遠程鏈接句柄,這裏並未真正鏈接 conn.setConnectTimeout(5000); //設置鏈接超時事件爲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的頭字段集合 //對返回的狀態碼進行判斷,用於檢查是否請求成功,返回200時執行下面的代碼 if(conn.getResponseCode() == RESPONSEOK) { this.fileSize = conn.getContentLength(); //根據響應得到文件大小 if(this.fileSize <= 0)throw new RuntimeException("不知道文件大小"); //文件長度小於等於0時拋出運行時異常 String filename = getFileName(conn); //獲取文件名稱 this.saveFile = new File(fileSaveDir,filename); //根據文件保存目錄和文件名保存文件 Map<Integer,Integer> logdata = fileService.getData(downloadUrl); //獲取下載記錄 //若是存在下載記錄 if(logdata.size() > 0) { //遍歷集合中的數據,把每條線程已下載的數據長度放入data中 for(Map.Entry<Integer, Integer> entry : logdata.entrySet()) { data.put(entry.getKey(), entry.getValue()); } } //若是已下載的數據的線程數和如今設置的線程數相同時則計算全部現場已經下載的數據總長度 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("服務器反饋出錯"); } }catch (Exception e) { print(e.toString()); //打印錯誤 throw new RuntimeException("沒法鏈接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; //若是遍歷到了返回頭末尾則退出循環 //獲取content-disposition返回字段,裏面可能包含文件名 if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){ //使用正則表達式查詢文件名 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"); //設置文件大小 if(this.fileSize>0) randOut.setLength(this.fileSize); randOut.close(); //關閉該文件,使設置生效 URL url = new URL(this.downloadUrl); 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);//初始化每條線程已經下載的數據長度爲0 } this.downloadedSize = 0; //設置已經下載的長度爲0 } for (int i = 0; i < this.threads.length; i++) {//開啓線程進行下載 int downLength = this.data.get(i+1); //經過特定的線程id獲取該線程已經下載的數據長度 //判斷線程是否已經完成下載,不然繼續下載 if(downLength < this.block && this.downloadedSize<this.fileSize){ //初始化特定id的線程 this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); //設置線程優先級,Thread.NORM_PRIORITY = 5; //Thread.MIN_PRIORITY = 1;Thread.MAX_PRIORITY = 10,數值越大優先級越高 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 notFinish = true; //下載未完成 while (notFinish) { // 循環判斷全部線程是否完成下載 Thread.sleep(900); notFinish = false; //假定所有線程下載完成 for (int i = 0; i < this.threads.length; i++){ if (this.threads[i] != null && !this.threads[i].isFinish()) { //若是發現線程未完成下載 notFinish = true; //設置標誌爲下載沒有完成 if(this.threads[i].getDownLength() == -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("文件下載異常"); } return this.downloadedSize; } /** * 獲取Http響應頭字段 * @param http * @return */ public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) { //使用LinkedHashMap保證寫入和便利的時候的順序相同,並且容許空值 Map<String, String> header = new LinkedHashMap<String, String>(); //此處使用無線循環,由於不知道頭字段的數量 for (int i = 0;; i++) { String mine = http.getHeaderField(i); //獲取第i個頭字段的值 if (mine == null) break; //沒值說明頭字段已經循環完畢了,使用break跳出循環 header.put(http.getHeaderFieldKey(i), mine); //得到第i個頭字段的鍵 } return header; } /** * 打印Http頭字段 * @param http */ public static void printResponseHeader(HttpURLConnection http){ //獲取http響應的頭字段 Map<String, String> header = getHttpResponseHeader(http); //使用加強for循環遍歷取得頭字段的值,此時遍歷的循環順序與輸入樹勳相同 for(Map.Entry<String, String> entry : header.entrySet()){ //當有鍵的時候則獲取值,若是沒有則爲空字符串 String key = entry.getKey()!=null ? entry.getKey()+ ":" : ""; print(key+ entry.getValue()); //打印鍵和值得組合 } } /** * 打印信息 * @param msg 信息字符串 * */ private static void print(String msg) { Log.i(TAG, msg); } }
這個自定義的線程類要作的事情以下:app
- ① 首先確定是要繼承Thread類啦,而後重寫Run()方法
- ② Run()方法:先判斷是否下載完成,沒有得話:打開URLConnection連接,接着RandomAccessFile 進行數據讀寫,完成時設置完成標記爲true,發生異常的話設置長度爲-1,打印異常信息
- ③打印log信息的方法
- ④判斷下載是否完成的方法(根據完成標記)
- ⑤得到已下載的內容大小
DownLoadThread.java:
package com.jay.example.service; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.util.Log; public class DownloadThread extends Thread { private static final String TAG = "下載線程類"; //定義TAG,在打印log時進行標記 private File saveFile; //下載的數據保存到的文件 private URL downUrl; //下載的URL private int block; //每條線程下載的大小 private int threadId = -1; //初始化線程id設置 private int downLength; //該線程已下載的數據長度 private boolean finish = false; //該線程是否完成下載的標誌 private FileDownloadered downloader; //文件下載器 public DownloadThread(FileDownloadered downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) { this.downUrl = downUrl; this.saveFile = saveFile; this.block = block; this.downloader = downloader; this.threadId = threadId; this.downLength = downLength; } @Override public void run() { if(downLength < block){//未下載完成 try { HttpURLConnection http = (HttpURLConnection) downUrl.openConnection(); http.setConnectTimeout(5 * 1000); http.setRequestMethod("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"); int startPos = block * (threadId - 1) + downLength;//開始位置 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]; //設置本地數據的緩存大小爲1MB int offset = 0; //每次讀取的數據量 print("Thread " + this.threadId + " start download from position "+ startPos); //打印該線程開始下載的位置 RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd"); threadfile.seek(startPos); //用戶沒有要求中止下載,同時沒有達到請求數據的末尾時會一直循環讀取數據 while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) { threadfile.write(buffer, 0, offset); //直接把數據寫入到文件中 downLength += offset; //把新線程已經寫到文件中的數據加入到下載長度中 downloader.update(this.threadId, downLength); //把該線程已經下載的數據長度更新到數據庫和內存哈希表中 downloader.append(offset); //把新下載的數據長度加入到已經下載的數據總長度中 } threadfile.close(); inStream.close(); print("Thread " + this.threadId + " download finish"); this.finish = true; //設置完成標記爲true,不管下載完成仍是用戶主動中斷下載 } catch (Exception e) { this.downLength = -1; //設置該線程已經下載的長度爲-1 print("Thread "+ this.threadId+ ":"+ e); } } } private static void print(String msg){ Log.i(TAG, msg); } /** * 下載是否完成 * @return */ public boolean isFinish() { return finish; } /** * 已經下載的內容大小 * @return 若是返回值爲-1,表明下載失敗 */ public long getDownLength() { return downLength; } }
FileDownloader中使用了DownloadProgressListener進行進度監聽, 因此這裏須要建立一個接口,同時定義一個方法的空實現:
DownloadProgressListener.java:
package com.jay.example.service; public interface DownloadProgressListener { public void onDownloadSize(int downloadedSize); }
另外調用android:enabled="false"設置組件是否可點擊, 代碼以下
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.jay.example.multhreadcontinuabledemo.MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="請輸入要下載的文件地址" /> <EditText android:id="@+id/editpath" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="http://10.13.20.32:8080/Test/twelve.mp3" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btndown" android:text="下載" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnstop" android:text="中止" android:enabled="false" /> <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/textresult" android:text="顯示實時下載的百分比" /> </LinearLayout>
最後就是咱們的MainActivity了,完成組件以及相關變量的初始化; 使用handler來完成界面的更新操做,另外耗時操做不可以在主線程中進行, 因此這裏須要開闢新的線程,這裏用Runnable實現,詳情見代碼 吧
MainActivity.java:
package com.jay.example.multhreadcontinuabledemo; import java.io.File; import com.jay.example.service.FileDownloadered; 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 MainActivity extends Activity { private EditText editpath; private Button btndown; private Button btnstop; private TextView textresult; private ProgressBar progressbar; private static final int PROCESSING = 1; //正在下載實時數據傳輸Message標誌 private static final int FAILURE = -1; //下載失敗時的Message標誌 private Handler handler = new UIHander(); private final class UIHander extends 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); //把獲取的浮點數計算結果轉換爲整數 textresult.setText(result+ "%"); //把下載的百分比顯示到界面控件上 if(progressbar.getProgress() == progressbar.getMax()){ //下載完成時提示 Toast.makeText(getApplicationContext(), "文件下載成功", 1).show(); } break; case FAILURE: //下載失敗時提示 Toast.makeText(getApplicationContext(), "文件下載失敗", 1).show(); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editpath = (EditText) findViewById(R.id.editpath); btndown = (Button) findViewById(R.id.btndown); btnstop = (Button) findViewById(R.id.btnstop); textresult = (TextView) findViewById(R.id.textresult); progressbar = (ProgressBar) findViewById(R.id.progressBar); ButtonClickListener listener = new ButtonClickListener(); btndown.setOnClickListener(listener); btnstop.setOnClickListener(listener); } private final class ButtonClickListener implements View.OnClickListener{ public void onClick(View v) { switch (v.getId()) { case R.id.btndown: String path = editpath.getText().toString(); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File saveDir = Environment.getExternalStorageDirectory(); download(path, saveDir); }else{ Toast.makeText(getApplicationContext(), "sd卡讀取失敗", 1).show(); } btndown.setEnabled(false); btnstop.setEnabled(true); break; case R.id.btnstop: exit(); btndown.setEnabled(true); btnstop.setEnabled(false); break; } } /* 因爲用戶的輸入事件(點擊button, 觸摸屏幕....)是由主線程負責處理的,若是主線程處於工做狀態, 此時用戶產生的輸入事件若是沒能在5秒內獲得處理,系統就會報「應用無響應」錯誤。 因此在主線程裏不能執行一件比較耗時的工做,不然會因主線程阻塞而沒法處理用戶的輸入事件, 致使「應用無響應」錯誤的出現。耗時的工做應該在子線程裏執行。 */ 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控件的值,更新後的值不會重繪到屏幕上 * 必定要在主線程裏更新UI控件的值,這樣才能在屏幕上顯示出來,不能在子線程中更新UI控件的值 */ private final class DownloadTask implements Runnable{ private String path; private File saveDir; private FileDownloadered loader; public DownloadTask(String path, File saveDir) { this.path = path; this.saveDir = saveDir; } /** * 退出下載 */ public void exit(){ if(loader!=null) loader.exit(); } public void run() { try { loader = new FileDownloadered(getApplicationContext(), path, saveDir, 3); progressbar.setMax(loader.getFileSize());//設置進度條的最大刻度 loader.download(new com.jay.example.service.DownloadProgressListener() { public void onDownloadSize(int size) { Message msg = new Message(); msg.what = 1; msg.getData().putInt("size", size); handler.sendMessage(msg); } }); } catch (Exception e) { e.printStackTrace(); handler.sendMessage(handler.obtainMessage(-1)); } } } } }
<!-- 訪問internet權限 --> <uses-permission android:name="android.permission.INTERNET"/> <!-- 在SDCard中建立與刪除文件權限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 往SDCard寫入數據權限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>