一麦乞,原生的DownloadManager
從Android 2.3(API level 9)開(kāi)始,Android以Service的方式提供了全局的DownloadManager來(lái)系統(tǒng)級(jí)地優(yōu)化處理長(zhǎng)時(shí)間的下載操作劝评。
DownloadManager支持失敗重試姐直、Notification通知等基本特性。但是DownloadManager提供的接口很有限蒋畜,雖然有暫停狀態(tài)卻沒(méi)有提供主動(dòng)暫停的接口声畏。所以需手動(dòng)實(shí)現(xiàn)斷點(diǎn)續(xù)傳,以及需手動(dòng)實(shí)現(xiàn)單文件的多線程(分段)下載姻成,還是比較麻煩插龄。
總結(jié):DownloadManager比較適用于簡(jiǎn)單的單文件下載。
二科展,第三方組件
1均牢,F(xiàn)ileDownloader
git地址:https://github.com/lingochamp/FileDownloader
中文文檔:https://github.com/lingochamp/FileDownloader/blob/master/README-zh.md
描述:在第三方下載組件中比較突出,成熟才睹,穩(wěn)定徘跪,健壯。支持多任務(wù)下載琅攘,支持多線程下載真椿,支持?jǐn)帱c(diǎn)續(xù)傳,支持替換網(wǎng)絡(luò)請(qǐng)求框架等乎澄。
實(shí)際應(yīng)用:在快手的后臺(tái)進(jìn)程里有l(wèi)iulishuo/FileDownloader,因?yàn)榭焓謨?nèi)有下載服務(wù)测摔,有可能用于應(yīng)用內(nèi)的更新模塊置济,liulishuo的FileDownloader就是這個(gè)組件解恰。
2,OkDownload
git地址:https://github.com/lingochamp/okdownload
中文文檔:https://github.com/lingochamp/okdownload/blob/master/README-zh.md
描述:繼承了所有FileDownloader的優(yōu)點(diǎn)浙于,甚至做了更多的優(yōu)化以及更多的特性护盈。
總結(jié):建議在項(xiàng)目中實(shí)現(xiàn)多任務(wù)多線程下載功能優(yōu)先考慮上面的第三方組件,而不是重復(fù)造輪子羞酗。畢竟能在受眾群大的商業(yè)應(yīng)用中成熟使用的第三方框架腐宋,是經(jīng)過(guò)多次迭代的思想結(jié)晶,考慮了多種情況檀轨,踩過(guò)很多坑胸竞。一般自己從頭寫(xiě)的話,很難比其的做得更好参萄,考慮得更完善卫枝,尤其是在有限的項(xiàng)目開(kāi)發(fā)時(shí)間內(nèi)。
三讹挎,原理demo
這里列一個(gè)兩年前寫(xiě)的demo校赤,用的都是非常基礎(chǔ)的東西筒溃÷砝海考慮的點(diǎn)也比較少,就只是從頭實(shí)現(xiàn)這個(gè)功能怜奖。
(一)思路:
1浑测,如何實(shí)現(xiàn)多線程下載功能?
(后續(xù)都假設(shè)1個(gè)文件啟動(dòng)3個(gè)線程去下載)先起一個(gè)初始線程(initThread)去查詢目標(biāo)下載文件的基本信息比如文件長(zhǎng)度等烦周,生成對(duì)應(yīng)本地文件尽爆。計(jì)算每個(gè)下載線程(DownloadThread)的下載長(zhǎng)度和下載起始結(jié)束點(diǎn),通過(guò)線程池啟動(dòng)3個(gè)下載線程進(jìn)行下載并寫(xiě)入到本地文件的對(duì)應(yīng)起始點(diǎn)中读慎,當(dāng)3個(gè)下載線程都下載完成后漱贱,這個(gè)文件就下載完整了。
2夭委,如何實(shí)現(xiàn)暫停功能幅狮?
設(shè)計(jì)一個(gè)下載任務(wù)類(DownloadTask),開(kāi)始下載一個(gè)文件就新建一個(gè)下載任務(wù)類DownloadTask株灸。下載任務(wù)類負(fù)責(zé)管理啟動(dòng)多個(gè)下載線程開(kāi)始下載崇摄。下載任務(wù)類里設(shè)置一個(gè)公開(kāi)的暫停標(biāo)志,在外部可以設(shè)置暫停標(biāo)志的true/false慌烧,當(dāng)下載線程下載文件時(shí)逐抑,判斷到暫停標(biāo)志為true,則結(jié)束本線程即中斷下載屹蚊。
3厕氨,如何實(shí)現(xiàn)即時(shí)展示當(dāng)前下載進(jìn)度进每?
下載任務(wù)類里設(shè)置一個(gè)表示當(dāng)前已完成下載長(zhǎng)度的變量,在下載線程的下載過(guò)程中命斧,及時(shí)更新累加田晚。設(shè)置每500ms計(jì)算一次下載進(jìn)度=當(dāng)前已完成下載長(zhǎng)度/下載文件總長(zhǎng)度,并發(fā)出廣播国葬。外部接收到廣播后更新ui贤徒。
4,如何實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能(暫停/網(wǎng)絡(luò)故障后再恢復(fù)下載從之前下載進(jìn)度基礎(chǔ)上繼續(xù)下載汇四,而不是重新下載)接奈?
這就必須把下載進(jìn)程的相關(guān)信息本地存儲(chǔ),然后在恢復(fù)下載時(shí)查詢本地存儲(chǔ)的數(shù)據(jù)船殉,獲得目前的下載進(jìn)度鲫趁,再?gòu)哪情_(kāi)始下載。
要給每一個(gè)下載線程保存一條這個(gè)線程相關(guān)信息的數(shù)據(jù)利虫,考慮到當(dāng)新建n個(gè)下載任務(wù)時(shí)挨厚,同時(shí)開(kāi)啟3n個(gè)下載線程,就需要保存3n條線程信息數(shù)據(jù)糠惫。而在暫停/網(wǎng)絡(luò)故障/下載失敗時(shí)疫剃,都要更新對(duì)應(yīng)數(shù)據(jù),選用本地?cái)?shù)據(jù)庫(kù)sqlite來(lái)存儲(chǔ)數(shù)據(jù)硼讽。
安卓的數(shù)據(jù)表的操作巢价,基本就是“數(shù)據(jù)模型+helper幫助類+dao實(shí)現(xiàn)類”來(lái)一套。
但是注意這里是多線程操作數(shù)據(jù)表固阁,“增刪改查”操作里壤躲,“增刪改”方法都要加上synchronized修飾詞。synchronized是保證線程安全备燃,就是在當(dāng)多個(gè)線程操作同一資源時(shí)保證某一時(shí)刻只能有一個(gè)線程在執(zhí)行這個(gè)任務(wù)碉克。
舉個(gè)例子,想象一個(gè)線程是一個(gè)人并齐,一張數(shù)據(jù)表裝在一棟房子里漏麦,房子有兩個(gè)門(mén),門(mén)a只給不帶synchronized方法的人進(jìn)况褪,門(mén)b只給帶synchronized方法的人進(jìn)撕贞。門(mén)a沒(méi)什么條件,只要是使用不帶synchronized方法的人都可以直接進(jìn)去测垛。門(mén)b卻有條件捏膨,一次只能進(jìn)去一個(gè)人,并且下一個(gè)進(jìn)去的人要在上一個(gè)進(jìn)去的人出來(lái)之后才能進(jìn)去食侮。
synchronized更多詳細(xì)資料參考:http://www.reibang.com/p/d53bf830fa09
https://www.cnblogs.com/yulinfeng/p/11020576.html
(二)當(dāng)年湊zhuanli畫(huà)的流程圖:
(三)代碼:
啟動(dòng)下載
Intent intent = new Intent( mContext, DownloadService.class );
fileInfo.setIsDownload("true");
intent.setAction(IntentAction.ACTION_START);
intent.putExtra( KeyName.FILEINFO_TAG, fileInfo );
mContext.startService(intent); // 通過(guò)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的標(biāo)志
*/
private static final int MSG_INIT =0 ;
/**
* 文件下載線程任務(wù)的集合
*/
private MapmTask =new LinkedHashMap();
/**
* onStartCommand()接收Activity中StartService發(fā)送的信息
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 獲取Activity傳進(jìn)來(lái)的參數(shù)
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) );
// 開(kāi)啟多線程下載
if ( IntentAction.ACTION_START.equals(intent.getAction()) ) {
initThread mInitThread =new initThread(fileInfo);
DownloadTask.sExecutorService.execute(mInitThread);// 通過(guò)線程池開(kāi)啟初始化線程
// 暫停多線程下載
}else if ( IntentAction.ACTION_PAUSE.equals(intent.getAction()) ) {
DownloadTask tast =mTask.get(fileInfo.getId() ); // 從集合中取出下載任務(wù)
if (tast !=null) {
tast.isPause =true ;
}
}else if(IntentAction.ACTION_DELETE.equals(intent.getAction())){
DownloadTask tast =mTask.get(fileInfo.getId() ); // 從集合中取出下載任務(wù)
if (tast !=null) {
tast.isDel=true ;
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinderonBind(Intent intent) {
return null;
}
/**
* 初始化文件下載線程:創(chuàng)建本地下載位置号涯,并開(kāi)啟下載任務(wù)
**/
class initThread extends Thread{
/**
* 下載的文件的所有屬性信息
*/
private FileInfomFileInfo =null ;
/**
* 初始化文件下載線程:確保創(chuàng)建本地下載位置熬北,并開(kāi)啟文件下載任務(wù)
* @param mFileInfo 下載的文件的所有屬性信息
*/
public initThread(FileInfo mFileInfo) {
this.mFileInfo = mFileInfo;
}
//開(kāi)啟開(kāi)啟下載任務(wù)
Handlerhandler =new Handler(){
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INIT:
FileInfo fileInfo = (FileInfo) msg.obj ;
// 啟動(dòng)下載任務(wù)
DownloadTask task =new DownloadTask( DownloadService.this, fileInfo, DownloadConfig.DONWNLOAD_THREAD_NUM);
task.download();
//把下載任務(wù)添加到下載集合中
mTask.put(fileInfo.getId(), task); //將開(kāi)啟下載線程的id和實(shí)例添加到map中,在暫停時(shí)通過(guò)id獲取實(shí)例诚隙,并令它暫停
break;
default:
break;
}
}
};
@Override
public void run(){
HttpURLConnection conn =null ;
RandomAccessFile raf =null ;// 隨機(jī)訪問(wèn)文件,可以在文件的隨機(jī)寫(xiě)入起胰,對(duì)應(yīng)斷點(diǎn)續(xù)傳功能
try {
// 連接網(wǎng)絡(luò)文件
URL url =new URL(mFileInfo.getUrl() );
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);// 設(shè)置連接超時(shí)時(shí)間
conn.setRequestMethod("GET");
// 獲取文件長(zhǎng)度
int length = -1;
if (conn.getResponseCode()== HttpStatus.SC_OK ) {
length = conn.getContentLength();
Log.i("http", "DownloadService-138行 獲取網(wǎng)絡(luò)數(shù)據(jù)長(zhǎng)度="+ length );
}else{
Log.i("http", "DownloadService-140行 http連接失斁糜帧!");
}
if ( length <=0 ){
return ;
}
// 判斷下載路徑是否存在
File dir =new File( DownloadConfig.DOWNLOAD_PATH );
if ( !dir.exists() ){
dir.mkdir();
}
// 在本地創(chuàng)建文件
File file =new File( dir, mFileInfo.getFileName() );
raf =new RandomAccessFile( file, "rwd" );
// 設(shè)置文件長(zhǎng)度
raf.setLength( length );
mFileInfo.setLength( length );
handler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget(); //將數(shù)據(jù)發(fā)回給handler
//關(guān)閉連接
if (raf!=null) {
raf.close();
}if (conn !=null) {
conn.disconnect();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
DownloadTask
/**
* 下載任務(wù)類
**/
public class DownloadTask {
/**
* 上下文
*/
private Context mContext = null ;
/**
* 下載的文件的所有屬性信息
*/
private FileInfo mFileInfo = null ;
/**
* 數(shù)據(jù)(庫(kù))訪問(wèn)接口
*/
private ThreadDAO mDAO = null;
/**
* 已下載字節(jié)長(zhǎng)度
*/
private int mFinishedLen = 0;
/**
* 下載暫停標(biāo)志
*/
public Boolean isPause = false;
/**
* 下載刪除標(biāo)志
*/
public Boolean isDel = false;
/**
* 默認(rèn)的下載線程數(shù)
*/
private int mThreadNum = 0 ;
/**
* 下載線程集合
*/
private List<DownloadThread> mThreadList = null ;
/**
* 帶緩存線程池效五,s開(kāi)頭表示用到static關(guān)鍵字
*/
public static ExecutorService sExecutorService = Executors.newCachedThreadPool();
/**
* 文件下載的線程任務(wù)類
* @param mContext 上下文
* @param mFileInfo 下載的文件的所有屬性信息
* @param threadNum 文件分段下載線程數(shù)
*/
public DownloadTask(Context mContext, FileInfo mFileInfo, int threadNum) {
this.mContext = mContext;
this.mFileInfo = mFileInfo;
this.mThreadNum = threadNum ;
mDAO = new ThreadDAOIImpl(mContext);
}
/**
* 開(kāi)始下載文件
*/
public void download() {
List<ThreadInfo> threadInfoList = mDAO.getThreads(mFileInfo.getUrl()); // 讀取數(shù)據(jù)庫(kù)中文件下載的信息
// 不存下載線程則創(chuàng)建
if( threadInfoList.size()== 0 ){
int length = mFileInfo.getLength()/mThreadNum ; // 獲取單個(gè)線程下載長(zhǎng)度
for (int i = 0; i < mThreadNum; i++) { // 創(chuàng)建線程信息
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());// 設(shè)置最后一個(gè)線程的下載長(zhǎng)度
}
threadInfoList.add(threadInfo); // 添加線程信息到集合
mDAO.insertThread(threadInfo); // 向數(shù)據(jù)庫(kù)插入文件下載的信息
} // 放到run外面地消,比較不容易產(chǎn)生數(shù)據(jù)庫(kù)的死鎖
}
// 啟動(dòng)多個(gè)線程進(jìn)行下載
mThreadList = new ArrayList<DownloadThread>();
for (ThreadInfo info : threadInfoList) {
DownloadThread downloadThread = new DownloadThread(info);
if ( info.getFinished()<(info.getEnd()-info.getStart())&& info.getId()==0 ||
info.getFinished()<(info.getEnd()-info.getStart()+1)&& info.getId()!=0 ) { //若未完成下載則下載
DownloadTask.sExecutorService.execute(downloadThread);
mThreadList.add(downloadThread); //添加線程到集合中
}
}
}
/**
* 判斷所有線程是否執(zhí)行完成
* @param threadInfo 下載文件的線程信息
*/
private synchronized void checkAllThreadsFinished(ThreadInfo threadInfo){ // synchronized 同步方法,保證同一時(shí)間只有一個(gè)線程訪問(wèn)該方法
boolean allFinished = true ; //所有線程下載結(jié)束標(biāo)識(shí)
// 遍歷線程集合畏妖,判斷是否都下載完畢
for (DownloadThread thread : mThreadList) {
if (!thread.isFinished) {
allFinished = false ;
break ;
}
}
// 所有線程下載結(jié)束:驗(yàn)證md5脉执,相同則存儲(chǔ)下載長(zhǎng)度,否則清空戒劫;發(fā)送廣播通知UI下載任務(wù)結(jié)束
if (allFinished) {
MD5Util md5Util = new MD5Util();
DateTools dateTools = new DateTools();
List<ThreadInfo> threadInfosList =mDAO.getThreads(threadInfo.getUrl());
for (ThreadInfo info :threadInfosList) {
if ( !md5Util.isMD5Equal(info) ) { // md5驗(yàn)證失敗半夷,清空下載長(zhǎng)度
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);
}
}
/**
* 進(jìn)行文件下載的線程
**/
class DownloadThread extends Thread{
/**
* 文件下載線程的信息
*/
private ThreadInfo mThreadInfo = null ;
/**
* 標(biāo)識(shí)線程是否執(zhí)行完成
*/
public Boolean isFinished = false ;
/**
* 廣播下載進(jìn)度的間隔時(shí)間
*/
private final static int BROADCAST_TIME = 100 ;
/**
* httpUrl連接
*/
HttpURLConnection conn = null ;
/**
* 任意寫(xiě)入文件:RandomAccessFile
*/
RandomAccessFile raf= null;
/**
* 輸入流
*/
InputStream input = null ;
/**
* 進(jìn)行文件下載的線程
* @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");
//設(shè)置url上資源下載位置/范圍
int start = mThreadInfo.getStart() +mThreadInfo.getFinished();
conn.setRequestProperty("Range", "bytes=" +start +"-" + mThreadInfo.getEnd() );
//設(shè)置文件本地寫(xiě)入位置
File file = new File( DownloadConfig.DOWNLOAD_PATH, mFileInfo.getFileName() );
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
mFinishedLen +=mThreadInfo.getFinished();
//開(kāi)始下載啦
if ( conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT ){
//讀取數(shù)據(jù)
input = conn.getInputStream();
byte[] buffer = new byte[ 1024 * 4 ];
int len = -1;
long time = System.currentTimeMillis();
while((len = input.read(buffer))!=-1){
//寫(xiě)入文件
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(); // 計(jì)算下載進(jìn)度
double download_rate = (double)len / (double)(1024*BROADCAST_TIME/1000); // 計(jì)算下載速率
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);
}
//下載暫停時(shí),保存下載進(jìn)度搭配數(shù)據(jù)庫(kù)
if (isPause) {
mDAO.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(), mThreadInfo.getFinished(), mThreadInfo.getMd5(), "false", "none" );
return;
}
//刪除下載時(shí)迅细,刪除數(shù)據(jù)庫(kù)內(nèi)的相關(guān)信息
if (isDel) {
if(!mDAO.getThreads(mThreadInfo.getUrl()).isEmpty()) {
mDAO.deleteThread(mThreadInfo.getUrl());
}
// Intent intent = new Intent( IntentAction.ACTION_DELETE);
// mContext.sendBroadcast(intent);
return;
}
// 下載連接獲取輸入流或文件寫(xiě)入失敗巫橄,清空文件下載進(jìn)度,并重新下載
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 ;// 標(biāo)識(shí)線程執(zhí)行完畢
// 檢查下載任務(wù)是否執(zhí)行完畢
checkAllThreadsFinished(mThreadInfo );
}
} catch (Exception e) {
e.printStackTrace();
}finally{//關(guān)閉連接
try {
if (raf !=null) {
raf.close();
}else {
System.out.println("DOwnloadTask-280行 RadomAccessFile發(fā)生錯(cuò)誤");
}if (input !=null) {
input.close();
}else {
System.out.println("DOwnloadTask-280行 inputStream發(fā)生錯(cuò)誤");
}if (conn !=null) {
conn.disconnect();
}else {
System.out.println("DOwnloadTask-280行 HttpURLConnection發(fā)生錯(cuò)誤");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileInfo
/**
* 下載的文件的所有屬性信息
*/
public class FileInfo implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 下載文件的id
*/
private int id ;
/**
* 下載文件總長(zhǎng)度
*/
private int length ;
/**
* 下載文件的web路徑
*/
private String url ;
/**
* 下載文件名
*/
private String fileName ;
/**
* 下載文件的 MD5值
*/
private String md5 ;
/**
* 文件下載進(jìn)度(%)
*/
private double finished ;
/**
* 文件下載速率
*/
private double rate ;
/**
* 下載文件的 MD5值
*/
private String over ;
/**
* 下載文件完成下載的時(shí)間
*/
private String overtime ;
public String getOvertime() {
return overtime;
}
public void setOvertime(String overtime) {
this.overtime = overtime;
}
/**
* 下載文件是否正在下載
*/
private String isDownload ;
public FileInfo() {
}
/**
* 存儲(chǔ)下載文件的屬性信息
* @param id 下載文件的id標(biāo)識(shí)
* @param length 文件總長(zhǎng)度
* @param url 文件web路徑
* @param fileName 下載文件名
* @param finished 文件下載進(jìn)度
* @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 ;
}
/**
* 獲取文件下載狀態(tài)
*/
public String getIsDownload() {
return isDownload;
}
/**
* 設(shè)置文件下載狀態(tài)
* @param isDownload 文件是否正在下載
*/
public void setIsDownload(String isDownload) {
this.isDownload = isDownload;
}
/**
* 獲取文件下載進(jìn)度(%)
*/
public double getRate() {
return rate;
}
/**
* 存儲(chǔ)文件下載進(jìn)度(%)
*/
public void setRate(double rate) {
this.rate = rate;
}
/**
* 獲取文件MD5
*/
public String getMd5() {
return md5;
}
/**
* 存儲(chǔ)文件MD5
*/
public void setMd5(String md5) {
this.md5 = md5;
}
/**
* 獲取下載文件的id標(biāo)識(shí)
*/
public int getId() {
return id;
}
/**
* 設(shè)置下載文件的
*/
public void setId(int id) {
this.id = id;
}
/**
* 獲取下載文件的長(zhǎng)度
*/
public int getLength() {
return length;
}
/**
* 存入下載文件的
*/
public void setLength(int length) {
this.length = length;
}
/**
* 獲取下載文件的URL路徑
*/
public String getUrl() {
return url;
}
/**
* 存儲(chǔ)下載文件的URL路徑
*/
public void setUrl(String url) {
this.url = url;
}
/**
* 獲取下載文件的文件名
*/
public String getFileName() {
return fileName;
}
/**
* 存儲(chǔ)下載文件的文件名
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* 獲取下載文件的下載進(jìn)度
*/
public double getFinished() {
return finished;
}
/**
* 存儲(chǔ)下載文件的存儲(chǔ)進(jìn)度
*/
public void setFinished(double finished) {
this.finished = finished;
}
/**
* 獲取文件完成下載標(biāo)識(shí)
*/
public String getOver() {
return over;
}
/**
* 存儲(chǔ)文件完成下載標(biāo)識(shí)
*/
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標(biāo)識(shí)
*/
private int id ;
/**
* 下載文件的URL路徑
*/
private String url ;
/**
* 文件下載的起始字節(jié)位置
*/
private int start ;
/**
* 文件下載的結(jié)束字節(jié)位置
*/
private int end ;
/**
* 文件已下載的字節(jié)長(zhǎng)度
*/
private int finished ;
/**
* 下載文件的 MD5值
*/
private String md5 ;
/**
* 下載文件是否完成下載
*/
private String over ;
/**
* 下載文件完成下載的時(shí)間
*/
private String overtime ;
public ThreadInfo() {
}
/**
* 存儲(chǔ)文件下載線程的屬性信息
* @param id 文件下載線程id標(biāo)識(shí)
* @param url 下載文件路徑
* @param start 文件下載起始字節(jié)位置
* @param end 文件下載終止字節(jié)位置
* @param finished 文件已下載的字節(jié)長(zhǎng)度
* @param md5 文件的md5值
* @param over 文件是否完成下載
* @param overtime 文件完成下載的時(shí)間
*/
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;
}
/**
* 獲取文件完成下載的時(shí)間
*/
public String getOvertime() {
return overtime;
}
/**
* 設(shè)置文件完成下載的時(shí)間
*/
public void setOver_time(String overtime) {
this.overtime = overtime;
}
/**
* 獲取文件下載線程的id標(biāo)識(shí)
*/
public int getId() {
return id;
}
/**
* 設(shè)置文件下載線程的id標(biāo)識(shí)
*/
public void setId(int id) {
this.id = id;
}
/**
* 獲取文件下載線程的url
*/
public String getUrl() {
return url;
}
/**
* 設(shè)置下載文件的url
*/
public void setUrl(String url) {
this.url = url;
}
/**
* 獲取文件下載線程的起始字節(jié)位置
*/
public int getStart() {
return start;
}
/**
* 設(shè)置下載文件的起始字節(jié)位置
*/
public void setStart(int start) {
this.start = start;
}
/**
* 獲取文件下載線程的結(jié)束字節(jié)位置
*/
public int getEnd() {
return end;
}
/**
* 設(shè)置下載文件的結(jié)束字節(jié)位置
*/
public void setEnd(int end) {
this.end = end;
}
/**
* 獲取文件下載線程已下載的字節(jié)長(zhǎng)度
*/
public int getFinished() {
return finished;
}
/**
* 設(shè)置文件下載線程已下載的字節(jié)長(zhǎng)度
*/
public void setFinished(int finished) {
this.finished = finished;
}
/**
* 獲取文件MD5
*/
public String getMd5() {
return md5;
}
/**
* 存儲(chǔ)文件MD5
*/
public void setMd5(String md5) {
this.md5 = md5;
}
/**
* 獲取文件完成下載標(biāo)識(shí)
*/
public String getOver() {
return over;
}
/**
* 存儲(chǔ)文件完成下載標(biāo)識(shí)
*/
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
/**
* 數(shù)據(jù)庫(kù)幫助類
* 多線程操作數(shù)據(jù)庫(kù)時(shí)注意使用【單例模式】
* 1茵典、構(gòu)造方法定為private
* 2湘换、定義該類的一個(gè)靜態(tài)對(duì)象用以應(yīng)用
* 3、通過(guò)getInstance()方法返回該類對(duì)象统阿,
* 使該方法無(wú)論調(diào)用多少次彩倚,該類都是唯一。
*/
public class DBHelper extends SQLiteOpenHelper{
private AtomicInteger mOpenCounter = new AtomicInteger();
private SQLiteDatabase mDatabase;
/**
* 數(shù)據(jù)庫(kù)名稱
*/
private static final String DB_NAME = "download.db" ;
/**
* 數(shù)據(jù)庫(kù)名稱
*/
public static final String TABLE_NAME = "thread_info" ;
/**
* 文件下載線程id
*/
public static final String THREAD_ID = "thread_id" ;
/**
* 下載文件的url文件
*/
public static final String URL = "url" ;
/**
* 文件下載的起始位置(字節(jié)長(zhǎng)度)
*/
public static final String START = "start" ;
/**
* 文件下載的結(jié)束位置(字節(jié)長(zhǎng)度)
*/
public static final String END = "end" ;
/**
* 文件已下載的字節(jié)長(zhǎng)度
*/
public static final String FINISHED = "finished" ;
/**
* 文件的md5
*/
public static final String MD5 = "md5" ;
/**
* 文件是否完成下載標(biāo)識(shí)
*/
public static final String OVER = "over" ;
/**
* 文件是成下載時(shí)間
*/
public static final String OVER_TIME = "over_time" ;
/**
* 數(shù)據(jù)庫(kù)幫助類的靜態(tài)對(duì)象引用
*/
private static DBHelper sHelper = null ;
/**
* 數(shù)據(jù)庫(kù)版本
*/
private static final int VERSION = 1;
/**
* sql創(chuàng)建保存線程信息表命令句
*/
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出來(lái)帆离,保證db的單例,防止數(shù)據(jù)庫(kù)被鎖定
}
/**
* 獲得類對(duì)象sHelper
*/
public static DBHelper getInstance(Context context){ // 單例模式蜻直,DBHelper只會(huì)被實(shí)例化一次
if (sHelper == null ) { // 靜態(tài)方法訪問(wèn)數(shù)據(jù)庫(kù)盯质,無(wú)論創(chuàng)建多少個(gè)數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象,
sHelper = new DBHelper(context); // 里面的Helper只有一個(gè)概而,保證程序中只有一個(gè)DBHelper對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪問(wèn)
}
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();
}
}
/**
* 創(chuàng)建表
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE); //創(chuàng)建表
}
/**
* 更新數(shù)據(jù)庫(kù)表
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(SQL_DROP);
db.execSQL(SQL_CREATE);
}
}
ThreadDAO
/**
* 【數(shù)據(jù)訪問(wèn)接口】
* DAO:data access object 數(shù)據(jù)訪問(wèn)對(duì)象呼巷,DAO一定是和數(shù)據(jù)庫(kù)的每張表一一對(duì)應(yīng);
* 是一種應(yīng)用程序表承接口(api),可以訪問(wèn)其它的結(jié)構(gòu)化查詢語(yǔ)言(SQL)數(shù)據(jù)庫(kù);
* J2EE用DAO設(shè)計(jì)模塊赎瑰,把底層數(shù)據(jù)訪問(wèn)和高層的商務(wù)邏輯分開(kāi)王悍;典型的DAO有以下幾個(gè)組件:
* 1、一個(gè)DAO工廠類
* 2餐曼、一個(gè)DAO接口
* 3压储、一個(gè)實(shí)現(xiàn)DAO接口的具體類
* 4鲜漩、數(shù)據(jù)傳遞對(duì)象
*/
public interface ThreadDAO {
/**
* 插入文件下載信息
* @param threadInfo 下載線程的信息
*/
public void insertThread(ThreadInfo threadInfo);
/**
* 刪除文件下載信息
*/
public void deleteThread(String url );
/**
* 更新下載文件下載進(jìn)度
* @param url 下載線程的URL
* @param thread_id 下載線程的id
* @param finished 下載線程的文件已下載字節(jié)數(shù)
* @param md5 下載文件的md5
* @param over 文件下載完成標(biāo)識(shí)
* @param over_time 文件下載完成時(shí)間
*/
public void updateThread(String url, int thread_id, int finished, String md5, String over, String over_time) ;
/**
* 查詢文件的文件下載線程信息
* @param url 下載線程的URL
* @return List<ThreadInfo> 線程信息集
*/
public List<ThreadInfo> getThreads(String url) ;
/**
* 獲取所有文件的文件下載信息
* @return List<ThreadInfo> 包含下載線程信息的list集
*/
public List<FileInfo> getDBFileInfoList();
/**
* 文件下載信息是否已結(jié)束下載
* @return 當(dāng)存在下載信息返回true;否則返回false
*/
boolean isDownloadOver(String url);
}
ThreadDAOIImpl
/**
* 【數(shù)據(jù)庫(kù)訪問(wèn)接口的實(shí)現(xiàn)】
*/
public class ThreadDAOIImpl implements ThreadDAO{
/**
* 數(shù)據(jù)庫(kù)幫助類
*/
private DBHelper mDBHelper = null ; // 將DBHelper用private修飾
public ThreadDAOIImpl(Context context) {
mDBHelper = DBHelper.getInstance(context);
}
/**
* 插入文件下載信息】
* 多線程數(shù)據(jù)庫(kù)的增集惋、刪孕似、改(更新)方法用synchronized修飾,以保證線程安全:
* 保證同一時(shí)間段不會(huì)有多個(gè)(只有一個(gè))線程對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改刮刑,需等待線程執(zhí)
* 行完后再開(kāi)啟線程執(zhí)行下一個(gè)功能喉祭;而查詢因?yàn)椴挥貌僮鲾?shù)據(jù)庫(kù),不會(huì)導(dǎo)致 數(shù)據(jù)庫(kù)
* 死鎖 雷绢,所以不用
* @see com.download.db.ThreadDAO#insertThread(com.download.entities.ThreadInfo)
*/
@Override
public synchronized void insertThread(ThreadInfo threadInfo ){
SQLiteDatabase db = mDBHelper.openDatabase(); // 實(shí)例化數(shù)據(jù)庫(kù)泛烙,設(shè)置為【讀寫(xiě)】模式
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() } // 插入數(shù)據(jù)
);
mDBHelper.closeDatabase(); // 關(guān)閉數(shù)據(jù)庫(kù)
}
/**
* 【刪除文件下載信息】
*/
@Override
public synchronized void deleteThread(String url) {
SQLiteDatabase db = mDBHelper.openDatabase();
db.execSQL( "delete from thread_info where url = ?",
new Object[] {url});
mDBHelper.closeDatabase();
}
/**
* 【更新下載文件下載進(jìn)度】
*/
@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<ThreadInfo> 包含下載線程信息的list集
* @see com.download.db.ThreadDAO#getThreads(java.lang.String)
*/
@Override
public List<ThreadInfo> getThreads(String url) {
SQLiteDatabase db = mDBHelper.getReadableDatabase(); //注意,此處用【只讀】模式
List<ThreadInfo> list = new ArrayList<ThreadInfo>();
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;
}
/**
* 獲取數(shù)據(jù)庫(kù)中所有下載文件的信息
* @return List<ThreadInfo> 包含下載線程信息的list集
*/
@Override
public List<FileInfo> getDBFileInfoList() {
URLTools urlTools = new URLTools();
List<FileInfo> list = new ArrayList<FileInfo>();
SQLiteDatabase db = mDBHelper.getReadableDatabase(); //注意翘紊,此處用【只讀】模式
//查詢數(shù)據(jù)庫(kù)文件下載信息
Cursor cursor = db.rawQuery("select * from " +DBHelper.TABLE_NAME, null );
while( cursor.moveToNext() ){
FileInfo fileInfo = new FileInfo();
if( fileInfo.getId()==0 ){
long finish = 0; //文件已下載的字節(jié)數(shù)
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");
//獲取百分比下載進(jìn)度
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 當(dāng)存在下載信息返回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;
}