DownloadManager實現(xiàn)網(wǎng)絡視頻文件下載

一.DownloadManager的介紹? ? 1.Android涉及到的網(wǎng)絡數(shù)據(jù)請求,如果是零星數(shù)據(jù)钳踊、且數(shù)據(jù)量較兄缘小(幾十KB到幾百KB,1MB以內(nèi))拓瞪,一般的缴罗,可以自己使用Android原生HTTP或者第三方開源框架如Volley? ? 2.如果下載數(shù)據(jù)大,幾MB到幾百MB甚至GB量級的數(shù)據(jù)祭埂,這種情況下載任務必然耗時面氓,并且極可能需要斷點續(xù)傳兵钮,典型的,如現(xiàn)在很多手機應用市場APP舌界,給用戶提供多任務下載APP安裝文件到本地的功能掘譬,而這些APP小則幾MB大則上百MB,那么這種場景就應該考慮使用Android DownloadManager? ? 3.Android DownloadManager就是為了支持大數(shù)據(jù)呻拌、斷點續(xù)傳這些下載任務而設計的public class MainActivity extends Activity {private DownloadManager downloadManager;private long Id;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = (Button) findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// remove將依據(jù)Id號取消相應的下載任務// 可批量取消葱轩,remove(id1,id2,id3,id4,...);downloadManager.remove(Id);}});downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);// 假設從這一個鏈接下載一個大文件。Request request = new Request(Uri.parse("http://apkc.mumayi.com/2015/03/06/92/927937/xingxiangyi_V3.1.3_mumayi_00169.apk"));// 僅允許在WIFI連接情況下下載request.setAllowedNetworkTypes(Request.NETWORK_WIFI);// 通知欄中將出現(xiàn)的內(nèi)容request.setTitle("我的下載");request.setDescription("下載一個大文件");// 下載過程和下載完成后通知欄有通知消息藐握。request.setNotificationVisibility(Request.VISIBILITY_VISIBLE | Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);// 此處可以由開發(fā)者自己指定一個文件存放下載文件靴拱。// 如果不指定則Android將使用系統(tǒng)默認的// request.setDestinationUri(Uri.fromFile(new File("")));// 默認的Android系統(tǒng)下載存儲目錄request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "my.apk");// enqueue 開始啟動下載...Id = downloadManager.enqueue(request);}}添加權限:二.Android 多線程斷點下載的代碼流程解析Step1:創(chuàng)建一個用來記錄線程下載信息的表DBOpenHelper.java:public class DBOpenHelper extends SQLiteOpenHelper {? public DBOpenHelper(Context context) {? ? super(context, "downs.db", null, 1);? }? @Override? public void onCreate(SQLiteDatabase db) {? ? //數(shù)據(jù)庫的結構為:表名: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) {? ? //當版本號發(fā)生改變時調(diào)用該方法,這里刪除數(shù)據(jù)表,在實際業(yè)務中一般是要進行數(shù)據(jù)備份的? ? db.execSQL("DROP TABLE IF EXISTS filedownlog");? ? onCreate(db);? }}Step2:創(chuàng)建一個數(shù)據(jù)庫操作類? ? 1.我們需要一個根據(jù)URL獲得每條線程當前下載長度的方法? ? 2.接著,當我們的線程新開辟后,我們需要往數(shù)據(jù)庫中插入與該線程相關參數(shù)的方法? ? ? ? 3.還要定義一個可以實時更新下載文件長度的方法? ? ? ? 4.我們線程下載完,還需要根據(jù)線程id,刪除對應記錄的方法FileService.java:/* * 該類是一個業(yè)務bean類,完成數(shù)據(jù)庫的相關操作 * */public class FileService {? //聲明數(shù)據(jù)庫管理器? private DBOpenHelper openHelper;? ? //在構造方法中根據(jù)上下文對象實例化數(shù)據(jù)庫管理器? public FileService(Context context) {? ? openHelper = new DBOpenHelper(context);? }? ? /**? * 獲得指定URI的每條線程已經(jīng)下載的文件長度? * @param path? * @return? ? * */? public MapgetData(String path)? {? ? //獲得可讀數(shù)據(jù)庫句柄,通常內(nèi)部實現(xiàn)返回的其實都是可寫的數(shù)據(jù)庫句柄? ? SQLiteDatabase db = openHelper.getReadableDatabase();? ? //根據(jù)下載的路徑查詢所有現(xiàn)場的下載數(shù)據(jù),返回的Cursor指向第一條記錄之前? ? Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?",? ? ? ? new String[]{path});? ? //建立一個哈希表用于存放每條線程已下載的文件長度? ? Mapdata = new HashMap();? ? //從第一條記錄開始遍歷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;? }? ? /**? * 保存每條線程已經(jīng)下載的文件長度? * @param path 下載的路徑? * @param map 現(xiàn)在的di和已經(jīng)下載的長度的集合? */? public void save(String path,Mapmap)? {? ? SQLiteDatabase db = openHelper.getWritableDatabase();? ? //開啟事務,因為此處需要插入多條數(shù)據(jù)? ? db.beginTransaction();? ? try{? ? ? //使用增強for循環(huán)遍歷數(shù)據(jù)集合? ? ? for(Map.Entryentry : map.entrySet())? ? ? {? ? ? ? //插入特定下載路徑特定線程ID已經(jīng)下載的數(shù)據(jù)? ? ? ? db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",? ? ? ? ? ? new Object[]{path, entry.getKey(), entry.getValue()});? ? ? }? ? ? //設置一個事務成功的標志,如果成功就提交事務,如果沒調(diào)用該方法的話那么事務回滾? ? ? //就是上面的數(shù)據(jù)庫操作撤銷? ? ? db.setTransactionSuccessful();? ? }finally{? ? ? //結束一個事務? ? ? db.endTransaction();? ? }? ? db.close();? }? ? /**? * 實時更新每條線程已經(jīng)下載的文件長度? * @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();? }? }Step3:創(chuàng)建一個文件下載器類? ? 1.定義一堆變量,核心是線程池threads和同步集合ConcurrentHashMap,用于緩存線程下載長度的? ? 2.定義一個獲取線程池中線程數(shù)的方法;? ? 3.定義一個退出下載的方法,? ? 4.獲取當前文件大小的方法? ? 5.累計當前已下載長度的方法,這里需要添加一個synchronized關鍵字,用來解決并發(fā)訪問的問題? ? 6.更新指定線程最后的下載位置,同樣也需要用同步? ? 7.在構造方法中完成文件下載,線程開辟等操作? ? 8.獲取文件名的方法:? ? ? ? ? 先截取提供的url最后的'/'后面的字符串,如果獲取不到,再從頭字段查找,還是找不到的話,就使用網(wǎng)卡標識數(shù)字+cpu的唯一數(shù)字生成一個16個字節(jié)的二進制作為文件名? ? 9.開始下載文件的方法? ? 10.獲取http響應頭字段的方法? ? 11.打印http頭字段的方法? ? 12.打印日志信息的方法FileDownloadered.java:public class FileDownloadered {? ? private static final String TAG = "文件下載類";? //設置一個查log時的一個標志? private static final int RESPONSEOK = 200;? ? //設置響應碼為200,代表訪問成功? private FileService fileService;? ? ? ? //獲取本地數(shù)據(jù)庫的業(yè)務Bean? private boolean exited;? ? ? ? ? ? //停止下載的標志? private Context context;? ? ? ? ? ? //程序的上下文對象? private int downloadedSize = 0;? ? ? ? ? ? ? //已下載的文件長度? private int fileSize = 0;? ? ? ? ? //開始的文件長度? private DownloadThread[] threads;? ? ? ? //根據(jù)線程數(shù)設置下載的線程池? private File saveFile;? ? ? ? ? ? ? //數(shù)據(jù)保存到本地的文件中? private Mapdata = new ConcurrentHashMap();? //緩存?zhèn)€條線程的下載的長度? private int block;? ? ? ? ? ? ? ? ? ? ? ? ? ? //每條線程下載的長度? private String downloadUrl;? ? ? ? ? ? ? ? ? //下載的路徑? ? ? /**? * 獲取線程數(shù)? */? 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;? }? ? /**? * 累計已下載的大小? * 使用同步鎖來解決并發(fā)的訪問問題? * */? 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);? ? //更新數(shù)據(jù)庫中制定線程的下載長度? ? this.fileService.update(this.downloadUrl, threadId, pos);? }? ? ? /**? * 構建文件下載器? * @param downloadUrl 下載路徑? * @param fileSaveDir 文件的保存目錄? * @param threadNum? 下載線程數(shù)? * @return? ? */? public FileDownloadered(Context context,String downloadUrl,File fileSaveDir,int threadNum)? {? ? try {? ? ? this.context = context;? ? //獲取上下文對象,賦值? ? ? this.downloadUrl = downloadUrl;? //為下載路徑賦值? ? ? fileService = new FileService(this.context);? //實例化數(shù)據(jù)庫操作的業(yè)務Bean類,需要傳一個context值? ? ? URL url = new URL(this.downloadUrl);? ? //根據(jù)下載路徑實例化URL? ? ? if(!fileSaveDir.exists()) fileSaveDir.mkdir();? //如果文件不存在的話指定目錄,這里可創(chuàng)建多層目錄? ? ? this.threads = new DownloadThread[threadNum];? //根據(jù)下載的線程數(shù)量創(chuàng)建下載的線程池? ? ? ? ? ? ? ? ? HttpURLConnection conn = (HttpURLConnection) url.openConnection();? //創(chuàng)建遠程連接句柄,這里并未真正連接? ? ? 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);? ? //設置請求的來源頁面,便于服務端進行來源統(tǒng)計? ? ? 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();? ? ? //和遠程資源建立正在的鏈接,但尚無返回的數(shù)據(jù)流? ? ? printResponseHeader(conn);? //打印返回的Http的頭字段集合? ? ? //對返回的狀態(tài)碼進行判斷,用于檢查是否請求成功,返回200時執(zhí)行下面的代碼? ? ? if(conn.getResponseCode() == RESPONSEOK)? ? ? {? ? ? ? this.fileSize = conn.getContentLength();? //根據(jù)響應獲得文件大小? ? ? ? if(this.fileSize <= 0)throw new RuntimeException("不知道文件大小");? //文件長度小于等于0時拋出運行時異常? ? ? ? String filename = getFileName(conn);? ? ? //獲取文件名稱? ? ? ? this.saveFile = new File(fileSaveDir,filename);? //根據(jù)文件保存目錄和文件名保存文件? ? ? ? Maplogdata = fileService.getData(downloadUrl);? ? //獲取下載記錄? ? ? ? //如果存在下載記錄? ? ? ? if(logdata.size() > 0)? ? ? ? {? ? ? ? ? //遍歷集合中的數(shù)據(jù),把每條線程已下載的數(shù)據(jù)長度放入data中? ? ? ? ? for(Map.Entryentry : logdata.entrySet())? ? ? ? ? {? ? ? ? ? ? data.put(entry.getKey(), entry.getValue());? ? ? ? ? }? ? ? ? }? ? ? ? //如果已下載的數(shù)據(jù)的線程數(shù)和現(xiàn)在設置的線程數(shù)相同時則計算所有現(xiàn)場已經(jīng)下載的數(shù)據(jù)總長度? ? ? ? if(this.data.size() == this.threads.length)? ? ? ? {? ? ? ? ? //遍歷每條線程已下載的數(shù)據(jù)? ? ? ? ? for(int i = 0;i < this.threads.length;i++)? ? ? ? ? {? ? ? ? ? ? this.downloadedSize += this.data.get(i+1);? ? ? ? ? }? ? ? ? ? print("已下載的長度" + this.downloadedSize + "個字節(jié)");? ? ? ? }? ? ? ? //使用條件運算符求出每個線程需要下載的數(shù)據(jù)長度? ? ? ? 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++)? //使用無限循環(huán)遍歷? ? {? ? ? String mine = conn.getHeaderField(i);? ? //從返回的流中獲取特定索引的頭字段的值? ? ? if (mine == null) break;? ? ? ? ? //如果遍歷到了返回頭末尾則退出循環(huán)? ? ? //獲取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);? ? //如果有符合正則表達式規(guī)則的字符串,返回? ? ? }? ? }? ? filename = UUID.randomUUID()+ ".tmp";//如果都沒找到的話,默認取一個文件名? ? //由網(wǎng)卡標識數(shù)字(每個網(wǎng)卡都有唯一的標識號)以及CPU時間的唯一數(shù)字生成的一個16字節(jié)的二進制作為文件名? ? }? ? ? return filename;? }? ? /**? *? 開始下載文件? * @param listener 監(jiān)聽下載數(shù)量的變化,如果不需要了解實時下載的數(shù)量,可以設置為null? * @return 已下載文件大小? * @throws Exception? */? //進行下載,如果有異常的話,拋出異常給調(diào)用者? 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){? ? ? //如果原先未曾下載或者原先的下載線程數(shù)與現(xiàn)在的線程數(shù)不一致? ? ? ? this.data.clear();? ? ? ? //遍歷線程池? ? ? ? for (int i = 0; i < this.threads.length; i++) {? ? ? ? ? this.data.put(i+1, 0);//初始化每條線程已經(jīng)下載的數(shù)據(jù)長度為0? ? ? ? }? ? ? ? this.downloadedSize = 0;? //設置已經(jīng)下載的長度為0? ? ? }? ? ? ? ? ? for (int i = 0; i < this.threads.length; i++) {//開啟線程進行下載? ? ? ? int downLength = this.data.get(i+1);? ? ? ? ? //通過特定的線程id獲取該線程已經(jīng)下載的數(shù)據(jù)長度? ? ? ? //判斷線程是否已經(jīng)完成下載,否則繼續(xù)下載? ? ? ? if(downLength < this.block && this.downloadedSizegetHttpResponseHeader(HttpURLConnection http) {? ? //使用LinkedHashMap保證寫入和便利的時候的順序相同,而且允許空值? ? Mapheader = new LinkedHashMap();? ? //此處使用無線循環(huán),因為不知道頭字段的數(shù)量? ? for (int i = 0;; i++) {? ? ? String mine = http.getHeaderField(i);? //獲取第i個頭字段的值? ? ? if (mine == null) break;? ? ? //沒值說明頭字段已經(jīng)循環(huán)完畢了,使用break跳出循環(huán)? ? ? header.put(http.getHeaderFieldKey(i), mine); //獲得第i個頭字段的鍵? ? }? ? return header;? }? /**? * 打印Http頭字段? * @param http? */? public static void printResponseHeader(HttpURLConnection http){? ? //獲取http響應的頭字段? ? Mapheader = getHttpResponseHeader(http);? ? //使用增強for循環(huán)遍歷取得頭字段的值,此時遍歷的循環(huán)順序與輸入樹勛相同? ? for(Map.Entryentry : 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);? }}Step4:自定義一個下載線程類? ? 1.首先肯定是要繼承Thread類啦,然后重寫Run()方法? ? 2.Run()方法:先判斷是否下載完成,沒有得話:打開URLConnection鏈接,接著RandomAccessFile 進行數(shù)據(jù)讀寫,完成時設置完成標記為true,發(fā)生異常的話設置長度為-1,打印異常信息? ? 3.打印log信息的方法? ? 4.判斷下載是否完成的方法(根據(jù)完成標記)? ? ? ? 5.獲得已下載的內(nèi)容大小DownLoadThread.java:public class DownloadThread extends Thread {? private static final String TAG = "下載線程類";? ? //定義TAG,在打印log時進行標記? private File saveFile;? ? ? ? ? ? ? //下載的數(shù)據(jù)保存到的文件? private URL downUrl;? ? ? ? ? ? ? //下載的URL? private int block;? ? ? ? ? ? ? ? //每條線程下載的大小? private int threadId = -1;? ? ? ? ? ? //初始化線程id設置? private int downLength;? ? ? ? ? ? //該線程已下載的數(shù)據(jù)長度? 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);//設置獲取實體數(shù)據(jù)的范圍? ? ? ? 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];? ? ? ? ? //設置本地數(shù)據(jù)的緩存大小為1MB? ? ? ? int offset = 0;? ? ? ? ? ? ? ? ? //每次讀取的數(shù)據(jù)量? ? ? ? print("Thread " + this.threadId + " start download from position "+ startPos);? //打印該線程開始下載的位置? ? ? ? ? ? ? ? RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");? ? ? ? threadfile.seek(startPos);? ? ? ? //用戶沒有要求停止下載,同時沒有達到請求數(shù)據(jù)的末尾時會一直循環(huán)讀取數(shù)據(jù)? ? ? ? while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) {? ? ? ? ? threadfile.write(buffer, 0, offset);? ? ? ? ? //直接把數(shù)據(jù)寫入到文件中? ? ? ? ? downLength += offset;? ? ? ? ? ? //把新線程已經(jīng)寫到文件中的數(shù)據(jù)加入到下載長度中? ? ? ? ? downloader.update(this.threadId, downLength); //把該線程已經(jīng)下載的數(shù)據(jù)長度更新到數(shù)據(jù)庫和內(nèi)存哈希表中? ? ? ? ? downloader.append(offset);? ? ? ? ? ? //把新下載的數(shù)據(jù)長度加入到已經(jīng)下載的數(shù)據(jù)總長度中? ? ? ? }? ? ? ? threadfile.close();? ? ? ? inStream.close();? ? ? ? print("Thread " + this.threadId + " download finish");? ? ? ? this.finish = true;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //設置完成標記為true,無論下載完成還是用戶主動中斷下載? ? ? } catch (Exception e) {? ? ? ? this.downLength = -1;? ? ? ? ? ? ? //設置該線程已經(jīng)下載的長度為-1? ? ? ? print("Thread "+ this.threadId+ ":"+ e);? ? ? }? ? }? }? private static void print(String msg){? ? Log.i(TAG, msg);? }? /**? * 下載是否完成? * @return? */? public boolean isFinish() {? ? return finish;? }? /**? * 已經(jīng)下載的內(nèi)容大小? * @return 如果返回值為-1,代表下載失敗? */? public long getDownLength() {? ? return downLength;? }}Step5:創(chuàng)建一個DownloadProgressListener接口監(jiān)聽下載進度FileDownloader中使用了DownloadProgressListener進行進度監(jiān)聽, 所以這里需要創(chuàng)建一個接口,同時定義一個方法的空實現(xiàn):DownloadProgressListener.javapublic interface DownloadProgressListener {? public void onDownloadSize(int downloadedSize);}Step6:編寫布局代碼Step7:MainActivity的編寫? ? 最后就是我們的MainActivity了,完成組件以及相關變量的初始化; 使用handler來完成界面的更新操作,另外耗時操作不能夠在主線程中進行, 所以這里需要開辟新的線程,這里用Runnable實現(xiàn),詳情見代碼 MainActivity.java: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;? //正在下載實時數(shù)據(jù)傳輸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");? ? //從消息中獲取已經(jīng)下載的數(shù)據(jù)長度? ? ? ? progressbar.setProgress(size);? ? ? ? //設置進度條的進度? ? ? ? //計算已經(jīng)下載的百分比,此處需要轉換為浮點數(shù)計算? ? ? ? float num = (float)progressbar.getProgress() / (float)progressbar.getMax();? ? ? ? int result = (int)(num * 100);? ? //把獲取的浮點數(shù)計算結果轉換為整數(shù)? ? ? ? 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, 觸摸屏幕....)是由主線程負責處理的袜炕,如果主線程處于工作狀態(tài),? ? 此時用戶產(chǎn)生的輸入事件如果沒能在5秒內(nèi)得到處理初家,系統(tǒng)就會報“應用無響應”錯誤妇蛀。? ? 所以在主線程里不能執(zhí)行一件比較耗時的工作,否則會因主線程阻塞而無法處理用戶的輸入事件笤成,? ? 導致“應用無響應”錯誤的出現(xiàn)评架。耗時的工作應該在子線程里執(zhí)行。? ? */? ? 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));? ? ? ? }? ? ? }? ? ? ? }? ? }}Step8: AndroidMainfest.xml文件中添加相關權限

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浙芙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子籽腕,更是在濱河造成了極大的恐慌嗡呼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皇耗,死亡現(xiàn)場離奇詭異南窗,居然都是意外死亡,警方通過查閱死者的電腦和手機郎楼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門万伤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呜袁,你說我怎么就攤上這事敌买。” “怎么了阶界?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵虹钮,是天一觀的道長聋庵。 經(jīng)常有香客問我,道長芙粱,這世上最難降的妖魔是什么祭玉? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮宅倒,結果婚禮上攘宙,老公的妹妹穿的比我還像新娘。我一直安慰自己拐迁,他們只是感情好蹭劈,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著线召,像睡著了一般铺韧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缓淹,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天哈打,我揣著相機與錄音,去河邊找鬼讯壶。 笑死料仗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的伏蚊。 我是一名探鬼主播立轧,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躏吊!你這毒婦竟也來了氛改?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤比伏,失蹤者是張志新(化名)和其女友劉穎胜卤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赁项,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡葛躏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肤舞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紫新。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖李剖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情囤耳,我是刑警寧澤篙顺,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布偶芍,位于F島的核電站,受9級特大地震影響德玫,放射性物質發(fā)生泄漏匪蟀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一宰僧、第九天 我趴在偏房一處隱蔽的房頂上張望材彪。 院中可真熱鬧,春花似錦琴儿、人聲如沸段化。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽显熏。三九已至,卻和暖如春晒屎,著一層夾襖步出監(jiān)牢的瞬間喘蟆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工鼓鲁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕴轨,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓骇吭,卻偏偏與公主長得像橙弱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绵跷,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理膘螟,服務發(fā)現(xiàn),斷路器碾局,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法荆残,類相關的語法,內(nèi)部類的語法净当,繼承相關的語法内斯,異常的語法,線程的語...
    子非魚_t_閱讀 31,623評論 18 399
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,810評論 0 11
  • 小編費力收集:給你想要的面試集合 1.C++或Java中的異常處理機制的簡單原理和應用像啼。 當JAVA程序違反了JA...
    八爺君閱讀 4,587評論 1 114
  • session在web開發(fā)中是一個非常重要的概念俘闯,這個概念很抽象,很難定義忽冻,也是最讓人迷惑的一個名詞真朗,也是最多被濫...
    沉默的金魚閱讀 423評論 0 1