android多线程下载原理,安卓多线程断点续传下载功能(靠谱第三方组件,原理demo)...
一,原生的DownloadManager
從Android 2.3(API level 9)開始,Android以Service的方式提供了全局的DownloadManager來系統級地優化處理長時間的下載操作。
DownloadManager支持失敗重試、Notification通知等基本特性。但是DownloadManager提供的接口很有限,雖然有暫停狀態卻沒有提供主動暫停的接口。所以需手動實現斷點續傳,以及需手動實現單文件的多線程(分段)下載,還是比較麻煩。
總結:DownloadManager比較適用于簡單的單文件下載。
二,第三方組件
1,FileDownloader
git地址:https://github.com/lingochamp/FileDownloader
中文文檔:https://github.com/lingochamp/FileDownloader/blob/master/README-zh.md
描述:在第三方下載組件中比較突出,成熟,穩定,健壯。支持多任務下載,支持多線程下載,支持斷點續傳,支持替換網絡請求框架等。
實際應用:在快手的后臺進程里有liulishuo/FileDownloader,因為快手內有下載服務,有可能用于應用內的更新模塊,liulishuo的FileDownloader就是這個組件。
image
總結:建議在項目中實現多任務多線程下載功能優先考慮上面的第三方組件,而不是重復造輪子。畢竟能在受眾群大的商業應用中成熟使用的第三方框架,是經過多次迭代的思想結晶,考慮了多種情況,踩過很多坑。一般自己從頭寫的話,很難比其的做得更好,考慮得更完善,尤其是在有限的項目開發時間內。
三,原理demo
這里列一個兩年前寫的demo,用的都是非常基礎的東西。考慮的點也比較少,就只是從頭實現這個功能。
(一)思路:
1,如何實現多線程下載功能?
(后續都假設1個文件啟動3個線程去下載)先起一個初始線程(initThread)去查詢目標下載文件的基本信息比如文件長度等,生成對應本地文件。計算每個下載線程(DownloadThread)的下載長度和下載起始結束點,通過線程池啟動3個下載線程進行下載并寫入到本地文件的對應起始點中,當3個下載線程都下載完成后,這個文件就下載完整了。
2,如何實現暫停功能?
設計一個下載任務類(DownloadTask),開始下載一個文件就新建一個下載任務類DownloadTask。下載任務類負責管理啟動多個下載線程開始下載。下載任務類里設置一個公開的暫停標志,在外部可以設置暫停標志的true/false,當下載線程下載文件時,判斷到暫停標志為true,則結束本線程即中斷下載。
3,如何實現即時展示當前下載進度?
下載任務類里設置一個表示當前已完成下載長度的變量,在下載線程的下載過程中,及時更新累加。設置每500ms計算一次下載進度=當前已完成下載長度/下載文件總長度,并發出廣播。外部接收到廣播后更新ui。
4,如何實現斷點續傳功能(暫停/網絡故障后再恢復下載從之前下載進度基礎上繼續下載,而不是重新下載)?
這就必須把下載進程的相關信息本地存儲,然后在恢復下載時查詢本地存儲的數據,獲得目前的下載進度,再從那開始下載。
要給每一個下載線程保存一條這個線程相關信息的數據,考慮到當新建n個下載任務時,同時開啟3n個下載線程,就需要保存3n條線程信息數據。而在暫停/網絡故障/下載失敗時,都要更新對應數據,選用本地數據庫sqlite來存儲數據。
安卓的數據表的操作,基本就是“數據模型+helper幫助類+dao實現類”來一套。
但是注意這里是多線程操作數據表,“增刪改查”操作里,“增刪改”方法都要加上synchronized修飾詞。synchronized是保證線程安全,就是在當多個線程操作同一資源時保證某一時刻只能有一個線程在執行這個任務。
舉個例子,想象一個線程是一個人,一張數據表裝在一棟房子里,房子有兩個門,門a只給不帶synchronized方法的人進,門b只給帶synchronized方法的人進。門a沒什么條件,只要是使用不帶synchronized方法的人都可以直接進去。門b卻有條件,一次只能進去一個人,并且下一個進去的人要在上一個進去的人出來之后才能進去。
(二)當年湊zhuanli畫的流程圖:
image
image
(三)代碼:
啟動下載
Intent intent = new Intent( mContext, DownloadService.class );
fileInfo.setIsDownload("true");
intent.setAction(IntentAction.ACTION_START);
intent.putExtra( KeyName.FILEINFO_TAG, fileInfo );
mContext.startService(intent); // 通過intent傳遞信息fileInfo給servers
暫停下載
Intent intent = new Intent( mContext, DownloadService.class );
fileInfo.setIsDownload("false");
intent.setAction(IntentAction.ACTION_PAUSE);
intent.putExtra( KeyName.FILEINFO_TAG, fileInfo );
mContext.startService(intent);
DownloadService
/**
* 用于文件下載的service
*/
public class DownloadService extends Service {
/**
* 定義消息處理handler的標志
*/
private static final int MSG_INIT =0 ;
/**
* 文件下載線程任務的集合
*/
private MapmTask =new LinkedHashMap();
/**
* onStartCommand()接收Activity中StartService發送的信息
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 獲取Activity傳進來的參數
FileInfo fileInfo =(FileInfo) intent.getSerializableExtra( KeyName.FILEINFO_TAG );// 獲取下載文件信息
Log.i("intent", "DownloadService-47行- "+fileInfo.getFileName()+" intent=null: " + (intent==null) );
Log.i("intent", "DownloadService-48行- "+fileInfo.getFileName()+" intent.getSerializableExtra(Config.FILEINFO_TAG):"
+(intent.getSerializableExtra(KeyName.FILEINFO_TAG)==null) );
// 開啟多線程下載
if ( IntentAction.ACTION_START.equals(intent.getAction()) ) {
initThread mInitThread =new initThread(fileInfo);
DownloadTask.sExecutorService.execute(mInitThread);// 通過線程池開啟初始化線程
// 暫停多線程下載
}else if ( IntentAction.ACTION_PAUSE.equals(intent.getAction()) ) {
DownloadTask tast =mTask.get(fileInfo.getId() ); // 從集合中取出下載任務
if (tast !=null) {
tast.isPause =true ;
}
}else if(IntentAction.ACTION_DELETE.equals(intent.getAction())){
DownloadTask tast =mTask.get(fileInfo.getId() ); // 從集合中取出下載任務
if (tast !=null) {
tast.isDel=true ;
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinderonBind(Intent intent) {
return null;
}
/**
* 初始化文件下載線程:創建本地下載位置,并開啟下載任務
**/
class initThread extends Thread{
/**
* 下載的文件的所有屬性信息
*/
private FileInfomFileInfo =null ;
/**
* 初始化文件下載線程:確保創建本地下載位置,并開啟文件下載任務
* @param mFileInfo 下載的文件的所有屬性信息
*/
public initThread(FileInfo mFileInfo) {
this.mFileInfo = mFileInfo;
}
//開啟開啟下載任務
Handlerhandler =new Handler(){
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INIT:
FileInfo fileInfo = (FileInfo) msg.obj ;
// 啟動下載任務
DownloadTask task =new DownloadTask( DownloadService.this, fileInfo, DownloadConfig.DONWNLOAD_THREAD_NUM);
task.download();
//把下載任務添加到下載集合中
mTask.put(fileInfo.getId(), task); //將開啟下載線程的id和實例添加到map中,在暫停時通過id獲取實例,并令它暫停
break;
default:
break;
}
}
};
@Override
public void run(){
HttpURLConnection conn =null ;
RandomAccessFile raf =null ;// 隨機訪問文件,可以在文件的隨機寫入,對應斷點續傳功能
try {
// 連接網絡文件
URL url =new URL(mFileInfo.getUrl() );
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);// 設置連接超時時間
conn.setRequestMethod("GET");
// 獲取文件長度
int length = -1;
if (conn.getResponseCode()== HttpStatus.SC_OK ) {
length = conn.getContentLength();
Log.i("http", "DownloadService-138行 獲取網絡數據長度="+ length );
}else{
Log.i("http", "DownloadService-140行 http連接失敗!");
}
if ( length <=0 ){
return ;
}
// 判斷下載路徑是否存在
File dir =new File( DownloadConfig.DOWNLOAD_PATH );
if ( !dir.exists() ){
dir.mkdir();
}
// 在本地創建文件
File file =new File( dir, mFileInfo.getFileName() );
raf =new RandomAccessFile( file, "rwd" );
// 設置文件長度
raf.setLength( length );
mFileInfo.setLength( length );
handler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget(); //將數據發回給handler
//關閉連接
if (raf!=null) {
raf.close();
}if (conn !=null) {
conn.disconnect();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
DownloadTask
/**
* 下載任務類
**/
public class DownloadTask {
/**
* 上下文
*/
private Context mContext = null ;
/**
* 下載的文件的所有屬性信息
*/
private FileInfo mFileInfo = null ;
/**
* 數據(庫)訪問接口
*/
private ThreadDAO mDAO = null;
/**
* 已下載字節長度
*/
private int mFinishedLen = 0;
/**
* 下載暫停標志
*/
public Boolean isPause = false;
/**
* 下載刪除標志
*/
public Boolean isDel = false;
/**
* 默認的下載線程數
*/
private int mThreadNum = 0 ;
/**
* 下載線程集合
*/
private List mThreadList = null ;
/**
* 帶緩存線程池,s開頭表示用到static關鍵字
*/
public static ExecutorService sExecutorService = Executors.newCachedThreadPool();
/**
* 文件下載的線程任務類
* @param mContext 上下文
* @param mFileInfo 下載的文件的所有屬性信息
* @param threadNum 文件分段下載線程數
*/
public DownloadTask(Context mContext, FileInfo mFileInfo, int threadNum) {
this.mContext = mContext;
this.mFileInfo = mFileInfo;
this.mThreadNum = threadNum ;
mDAO = new ThreadDAOIImpl(mContext);
}
/**
* 開始下載文件
*/
public void download() {
List threadInfoList = mDAO.getThreads(mFileInfo.getUrl()); // 讀取數據庫中文件下載的信息
// 不存下載線程則創建
if( threadInfoList.size()== 0 ){
int length = mFileInfo.getLength()/mThreadNum ; // 獲取單個線程下載長度
for (int i = 0; i < mThreadNum; i++) { // 創建線程信息
ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(),length*i, (i+1)*length-1, 0, mFileInfo.getMd5(),"false", "none");
if (i==mThreadNum-1) {
threadInfo.setEnd(mFileInfo.getLength());// 設置最后一個線程的下載長度
}
threadInfoList.add(threadInfo); // 添加線程信息到集合
mDAO.insertThread(threadInfo); // 向數據庫插入文件下載的信息
} // 放到run外面,比較不容易產生數據庫的死鎖
}
// 啟動多個線程進行下載
mThreadList = new ArrayList();
for (ThreadInfo info : threadInfoList) {
DownloadThread downloadThread = new DownloadThread(info);
if ( info.getFinished()
info.getFinished()
DownloadTask.sExecutorService.execute(downloadThread);
mThreadList.add(downloadThread); //添加線程到集合中
}
}
}
/**
* 判斷所有線程是否執行完成
* @param threadInfo 下載文件的線程信息
*/
private synchronized void checkAllThreadsFinished(ThreadInfo threadInfo){ // synchronized 同步方法,保證同一時間只有一個線程訪問該方法
boolean allFinished = true ; //所有線程下載結束標識
// 遍歷線程集合,判斷是否都下載完畢
for (DownloadThread thread : mThreadList) {
if (!thread.isFinished) {
allFinished = false ;
break ;
}
}
// 所有線程下載結束:驗證md5,相同則存儲下載長度,否則清空;發送廣播通知UI下載任務結束
if (allFinished) {
MD5Util md5Util = new MD5Util();
DateTools dateTools = new DateTools();
List threadInfosList =mDAO.getThreads(threadInfo.getUrl());
for (ThreadInfo info :threadInfosList) {
if ( !md5Util.isMD5Equal(info) ) { // md5驗證失敗,清空下載長度
mDAO.updateThread(info.getUrl(), info.getId(), 0, info.getMd5(), "false" ,dateTools.getCurrentTime() );
}else if(info.getId()==0) {
mDAO.updateThread(info.getUrl(), info.getId(),(info.getEnd()-info.getStart()), info.getMd5(), "true", dateTools.getCurrentTime() );
}else if(info.getId()!=0){
mDAO.updateThread(info.getUrl(), info.getId(),(info.getEnd()-info.getStart()+1), info.getMd5(), "true",dateTools.getCurrentTime() );
}
}
Intent intent = new Intent(IntentAction.ACTION_FINISH);
mFileInfo.setOvertime(dateTools.getCurrentTime());
intent.putExtra( KeyName.FILEINFO_TAG, mFileInfo);
mContext.sendBroadcast(intent);
}
}
/**
* 進行文件下載的線程
**/
class DownloadThread extends Thread{
/**
* 文件下載線程的信息
*/
private ThreadInfo mThreadInfo = null ;
/**
* 標識線程是否執行完成
*/
public Boolean isFinished = false ;
/**
* 廣播下載進度的間隔時間
*/
private final static int BROADCAST_TIME = 100 ;
/**
* httpUrl連接
*/
HttpURLConnection conn = null ;
/**
* 任意寫入文件:RandomAccessFile
*/
RandomAccessFile raf= null;
/**
* 輸入流
*/
InputStream input = null ;
/**
* 進行文件下載的線程
* @param mThreadInfo 文件下載線程的信息
**/
public DownloadThread(ThreadInfo mThreadInfo) {
this.mThreadInfo = mThreadInfo;
}
@Override
public void run(){
try {
URL url = new URL(mThreadInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
//設置url上資源下載位置/范圍
int start = mThreadInfo.getStart() +mThreadInfo.getFinished();
conn.setRequestProperty("Range", "bytes=" +start +"-" + mThreadInfo.getEnd() );
//設置文件本地寫入位置
File file = new File( DownloadConfig.DOWNLOAD_PATH, mFileInfo.getFileName() );
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
mFinishedLen +=mThreadInfo.getFinished();
//開始下載啦
if ( conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT ){
//讀取數據
input = conn.getInputStream();
byte[] buffer = new byte[ 1024 * 4 ];
int len = -1;
long time = System.currentTimeMillis();
while((len = input.read(buffer))!=-1){
//寫入文件
raf.write(buffer,0,len);
mFinishedLen += len ;
mThreadInfo.setFinished(mThreadInfo.getFinished() + len);
if (System.currentTimeMillis()-time > BROADCAST_TIME ) { //每500ms刷新一次
time = System.currentTimeMillis();
double progress_result = (double)mFinishedLen / (double)mFileInfo.getLength(); // 計算下載進度
double download_rate = (double)len / (double)(1024*BROADCAST_TIME/1000); // 計算下載速率
Intent intent = new Intent( IntentAction.ACTION_UPDATE);
intent.putExtra( KeyName.FINISHED_TAG,progress_result );
intent.putExtra( KeyName.DOWNLOAD_RATE_TAG,download_rate );
intent.putExtra("id", mFileInfo.getId() );
mContext.sendBroadcast(intent);
}
//下載暫停時,保存下載進度搭配數據庫
if (isPause) {
mDAO.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished(), mThreadInfo.getMd5(), "false", "none" );
return;
}
//刪除下載時,刪除數據庫內的相關信息
if (isDel) {
if(!mDAO.getThreads(mThreadInfo.getUrl()).isEmpty()) {
mDAO.deleteThread(mThreadInfo.getUrl());
}
// Intent intent = new Intent( IntentAction.ACTION_DELETE);
// mContext.sendBroadcast(intent);
return;
}
// 下載連接獲取輸入流或文件寫入失敗,清空文件下載進度,并重新下載
if (raf ==null || input ==null ){
mDAO.updateThread(mThreadInfo.getUrl(), 0, 0, mThreadInfo.getMd5(), "false" , "none");
mDAO.updateThread(mThreadInfo.getUrl(), 1, 0, mThreadInfo.getMd5(), "false" , "none");
mDAO.updateThread(mThreadInfo.getUrl(), 2, 0, mThreadInfo.getMd5(), "false" , "none");
Intent intent = new Intent( IntentAction.ACTION_UPDATE);
intent.putExtra( KeyName.FINISHED_TAG, 0 );
intent.putExtra( KeyName.DOWNLOAD_RATE_TAG, 0 );
intent.putExtra("id", mFileInfo.getId() );
mContext.sendBroadcast(intent);
download();
}
}
isFinished = true ;// 標識線程執行完畢
// 檢查下載任務是否執行完畢
checkAllThreadsFinished(mThreadInfo );
}
} catch (Exception e) {
e.printStackTrace();
}finally{//關閉連接
try {
if (raf !=null) {
raf.close();
}else {
System.out.println("DOwnloadTask-280行 RadomAccessFile發生錯誤");
}if (input !=null) {
input.close();
}else {
System.out.println("DOwnloadTask-280行 inputStream發生錯誤");
}if (conn !=null) {
conn.disconnect();
}else {
System.out.println("DOwnloadTask-280行 HttpURLConnection發生錯誤");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileInfo
/**
* 下載的文件的所有屬性信息
*/
public class FileInfo implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 下載文件的id
*/
private int id ;
/**
* 下載文件總長度
*/
private int length ;
/**
* 下載文件的web路徑
*/
private String url ;
/**
* 下載文件名
*/
private String fileName ;
/**
* 下載文件的 MD5值
*/
private String md5 ;
/**
* 文件下載進度(%)
*/
private double finished ;
/**
* 文件下載速率
*/
private double rate ;
/**
* 下載文件的 MD5值
*/
private String over ;
/**
* 下載文件完成下載的時間
*/
private String overtime ;
public String getOvertime() {
return overtime;
}
public void setOvertime(String overtime) {
this.overtime = overtime;
}
/**
* 下載文件是否正在下載
*/
private String isDownload ;
public FileInfo() {
}
/**
* 存儲下載文件的屬性信息
* @param id 下載文件的id標識
* @param length 文件總長度
* @param url 文件web路徑
* @param fileName 下載文件名
* @param finished 文件下載進度
* @param rate 文件下載速率
* @param md5 文件的md5值
* @param over 文件是否完成下載
* @param isDownload 文件是否正在下載
*/
public FileInfo(int id, int length, String url, String fileName,
int finished,double rate, String md5, String over, String isDownload, String overtime) {
this.id = id;
this.url = url;
this.length = length;
this.fileName = fileName;
this.finished = finished;
this.rate = rate;
this.md5 = md5;
this.over = over ;
this.overtime = overtime ;
this.isDownload = isDownload ;
}
/**
* 獲取文件下載狀態
*/
public String getIsDownload() {
return isDownload;
}
/**
* 設置文件下載狀態
* @param isDownload 文件是否正在下載
*/
public void setIsDownload(String isDownload) {
this.isDownload = isDownload;
}
/**
* 獲取文件下載進度(%)
*/
public double getRate() {
return rate;
}
/**
* 存儲文件下載進度(%)
*/
public void setRate(double rate) {
this.rate = rate;
}
/**
* 獲取文件MD5
*/
public String getMd5() {
return md5;
}
/**
* 存儲文件MD5
*/
public void setMd5(String md5) {
this.md5 = md5;
}
/**
* 獲取下載文件的id標識
*/
public int getId() {
return id;
}
/**
* 設置下載文件的
*/
public void setId(int id) {
this.id = id;
}
/**
* 獲取下載文件的長度
*/
public int getLength() {
return length;
}
/**
* 存入下載文件的
*/
public void setLength(int length) {
this.length = length;
}
/**
* 獲取下載文件的URL路徑
*/
public String getUrl() {
return url;
}
/**
* 存儲下載文件的URL路徑
*/
public void setUrl(String url) {
this.url = url;
}
/**
* 獲取下載文件的文件名
*/
public String getFileName() {
return fileName;
}
/**
* 存儲下載文件的文件名
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* 獲取下載文件的下載進度
*/
public double getFinished() {
return finished;
}
/**
* 存儲下載文件的存儲進度
*/
public void setFinished(double finished) {
this.finished = finished;
}
/**
* 獲取文件完成下載標識
*/
public String getOver() {
return over;
}
/**
* 存儲文件完成下載標識
*/
public void setOver(String over) {
this.over = over;
}
/**
* 獲取下載文件的所有屬性信息
*/
@Override
public String toString() {
return "FileInfo [id=" + id + ", length=" + length + ", url=" + url
+ ", fileName=" + fileName + ", finished="
+ finished +", rate=" + rate +", md5=" + md5 + "]";
}
}
ThreadInfo
/**
*文件下載線程的信息
**/
public class ThreadInfo {
/**
* 文件下載線程的id標識
*/
private int id ;
/**
* 下載文件的URL路徑
*/
private String url ;
/**
* 文件下載的起始字節位置
*/
private int start ;
/**
* 文件下載的結束字節位置
*/
private int end ;
/**
* 文件已下載的字節長度
*/
private int finished ;
/**
* 下載文件的 MD5值
*/
private String md5 ;
/**
* 下載文件是否完成下載
*/
private String over ;
/**
* 下載文件完成下載的時間
*/
private String overtime ;
public ThreadInfo() {
}
/**
* 存儲文件下載線程的屬性信息
* @param id 文件下載線程id標識
* @param url 下載文件路徑
* @param start 文件下載起始字節位置
* @param end 文件下載終止字節位置
* @param finished 文件已下載的字節長度
* @param md5 文件的md5值
* @param over 文件是否完成下載
* @param overtime 文件完成下載的時間
*/
public ThreadInfo(int id, String url, int start, int end, int finished, String md5, String over,String overtime) {
this.id = id;
this.url = url;
this.start = start;
this.end = end;
this.finished = finished;
this.md5 = md5;
this.over = over;
this.overtime = overtime;
}
/**
* 獲取文件完成下載的時間
*/
public String getOvertime() {
return overtime;
}
/**
* 設置文件完成下載的時間
*/
public void setOver_time(String overtime) {
this.overtime = overtime;
}
/**
* 獲取文件下載線程的id標識
*/
public int getId() {
return id;
}
/**
* 設置文件下載線程的id標識
*/
public void setId(int id) {
this.id = id;
}
/**
* 獲取文件下載線程的url
*/
public String getUrl() {
return url;
}
/**
* 設置下載文件的url
*/
public void setUrl(String url) {
this.url = url;
}
/**
* 獲取文件下載線程的起始字節位置
*/
public int getStart() {
return start;
}
/**
* 設置下載文件的起始字節位置
*/
public void setStart(int start) {
this.start = start;
}
/**
* 獲取文件下載線程的結束字節位置
*/
public int getEnd() {
return end;
}
/**
* 設置下載文件的結束字節位置
*/
public void setEnd(int end) {
this.end = end;
}
/**
* 獲取文件下載線程已下載的字節長度
*/
public int getFinished() {
return finished;
}
/**
* 設置文件下載線程已下載的字節長度
*/
public void setFinished(int finished) {
this.finished = finished;
}
/**
* 獲取文件MD5
*/
public String getMd5() {
return md5;
}
/**
* 存儲文件MD5
*/
public void setMd5(String md5) {
this.md5 = md5;
}
/**
* 獲取文件完成下載標識
*/
public String getOver() {
return over;
}
/**
* 存儲文件完成下載標識
*/
public void setOver(String over) {
this.over = over;
}
/**
* 獲取文件下載線程的所有屬性信息
*/
@Override
public String toString() {
return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start
+ ", end =" + end + ", finished=" + finished +", md5=" + md5
+ ", over=" + over +"]";
}
}
DBHelper
/**
* 數據庫幫助類
* 多線程操作數據庫時注意使用【單例模式】
* 1、構造方法定為private
* 2、定義該類的一個靜態對象用以應用
* 3、通過getInstance()方法返回該類對象,
* 使該方法無論調用多少次,該類都是唯一。
*/
public class DBHelper extends SQLiteOpenHelper{
private AtomicInteger mOpenCounter = new AtomicInteger();
private SQLiteDatabase mDatabase;
/**
* 數據庫名稱
*/
private static final String DB_NAME = "download.db" ;
/**
* 數據庫名稱
*/
public static final String TABLE_NAME = "thread_info" ;
/**
* 文件下載線程id
*/
public static final String THREAD_ID = "thread_id" ;
/**
* 下載文件的url文件
*/
public static final String URL = "url" ;
/**
* 文件下載的起始位置(字節長度)
*/
public static final String START = "start" ;
/**
* 文件下載的結束位置(字節長度)
*/
public static final String END = "end" ;
/**
* 文件已下載的字節長度
*/
public static final String FINISHED = "finished" ;
/**
* 文件的md5
*/
public static final String MD5 = "md5" ;
/**
* 文件是否完成下載標識
*/
public static final String OVER = "over" ;
/**
* 文件是成下載時間
*/
public static final String OVER_TIME = "over_time" ;
/**
* 數據庫幫助類的靜態對象引用
*/
private static DBHelper sHelper = null ;
/**
* 數據庫版本
*/
private static final int VERSION = 1;
/**
* sql創建保存線程信息表命令句
*/
private static final String SQL_CREATE =
"create table " +TABLE_NAME +" (_id integer primary key autoincrement,"
+ THREAD_ID +" integer, "
+ URL +" text, "
+ START +" integer, "
+ END +" integer,"
+ FINISHED +" integer,"
+ MD5 +" text, "
+ OVER +" text, "
+OVER_TIME+ " text )";
/**
* 刪除表命令句
*/
private static final String SQL_DROP = "drop table if exists "+TABLE_NAME;
private DBHelper(Context context) { // 將public改為private,
super(context, DB_NAME, null, VERSION); // 防止在其它地方被new出來,保證db的單例,防止數據庫被鎖定
}
/**
* 獲得類對象sHelper
*/
public static DBHelper getInstance(Context context){ // 單例模式,DBHelper只會被實例化一次
if (sHelper == null ) { // 靜態方法訪問數據庫,無論創建多少個數據庫訪問對象,
sHelper = new DBHelper(context); // 里面的Helper只有一個,保證程序中只有一個DBHelper對數據庫進行訪問
}
return sHelper ;
}
public synchronized SQLiteDatabase openDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = sHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
/**
* 創建表
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE); //創建表
}
/**
* 更新數據庫表
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DROP);
db.execSQL(SQL_CREATE);
}
}
ThreadDAO
/**
* 【數據訪問接口】
* DAO:data access object 數據訪問對象,DAO一定是和數據庫的每張表一一對應;
* 是一種應用程序表承接口(api),可以訪問其它的結構化查詢語言(SQL)數據庫;
* J2EE用DAO設計模塊,把底層數據訪問和高層的商務邏輯分開;典型的DAO有以下幾個組件:
* 1、一個DAO工廠類
* 2、一個DAO接口
* 3、一個實現DAO接口的具體類
* 4、數據傳遞對象
*/
public interface ThreadDAO {
/**
* 插入文件下載信息
* @param threadInfo 下載線程的信息
*/
public void insertThread(ThreadInfo threadInfo);
/**
* 刪除文件下載信息
*/
public void deleteThread(String url );
/**
* 更新下載文件下載進度
* @param url 下載線程的URL
* @param thread_id 下載線程的id
* @param finished 下載線程的文件已下載字節數
* @param md5 下載文件的md5
* @param over 文件下載完成標識
* @param over_time 文件下載完成時間
*/
public void updateThread(String url, int thread_id, int finished, String md5, String over, String over_time) ;
/**
* 查詢文件的文件下載線程信息
* @param url 下載線程的URL
* @return List 線程信息集
*/
public List getThreads(String url) ;
/**
* 獲取所有文件的文件下載信息
* @return List 包含下載線程信息的list集
*/
public List getDBFileInfoList();
/**
* 文件下載信息是否已結束下載
* @return 當存在下載信息返回true;否則返回false
*/
boolean isDownloadOver(String url);
}
ThreadDAOIImpl
/**
* 【數據庫訪問接口的實現】
*/
public class ThreadDAOIImpl implements ThreadDAO{
/**
* 數據庫幫助類
*/
private DBHelper mDBHelper = null ; // 將DBHelper用private修飾
public ThreadDAOIImpl(Context context) {
mDBHelper = DBHelper.getInstance(context);
}
/**
* 插入文件下載信息】
* 多線程數據庫的增、刪、改(更新)方法用synchronized修飾,以保證線程安全:
* 保證同一時間段不會有多個(只有一個)線程對數據庫進行增刪改,需等待線程執
* 行完后再開啟線程執行下一個功能;而查詢因為不用操作數據庫,不會導致 數據庫
* 死鎖 ,所以不用
* @see com.download.db.ThreadDAO#insertThread(com.download.entities.ThreadInfo)
*/
@Override
public synchronized void insertThread(ThreadInfo threadInfo ){
SQLiteDatabase db = mDBHelper.openDatabase(); // 實例化數據庫,設置為【讀寫】模式
db.execSQL( "insert into " +DBHelper.TABLE_NAME
+ " ( "+DBHelper.THREAD_ID +","
+DBHelper.URL +","
+DBHelper.START +","
+DBHelper.END +","
+DBHelper.FINISHED +","
+DBHelper.MD5 +","
+DBHelper.OVER +","
+DBHelper.OVER_TIME +")"
+ " values(?,?,?,?,?,?,?,?)",
new Object[] {threadInfo.getId(), threadInfo.getUrl(),
threadInfo.getStart(), threadInfo.getEnd(),
threadInfo.getFinished(),threadInfo.getMd5(),
threadInfo.getOver() ,threadInfo.getOvertime() } // 插入數據
);
mDBHelper.closeDatabase(); // 關閉數據庫
}
/**
* 【刪除文件下載信息】
*/
@Override
public synchronized void deleteThread(String url) {
SQLiteDatabase db = mDBHelper.openDatabase();
db.execSQL( "delete from thread_info where url = ?",
new Object[] {url});
mDBHelper.closeDatabase();
}
/**
* 【更新下載文件下載進度】
*/
@Override
public synchronized void updateThread(String url, int thread_id, int finished, String md5, String over, String over_time) {
SQLiteDatabase db = mDBHelper.openDatabase();
db.execSQL( "update " +DBHelper.TABLE_NAME + " set "
+DBHelper.FINISHED+" = ?where " +DBHelper.URL +" =? and "
+DBHelper.THREAD_ID +" =?",
new Object[] {finished, url, thread_id});
db.execSQL( "update " +DBHelper.TABLE_NAME + " set "
+DBHelper.OVER+" = ?where " +DBHelper.URL +" =? and "
+DBHelper.THREAD_ID +" =?",
new Object[] {over, url, thread_id});
db.execSQL("update " + DBHelper.TABLE_NAME + " set "
+ DBHelper.OVER_TIME + " = ?where " + DBHelper.URL + " =? and "
+ DBHelper.THREAD_ID + " =?",
new Object[]{over_time, url, thread_id});
mDBHelper.closeDatabase();
}
/**
* 獲取文件的文件下載信息
* @return List 包含下載線程信息的list集
* @see com.download.db.ThreadDAO#getThreads(java.lang.String)
*/
@Override
public List getThreads(String url) {
SQLiteDatabase db = mDBHelper.getReadableDatabase(); //注意,此處用【只讀】模式
List list = new ArrayList();
Cursor cursor = db.rawQuery("select * from " +DBHelper.TABLE_NAME
+ " where " +DBHelper.URL +" =?", new String[]{url} );
while (cursor.moveToNext()) {
ThreadInfo threadInfo = new ThreadInfo();
threadInfo.setId(cursor.getInt(cursor.getColumnIndex(DBHelper.THREAD_ID)));
threadInfo.setUrl(cursor.getString(cursor.getColumnIndex(DBHelper.URL)));
threadInfo.setStart(cursor.getInt(cursor.getColumnIndex(DBHelper.START)));
threadInfo.setEnd(cursor.getInt(cursor.getColumnIndex(DBHelper.END)));
threadInfo.setFinished(cursor.getInt(cursor.getColumnIndex(DBHelper.FINISHED)));
threadInfo.setMd5(cursor.getString(cursor.getColumnIndex(DBHelper.MD5)));
threadInfo.setOver(cursor.getString(cursor.getColumnIndex(DBHelper.OVER)));
list.add(threadInfo);
}
cursor.close();
db.close();
return list;
}
/**
* 獲取數據庫中所有下載文件的信息
* @return List 包含下載線程信息的list集
*/
@Override
public List getDBFileInfoList() {
URLTools urlTools = new URLTools();
List list = new ArrayList();
SQLiteDatabase db = mDBHelper.getReadableDatabase(); //注意,此處用【只讀】模式
//查詢數據庫文件下載信息
Cursor cursor = db.rawQuery("select * from " +DBHelper.TABLE_NAME, null );
while( cursor.moveToNext() ){
FileInfo fileInfo = new FileInfo();
if( fileInfo.getId()==0 ){
long finish = 0; //文件已下載的字節數
fileInfo.setOver( cursor.getString(cursor.getColumnIndex(DBHelper.OVER)) );
fileInfo.setOvertime( cursor.getString(cursor.getColumnIndex(DBHelper.OVER_TIME)) );
fileInfo.setMd5( cursor.getString(cursor.getColumnIndex(DBHelper.MD5)) );
fileInfo.setUrl( cursor.getString(cursor.getColumnIndex(DBHelper.URL)) );
String fileName = urlTools.getURLFileName( fileInfo.getUrl(), "/" );
fileInfo.setFileName( fileName );
fileInfo.setIsDownload("false");
//獲取百分比下載進度
for (int i = 0; i < DownloadConfig.DONWNLOAD_THREAD_NUM; i++) {
finish += cursor.getInt( cursor.getColumnIndex(DBHelper.FINISHED) );
if ( i < DownloadConfig.DONWNLOAD_THREAD_NUM -1 ){
cursor.moveToPosition( cursor.getPosition() +1 );
}
}
long finished = finish*100/( cursor.getInt(cursor.getColumnIndex(DBHelper.END)) );
fileInfo.setFinished( (int)finished);
list.add(fileInfo);
}
}
cursor.close();
db.close();
return list;
}
/**
* 文件下載是否已完成
* @return 當存在下載信息返回true;否則返回false
*/
@Override
public boolean isDownloadOver(String url) {
SQLiteDatabase db = mDBHelper.getWritableDatabase();
Cursor cursor = db.rawQuery("select * from " +DBHelper.TABLE_NAME
+" where " +DBHelper.URL +" =? and " +DBHelper.THREAD_ID +" = ?",
new String[]{url, 1+"" } );
boolean over = false;
if (cursor.moveToNext()) {
String overStr = cursor.getString(cursor.getColumnIndex(DBHelper.OVER)) ;
try {
String name = URLDecoder.decode(cursor.getString(cursor.getColumnIndex(DBHelper.URL)), "UTF-8") ;
if (overStr.equals("true")) {
over = true ;
Log.i("db", name+"文件是否下載完成:"+ over );
}else {
over = false ;
Log.i("db",name+ "文件是否下載完成:"+ over );
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
cursor.close();
db.close();
return over;
}
結束啦~~
總結
以上是生活随笔為你收集整理的android多线程下载原理,安卓多线程断点续传下载功能(靠谱第三方组件,原理demo)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言通讯录写入文件,学C三个月了,学了
- 下一篇: android 固定中间焦点,在Andr