Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能
效果圖
白話分析:
多線程:肯定是多個(gè)線程咯
斷點(diǎn):線程停止下載的位置
續(xù)傳:線程從停止下載的位置上繼續(xù)下載,直到完成任務(wù)為止。
核心分析:
斷點(diǎn):
當(dāng)前線程已經(jīng)下載的數(shù)據(jù)長度
續(xù)傳:
向服務(wù)器請(qǐng)求上次線程停止下載位置的數(shù)據(jù)
con.setRequestProperty("Range", "bytes=" + start + "-" + end);
分配線程:
int currentPartSize = fileSize / mThreadNum;
定義位置
定義線程開始下載的位置和結(jié)束的位置
for (int i = 0; i < mThreadNum; i++) { int start = i * currentPartSize;//計(jì)算每條線程下載的開始位置 int end = start + currentPartSize-1;//線程結(jié)束的位置 if(i==mThreadNum-1){ end=fileSize; }}
創(chuàng)建數(shù)據(jù)庫:
由于每一個(gè)文件要分成多個(gè)部分,要被不同的線程同時(shí)進(jìn)行下載。當(dāng)然要?jiǎng)?chuàng)建線程表,保存當(dāng)前線程下載開始的位置和結(jié)束的位置,還有完成進(jìn)度等。創(chuàng)建file表,保存當(dāng)前下載的文件信息,比如:文件名,url,下載進(jìn)度等信息
線程表:
public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary " +"key autoincrement, threadId, start , end, completed, url)";
file表:
public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary" + " key autoincrement ,fileName, url, length, finished)";
創(chuàng)建線程類
無非就2個(gè)類,一個(gè)是線程管理類DownLoadManager.Java,核心方法:start(),stop(),restart(),addTask().clear()。另一個(gè)是線程任務(wù)類
DownLoadTask.java,就是一個(gè)線程類,用于下載線程分配好的任務(wù)。后面會(huì)貼出具體代碼。
創(chuàng)建數(shù)據(jù)庫方法類
無非就是單例模式,封裝一些增刪改查等基礎(chǔ)數(shù)據(jù)庫方法,后面會(huì)貼出具體代碼。
創(chuàng)建實(shí)體類
也就是創(chuàng)建ThreadInfo和FileInfo這2個(gè)實(shí)體類,把下載文件信息和線程信息暫時(shí)存儲(chǔ)起來。
引入的第三方開源庫
NumberProgressBar是一個(gè)關(guān)于進(jìn)度條的開源庫,挺不錯(cuò)的。直達(dá)鏈接
代碼具體分析
1.首先是創(chuàng)建實(shí)體類,文件的實(shí)體類FileInfo,肯定有fileName,url,length,finised,isStop,isDownloading這些屬性。線程的實(shí)體類ThreadInfo肯定有threadId,start,end,completed,url這些屬性。這些都很簡單
//ThredInfo.java public class FileInfo { private String fileName; //文件名 private String url; //下載地址 private int length; //文件大小 private int finished; //下載已完成進(jìn)度 private boolean isStop=false; //是否暫停下載 private boolean isDownloading=false; //是否正在下載 public FileInfo(){ } public FileInfo(String fileName,String url){ this.fileName=fileName; this.url=url; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } public boolean isStop() { return isStop; } public void setStop(boolean stop) { isStop = stop; } public boolean isDownloading() { return isDownloading; } public void setDownloading(boolean downloading) { isDownloading = downloading; } @Override public String toString() { return "FileInfo{" + "fileName='" + fileName + '\'' + ", url='" + url + '\'' + ", length=" + length + ", finished=" + finished + ", isStop=" + isStop + ", isDownloading=" + isDownloading + '}'; }} //FileInfo.java public class FileInfo { private String fileName; //文件名 private String url; //下載地址 private int length; //文件大小 private int finished; //下載已完成進(jìn)度 private boolean isStop=false; //是否暫停下載 private boolean isDownloading=false; //是否正在下載 public FileInfo(){ } public FileInfo(String fileName,String url){ this.fileName=fileName; this.url=url; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getFinished() { return finished; } public void setFinished(int finished) { this.finished = finished; } public boolean isStop() { return isStop; } public void setStop(boolean stop) { isStop = stop; } public boolean isDownloading() { return isDownloading; } public void setDownloading(boolean downloading) { isDownloading = downloading; } @Override public String toString() { return "FileInfo{" + "fileName='" + fileName + '\'' + ", url='" + url + '\'' + ", length=" + length + ", finished=" + finished + ", isStop=" + isStop + ", isDownloading=" + isDownloading + '}'; }}
2.實(shí)體類寫完了,那么接下來寫創(chuàng)建一個(gè)類,繼承SQLiteOpenHelper類,來管理數(shù)據(jù)庫連接,主要作用:管理數(shù)據(jù)庫的初始化,并允許應(yīng)用程序通過該類獲取SQLiteDatabase對(duì)象。
public class ThreadHelper extends SQLiteOpenHelper{ public static final String TABLE_NAME="downthread"; public static final String CREATE_TABLE_SQL="create table "+TABLE_NAME+"(_id integer primary " +"key autoincrement, threadId, start , end, completed, url)"; public ThreadHelper(Context context, String name, int version) { super(context, name, null, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_SQL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }}
3.接下來封裝一些數(shù)據(jù)庫的增刪改查操作,用的單例模式,用雙重檢驗(yàn)鎖實(shí)現(xiàn)單例。好處:既能很大程度上確保線程安全,又能實(shí)現(xiàn)延遲加載。 缺點(diǎn):使用volatile關(guān)鍵字會(huì)使JVM對(duì)該代碼的優(yōu)化喪失,影響性能。并且在一些高并發(fā)的情況,仍然可能會(huì)創(chuàng)建多個(gè)實(shí)例,這稱為雙重檢驗(yàn)鎖定失效。單例模式
public class Thread { private SQLiteDatabase db; public static final String DB_NAME="downthread.db3"; public static final int VERSION=1; private Context mContext; private volatile static Thread t=null; private Thread(){ mContext= BaseApplication.getContext(); db=new ThreadHelper(mContext,DB_NAME,VERSION).getReadableDatabase(); } public static Thread getInstance(){ if(t==null){ synchronized (Thread.class){ if(t==null){ t=new Thread(); } } } return t; } public SQLiteDatabase getDb(){ return db; } //保存當(dāng)前線程下載進(jìn)度 public synchronized void insert(ThreadInfo threadInfo){ ContentValues values=new ContentValues(); values.put("threadId",threadInfo.getThreadId()); values.put("start",threadInfo.getStart()); values.put("end",threadInfo.getEnd()); values.put("completed",threadInfo.getCompeleted()); values.put("url",threadInfo.getUrl()); long rowId=db.insert(ThreadHelper.TABLE_NAME,null,values); if(rowId!=-1){ UtilsLog.i("插入線程記錄成功"); }else{ UtilsLog.i("插入線程記錄失敗"); } } //查詢當(dāng)前線程 下載的進(jìn)度 public synchronized ThreadInfo query(String threadId,String queryUrl){ Cursor cursor=db.query(ThreadHelper.TABLE_NAME,null,"threadId= ? and url= ?",new String[]{threadId,queryUrl},null,null,null); ThreadInfo info=new ThreadInfo(); if(cursor!=null){ while (cursor.moveToNext()){ int start=cursor.getInt(2); int end=cursor.getInt(3); int completed=cursor.getInt(4); String url=cursor.getString(5); info.setThreadId(threadId); info.setStart(start); info.setEnd(end); info.setCompeleted(completed); info.setUrl(url); } cursor.close(); } return info; } //更新當(dāng)前線程下載進(jìn)度 public synchronized void update(ThreadInfo info){ ContentValues values=new ContentValues(); values.put("start",info.getStart()); values.put("completed",info.getCompeleted()); db.update(ThreadHelper.TABLE_NAME,values,"threadId= ? and url= ?",new String[]{info.getThreadId(),info.getUrl()}); } //關(guān)閉db public void close(){ db.close(); } //判斷多線程任務(wù)下載 是否第一次創(chuàng)建線程 public boolean isExist(String url){ Cursor cursor=db.query(ThreadHelper.TABLE_NAME,null,"url= ?",new String[]{url},null,null,null); boolean isExist=cursor.moveToNext(); cursor.close(); return isExist; } public synchronized void delete(ThreadInfo info){ long rowId=db.delete(ThreadHelper.TABLE_NAME,"url =? and threadId= ?",new String[]{info.getUrl(),info.getThreadId()}); if(rowId!=-1){ UtilsLog.i("刪除下載線程記錄成功"); }else{ UtilsLog.i("刪除下載線程記錄失敗"); } } public synchronized void delete(String url){ long rowId=db.delete(ThreadHelper.TABLE_NAME,"url =? ",new String[]{url}); if(rowId!=-1){ UtilsLog.i("刪除下載線程記錄成功"); }else{ UtilsLog.i("刪除下載線程記錄失敗"); } }}
4.基本的準(zhǔn)備操作我們已經(jīng)完成了,那么開始寫關(guān)于下載的類吧。首先寫的肯定是DownLoadManager類,就是管理任務(wù)下載的類。不多說,直接看代碼。
public class DownLoadManager { private Map<String, FileInfo> map = new HashMap<>(); private static int mThreadNum; private int fileSize; private boolean flag = false; //true第一次下載 false不是第一次下載 private List<DownLoadTask> threads; private static FileInfo mInfo; private static ResultListener mlistener; public static ExecutorService executorService = Executors.newCachedThreadPool(); public static File file; private int totalComleted; private DownLoadManager() { threads = new ArrayList<>(); } public static DownLoadManager getInstance(FileInfo info, int threadNum,ResultListener listener) { mlistener = listener; mThreadNum = threadNum; mInfo = info; return DownLoadManagerHolder.dlm; } private static class DownLoadManagerHolder { private static final DownLoadManager dlm = new DownLoadManager(); } public void start() { totalComleted=0; clear(); final FileInfo newInfo = DownLoad.getInstance().queryData(mInfo.getUrl()); newInfo.setDownloading(true); map.put(mInfo.getUrl(),newInfo); prepare(newInfo); } //停止下載任務(wù) public void stop() { map.get(mInfo.getUrl()).setDownloading(false); map.get(mInfo.getUrl()).setStop(true); } public void clear(){ if(threads.size()>0){ threads.clear(); } } //重新下載任務(wù) public void restart() { stop(); try { File file = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, map.get(mInfo.getUrl()).getFileName()); if (file.exists()) { file.delete(); } java.lang.Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } DownLoad.getInstance().resetData(mInfo.getUrl()); start(); } //獲取當(dāng)前任務(wù)狀態(tài), 是否在下載 public boolean getCurrentState() { return map.get(mInfo.getUrl()).isDownloading(); } //添加下載任務(wù) public void addTask(FileInfo info) { //判斷數(shù)據(jù)庫是否已經(jīng)存在此下載信息 if (!DownLoad.getInstance().isExist(info)) { DownLoad.getInstance().insertData(info); map.put(info.getUrl(), info); } else { DownLoad.getInstance().delete(info); DownLoad.getInstance().insertData(info); UtilsLog.i("map已經(jīng)更新"); map.remove(info.getUrl()); map.put(info.getUrl(), info); } } private void prepare(final FileInfo newInfo) { new java.lang.Thread(){ @Override public void run() { HttpURLConnection con = null; RandomAccessFile raf=null; try { //連接資源 URL url = new URL(newInfo.getUrl()); UtilsLog.i("url=" + url); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(2 * 1000); con.setRequestMethod("GET"); int length = -1; UtilsLog.i("responseCode=" + con.getResponseCode()); if (con.getResponseCode() == 200) { length = con.getContentLength(); UtilsLog.i("文件大小=" + length); } if (length <= 0) { return; } //創(chuàng)建文件保存路徑 File dir = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH); if (!dir.exists()) { dir.mkdirs();//建立多級(jí)文件夾 } newInfo.setLength(length); fileSize = length; UtilsLog.i("當(dāng)前線程Id=" + java.lang.Thread.currentThread().getId() + ",name=" + java.lang.Thread.currentThread().getName()); int currentPartSize = fileSize / mThreadNum; file = new File(com.cmazxiaoma.downloader.download.DownLoadManager.FILE_PATH, newInfo.getFileName()); raf = new RandomAccessFile(file, "rwd"); raf.setLength(fileSize); if (Thread.getInstance().isExist(newInfo.getUrl())) { flag = false; } else { flag = true; } for (int i = 0; i < mThreadNum; i++) { if (flag) { UtilsLog.i("第一次多線程下載"); int start = i * currentPartSize;//計(jì)算每條線程下載的開始位置 int end = start + currentPartSize-1;//線程結(jié)束的位置 if(i==mThreadNum-1){ end=fileSize; } String threadId = "xiaoma" + i; ThreadInfo threadInfo = new ThreadInfo(threadId, start, end, 0,newInfo.getUrl()); Thread.getInstance().insert(threadInfo); DownLoadTask thread = new DownLoadTask(threadInfo,newInfo, threadId, start, end, 0); DownLoadManager.executorService.execute(thread); threads.add(thread); } else { UtilsLog.i("不是第一次多線程下載"); ThreadInfo threadInfo = Thread.getInstance().query("xiaoma" + i, newInfo.getUrl()); DownLoadTask thread = new DownLoadTask(threadInfo,newInfo,threadInfo.getThreadId(),threadInfo.getStart(),threadInfo.getEnd(),threadInfo.getCompeleted());//這里出現(xiàn)過問題 DownLoadManager.executorService.execute(thread); threads.add(thread); } } boolean isCompleted=false; while(!isCompleted){ isCompleted=true; for(DownLoadTask thread:threads){ totalComleted+=thread.completed; if(!thread.isCompleted){ isCompleted=false; } } if(newInfo.isStop()){ totalComleted=0; return; } Message message=new Message(); message.what=0x555; message.arg1=fileSize; message.arg2=totalComleted; handler.sendMessage(message); if(isCompleted){ totalComleted=0; //任務(wù)線程全部完成,清空集合 clear(); handler.sendEmptyMessage(0x666); return; } totalComleted=0; java.lang.Thread.sleep(1000); } }catch (Exception e) { e.printStackTrace(); }finally { try { if (con != null) { con.disconnect(); } if(raf!=null){ raf.close(); } } catch (IOException e) { e.printStackTrace(); } } } }.start(); } private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 0x555: if(mlistener!=null){ mlistener.progress(msg.arg1,msg.arg2); } break; case 0x666: if(mlistener!=null){ mlistener.comleted(); } break; } } };}
5.接下來呢,就是DownLoadTask類了,就是一個(gè)線程下載類。
public class DownLoadTask extends java.lang.Thread{ private int start;//當(dāng)前線程的開始下載位置 private int end;//當(dāng)前線程結(jié)束下載的位置 private RandomAccessFile raf;//當(dāng)前線程負(fù)責(zé)下載的文件大小 public int completed=0;//當(dāng)前線程已下載的字節(jié)數(shù) private String threadId;//自己定義的線程Id private FileInfo info; private ThreadInfo threadInfo; public boolean isCompleted=false; //true為當(dāng)前線程完成任務(wù),false為當(dāng)前線程未完成任務(wù) //保存新的start public int finshed=0; public int newStart=0; public DownLoadTask(ThreadInfo threadInfo,FileInfo info,String threadId, int start, int end,int completed){ this.threadInfo=threadInfo; this.info=info; this.threadId=threadId; this.start=start; this.end=end; this.completed=completed; } @Override public void run() { HttpURLConnection con = null; try { UtilsLog.i("start="+start+",end="+end+",completed="+completed+",threadId="+getThreadId()); URL url = new URL(info.getUrl()); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(2 * 1000); con.setRequestMethod("GET"); con.setRequestProperty("Range", "bytes=" + start + "-"+end);//重點(diǎn) raf=new RandomAccessFile(DownLoadManager.file,"rwd"); //從文件的某一位置寫入 raf.seek(start); if (con.getResponseCode() == 206) { //文件部分下載 返回碼是206 InputStream is = con.getInputStream(); byte[] buffer = new byte[4096]; int hasRead = 0; while ((hasRead = is.read(buffer)) != -1) { //寫入文件 raf.write(buffer, 0, hasRead); //單個(gè)文件的完成程度 completed += hasRead; threadInfo.setCompeleted(completed); //保存新的start finshed=finshed+hasRead;//這里出現(xiàn)過問題,嘻嘻 newStart=start+finshed; threadInfo.setStart(newStart); //UtilsLog.i("Thread:"+getThreadId()+",completed=" + completed); //停止下載 if (info.isStop()) { UtilsLog.i("isStop="+info.isStop()); //保存下載進(jìn)度 UtilsLog.i("現(xiàn)在Thread:"+getThreadId()+",completed=" + completed); Thread.getInstance().update(threadInfo); return; } } //刪除該線程下載記錄 Thread.getInstance().delete(threadInfo); isCompleted=true; Thread.getInstance().update(threadInfo); UtilsLog.i("thread:"+getThreadId()+"已經(jīng)完成任務(wù)!--"+"completed="+completed); } } catch (Exception e) { if (con != null) { con.disconnect(); } try { if (raf != null) { raf.close(); } } catch (IOException e1) { e1.printStackTrace(); } } } public String getThreadId() { return threadId; }}
6.接口,就是一個(gè)監(jiān)聽下載進(jìn)度的接口,也是很簡單。
public interface ResultListener{ void progress(int max, int progress); void comleted();}
結(jié)束
大致操作就是這樣,其實(shí)多線程也挺簡單的。
以上所述是小編給大家介紹的Android多線程+單線程+斷點(diǎn)續(xù)傳+進(jìn)度條顯示下載功能,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- android實(shí)現(xiàn)多線程下載文件(支持暫停、取消、斷點(diǎn)續(xù)傳)
- Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
- Android多線程斷點(diǎn)續(xù)傳下載功能實(shí)現(xiàn)代碼
- Android多線程斷點(diǎn)續(xù)傳下載示例詳解
- Android 使用AsyncTask實(shí)現(xiàn)多任務(wù)多線程斷點(diǎn)續(xù)傳下載
- Android實(shí)現(xiàn)網(wǎng)絡(luò)多線程斷點(diǎn)續(xù)傳下載實(shí)例
- Android編程開發(fā)實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳下載器實(shí)例
- PC版與Android手機(jī)版帶斷點(diǎn)續(xù)傳的多線程下載
- Android 使用AsyncTask實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳
- android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能
相關(guān)文章
android實(shí)現(xiàn)上下滾動(dòng)的TextView
android實(shí)現(xiàn)上下滾動(dòng)的TextView,需要的朋友可以參考一下2013-05-05android廣角相機(jī)畸變校正算法和實(shí)現(xiàn)示例
今天小編就為大家分享一篇android廣角相機(jī)畸變校正算法和實(shí)現(xiàn)示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08Android 如何保證service在后臺(tái)不被kill
本文主要介紹了Android 如何保證service在后臺(tái)不被kill的方法。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02Android實(shí)現(xiàn)懸浮可拖拽的Button
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)懸浮可拖拽的Button,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06Android實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07基于RecyclerView實(shí)現(xiàn)橫向GridView效果
這篇文章主要為大家詳細(xì)介紹了基于RecyclerView實(shí)現(xiàn)橫向GridView效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07android實(shí)現(xiàn)菜單三級(jí)樹效果
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)菜單三級(jí)樹效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11Android穩(wěn)定性:可遠(yuǎn)程配置化的Looper兜底框架
這篇文章主要為大家介紹了Android穩(wěn)定性可遠(yuǎn)程配置化的Looper兜底框架實(shí)例實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02使用Kotlin開發(fā)Android應(yīng)用的初體驗(yàn)
本篇文章主要介紹了使用Kotlin開發(fā)Android應(yīng)用的初體驗(yàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05Kotlin協(xié)程Context應(yīng)用使用示例詳解
這篇文章主要為大家介紹了Kotlin協(xié)程Context應(yīng)用使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12