




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領
文檔簡介
【移動應用開發(fā)技術(shù)】怎么在Android應用添加一個下載工具
這篇文章給大家介紹怎么在Android應用添加一個下載工具,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。首先如果服務器文件支持斷點續(xù)傳,則我們需要實現(xiàn)的主要功能點如下:多線程、斷點續(xù)傳下載下載管理:開始、暫停、繼續(xù)、取消、重新開始如果服務器文件不支持斷點續(xù)傳,則只能進行普通的單線程下載,而且不能暫停、繼續(xù)。當然一般情況服務器文件都應該支持斷點續(xù)傳吧!基本實現(xiàn)原理:接下來看看具體的實現(xiàn)原理,由于我們的下載是基于okhttp實現(xiàn)的,首先我們需要一個OkHttpManager類,進行最基本的網(wǎng)絡請求封裝:public
class
OkHttpManager
{
省略
/**
*
異步(根據(jù)斷點請求)
*
*
@param
url
*
@param
start
*
@param
end
*
@param
callback
*
@return
*/
public
Call
initRequest(String
url,
long
start,
long
end,
final
Callback
callback)
{
Request
request
=
new
Request.Builder()
.url(url)
.header("Range",
"bytes="
+
start
+
"-"
+
end)
.build();
Call
call
=
builder.build().newCall(request);
call.enqueue(callback);
return
call;
}
/**
*
同步請求
*
*
@param
url
*
@return
*
@throws
IOException
*/
public
Response
initRequest(String
url)
throws
IOException
{
Request
request
=
new
Request.Builder()
.url(url)
.header("Range",
"bytes=0-")
.build();
return
builder.build().newCall(request).execute();
}
/**
*
文件存在的情況下可判斷服務端文件是否已經(jīng)更改
*
*
@param
url
*
@param
lastModify
*
@return
*
@throws
IOException
*/
public
Response
initRequest(String
url,
String
lastModify)
throws
IOException
{
Request
request
=
new
Request.Builder()
.url(url)
.header("Range",
"bytes=0-")
.header("If-Range",
lastModify)
.build();
return
builder.build().newCall(request).execute();
}
/**
*
https請求時初始化證書
*
*
@param
certificates
*
@return
*/
public
void
setCertificates(InputStream...
certificates)
{
try
{
CertificateFactory
certificateFactory
=
CertificateFactory.getInstance("X.509");
KeyStore
keyStore
=
KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int
index
=
0;
for
(InputStream
certificate
:
certificates)
{
String
certificateAlias
=
Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias,
certificateFactory.generateCertificate(certificate));
try
{
if
(certificate
!=
null)
certificate.close();
}
catch
(IOException
e)
{
}
}
SSLContext
sslContext
=
SSLContext.getInstance("TLS");
TrustManagerFactory
trustManagerFactory
=
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null,
trustManagerFactory.getTrustManagers(),
new
SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory());
}
catch
(Exception
e)
{
e.printStackTrace();
}
}
}這個類里包含了基本的超時配置、根據(jù)斷點信息發(fā)起異步請求、校驗服務器文件是否有更新、https證書配置等。這樣網(wǎng)絡請求部分就有了。接下來,我們還需要數(shù)據(jù)庫的支持,以便記錄下載文件的基本信息,這里我們使用SQLite,只有一張表:/**
*
download_info表建表語句
*/
public
static
final
String
CREATE_DOWNLOAD_INFO
=
"create
table
download_info
("
+
"id
integer
primary
key
autoincrement,
"
+
"url
text,
"
+
"path
text,
"
+
"name
text,
"
+
"child_task_count
integer,
"
+
"current_length
integer,
"
+
"total_length
integer,
"
+
"percentage
real,
"
+
"last_modify
text,
"
+
"date
text)";當然還有對應表的增刪改查工具類,具體的可參考源碼。由于需要下載管理,所以線程池也是必不可少的,這樣可以避免過多的創(chuàng)建子線程,達到復用的目的,當然線程池的大小可以根據(jù)需求進行配置,主要代碼如下:public
class
ThreadPool
{
//可同時下載的任務數(shù)(核心線程數(shù))
private
int
CORE_POOL_SIZE
=
3;
//緩存隊列的大?。ㄗ畲缶€程數(shù))
private
int
MAX_POOL_SIZE
=
20;
//非核心線程閑置的超時時間(秒),如果超時則會被回收
private
long
KEEP_ALIVE
=
10L;
private
ThreadPoolExecutor
THREAD_POOL_EXECUTOR;
private
ThreadFactory
sThreadFactory
=
new
ThreadFactory()
{
private
final
AtomicInteger
mCount
=
new
AtomicInteger();
@Override
public
Thread
newThread(@NonNull
Runnable
runnable)
{
return
new
Thread(runnable,
"download_task#"
+
mCount.getAndIncrement());
}
};
省略
public
void
setCorePoolSize(int
corePoolSize)
{
if
(corePoolSize
==
0)
{
return;
}
CORE_POOL_SIZE
=
corePoolSize;
}
public
void
setMaxPoolSize(int
maxPoolSize)
{
if
(maxPoolSize
==
0)
{
return;
}
MAX_POOL_SIZE
=
maxPoolSize;
}
public
int
getCorePoolSize()
{
return
CORE_POOL_SIZE;
}
public
int
getMaxPoolSize()
{
return
MAX_POOL_SIZE;
}
public
ThreadPoolExecutor
getThreadPoolExecutor()
{
if
(THREAD_POOL_EXECUTOR
==
null)
{
THREAD_POOL_EXECUTOR
=
new
ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
new
LinkedBlockingDeque<Runnable>(),
sThreadFactory);
}
return
THREAD_POOL_EXECUTOR;
}
}接下來就是我們核心的下載類FileTask了,它實現(xiàn)了Runnable接口,這樣就能在線程池中執(zhí)行,首先看下run()方法的邏輯:@Override
public
void
run()
{
try
{
File
saveFile
=
new
File(path,
name);
File
tempFile
=
new
File(path,
name
+
".temp");
DownloadData
data
=
Db.getInstance(context).getData(url);
if
(Utils.isFileExists(saveFile)
&&
Utils.isFileExists(tempFile)
&&
data
!=
null)
{
Response
response
=
OkHttpManager.getInstance().initRequest(url,
data.getLastModify());
if
(response
!=
null
&&
response.isSuccessful()
&&
Utils.isNotServerFileChanged(response))
{
TEMP_FILE_TOTAL_SIZE
=
EACH_TEMP_SIZE
*
data.getChildTaskCount();
onStart(data.getTotalLength(),
data.getCurrentLength(),
"",
true);
}
else
{
prepareRangeFile(response);
}
saveRangeFile();
}
else
{
Response
response
=
OkHttpManager.getInstance().initRequest(url);
if
(response
!=
null
&&
response.isSuccessful())
{
if
(Utils.isSupportRange(response))
{
prepareRangeFile(response);
saveRangeFile();
}
else
{
saveCommonFile(response);
}
}
}
}
catch
(IOException
e)
{
onError(e.toString());
}
}如果下載的目標文件、記錄斷點的臨時文件、數(shù)據(jù)庫記錄都存在,則我們先判斷服務器文件是否有更新,如果沒有更新則根據(jù)之前的記錄直接開始下載,否則需要先進行斷點下載前的準備。如果記錄文件不全部存在則需要先判斷是否支持斷點續(xù)傳,如果支持則按照斷點續(xù)傳的流程進行,否則采用普通下載。首先看下prepareRangeFile()方法,在這里進行斷點續(xù)傳的準備工作:private
void
prepareRangeFile(Response
response)
{
省略
try
{
File
saveFile
=
Utils.createFile(path,
name);
File
tempFile
=
Utils.createFile(path,
name
+
".temp");
long
fileLength
=
response.body().contentLength();
onStart(fileLength,
0,
Utils.getLastModify(response),
true);
Db.getInstance(context).deleteData(url);
Utils.deleteFile(saveFile,
tempFile);
saveRandomAccessFile
=
new
RandomAccessFile(saveFile,
"rws");
saveRandomAccessFile.setLength(fileLength);
tempRandomAccessFile
=
new
RandomAccessFile(tempFile,
"rws");
tempRandomAccessFile.setLength(TEMP_FILE_TOTAL_SIZE);
tempChannel
=
tempRandomAccessFile.getChannel();
MappedByteBuffer
buffer
=
tempChannel.map(READ_WRITE,
0,
TEMP_FILE_TOTAL_SIZE);
long
start;
long
end;
int
eachSize
=
(int)
(fileLength
/
childTaskCount);
for
(int
i
=
0;
i
<
childTaskCount;
i++)
{
if
(i
==
childTaskCount
-
1)
{
start
=
i
*
eachSize;
end
=
fileLength
-
1;
}
else
{
start
=
i
*
eachSize;
end
=
(i
+
1)
*
eachSize
-
1;
}
buffer.putLong(start);
buffer.putLong(end);
}
}
catch
(Exception
e)
{
onError(e.toString());
}
finally
{
省略
}
}首先是清除歷史記錄,創(chuàng)建新的目標文件和臨時文件,childTaskCount代表文件需要通過幾個子任務去下載,這樣就可以得到每個子任務需要下載的任務大小,進而得到具體的斷點信息并記錄到臨時文件中。文件下載我們采用MappedByteBuffer類,相比RandomAccessFile更加的高效。同時執(zhí)行onStart()方法將代表下載的準備階段,具體細節(jié)后面會說到。接下來看saveRangeFile()方法:private
void
saveRangeFile()
{
省略
for
(int
i
=
0;
i
<
childTaskCount;
i++)
{
final
int
tempI
=
i;
Call
call
=
OkHttpManager.getInstance().initRequest(url,
range.start[i],
range.end[i],
new
Callback()
{
@Override
public
void
onFailure(Call
call,
IOException
e)
{
onError(e.toString());
}
@Override
public
void
onResponse(Call
call,
Response
response)
throws
IOException
{
startSaveRangeFile(response,
tempI,
range,
saveFile,
tempFile);
}
});
callList.add(call);
}
省略
}就是根據(jù)臨時文件保存的斷點信息發(fā)起childTaskCount數(shù)量的異步請求,如果響應成功則通過startSaveRangeFile()方法分段保存文件:private
void
startSaveRangeFile(Response
response,
int
index,
Ranges
range,
File
saveFile,
File
tempFile)
{
省略
try
{
saveRandomAccessFile
=
new
RandomAccessFile(saveFile,
"rws");
saveChannel
=
saveRandomAccessFile.getChannel();
MappedByteBuffer
saveBuffer
=
saveChannel.map(READ_WRITE,
range.start[index],
range.end[index]
-
range.start[index]
+
1);
tempRandomAccessFile
=
new
RandomAccessFile(tempFile,
"rws");
tempChannel
=
tempRandomAccessFile.getChannel();
MappedByteBuffer
tempBuffer
=
tempChannel.map(READ_WRITE,
0,
TEMP_FILE_TOTAL_SIZE);
inputStream
=
response.body().byteStream();
int
len;
byte[]
buffer
=
new
byte[BUFFER_SIZE];
while
((len
=
inputStream.read(buffer))
!=
-1)
{
//取消
if
(IS_CANCEL)
{
handler.sendEmptyMessage(CANCEL);
callList.get(index).cancel();
break;
}
saveBuffer.put(buffer,
0,
len);
tempBuffer.putLong(index
*
EACH_TEMP_SIZE,
tempBuffer.getLong(index
*
EACH_TEMP_SIZE)
+
len);
onProgress(len);
//退出保存記錄
if
(IS_DESTROY)
{
handler.sendEmptyMessage(DESTROY);
callList.get(index).cancel();
break;
}
//暫停
if
(IS_PAUSE)
{
handler.sendEmptyMessage(PAUSE);
callList.get(index).cancel();
break;
}
}
addCount();
}
catch
(Exception
e)
{
onError(e.toString());
}
finally
{
省略
}在while循環(huán)中進行目前文件的寫入和將當前下載到的位置保存到臨時文件:
saveBuffer.put(buffer,
0,
len);
tempBuffer.putLong(index
*
EACH_TEMP_SIZE,
tempBuffer.getLong(index
*
EACH_TEMP_SIZE)
+
len);同時調(diào)用onProgress()方法將進度發(fā)送出去,其中取消、退出保存記錄、暫停需要中斷while循環(huán)。因為下載是在子線程進行的,但我們一般需要在UI線程根據(jù)下載狀態(tài)來更新UI,所以我們通過Handler將下載過程的狀態(tài)數(shù)據(jù)發(fā)送到UI線程:即調(diào)用handler.sendEmptyMessage()方法。最后FileTask類還有一個saveCommonFile()方法,即進行不支持斷點續(xù)傳的普通下載。前邊我們提到了通過Handler將下載過程的狀態(tài)數(shù)據(jù)發(fā)送到UI線程,接下看下ProgressHandler類基本的處理:private
Handler
mHandler
=
new
Handler()
{
@Override
public
void
handleMessage(Message
msg)
{
super.handleMessage(msg);
switch
(mCurrentState)
{
case
START:
break;
case
PROGRESS:
break;
case
CANCEL:
break;
case
PAUSE:
break;
case
FINISH:
break;
case
DESTROY:
break;
case
ERROR:
break;
}
}
};在handleMessage()方法中,我們根據(jù)當前的下載狀態(tài)進行相應的操作。如果是START則需要將下載數(shù)據(jù)插入數(shù)據(jù)庫,執(zhí)行初始化回調(diào)等;如果是PROGRESS則執(zhí)行下載進度回調(diào);如果是CANCEL則刪除目標文件、臨時文件、數(shù)據(jù)庫記錄并執(zhí)行對應回調(diào)等;如果是PAUSE則更新數(shù)據(jù)庫文件記錄并執(zhí)行暫停的回調(diào)等;如果是FINISH則刪除臨時文件和數(shù)據(jù)庫記錄并執(zhí)行完成的回調(diào);如果是DESTROY則代表直接在Activity中下載,退出Activity則會更新數(shù)據(jù)庫記錄;最后的ERROR則對應出錯的情況。具體的細節(jié)可參考源碼。最后在DownloadManger類里使用線程池執(zhí)行下載操作:ThreadPool.getInstance().getThreadPoolExecutor().execute(fileTask);
//如果正在下載的任務數(shù)量等于線程池的核心線程數(shù),則新添加的任務處于等待狀態(tài)
if
(ThreadPool.getInstance().getThreadPoolExecutor().getActiveCount()
==
ThreadPool.getInstance().getCorePoolSize())
{
downloadCallback.onWait();
}以及判斷新添加的任務是否處于等待的狀態(tài),方便在UI層處理。到這里核心的實現(xiàn)原理就完了,更多的細節(jié)可以參考源碼。如何使用:DownloadManger是個單例類,在這里封裝在了具體的使用操作,我們可以根據(jù)url進行下載的開始、暫停、繼續(xù)、取消、重新開始、線程池配置、https證書配置、查詢數(shù)據(jù)的記錄數(shù)據(jù)、獲得當前某個下載狀態(tài)的數(shù)據(jù):開始一個下載任務我們可以通過三種方式來進行:1、通過DownloadManager類的start(DownloadDatadownloadData,DownloadCallbackdownloadCallback)方法,data可以設置url、保存路徑、文件名、子任務數(shù)量:2、先執(zhí)行DownloadManager類的setOnDownloadCallback(DownloadDatadownloadData,DownloadCallbackdownloadCallback)方法,綁定data和callback,再執(zhí)行start(Stringurl)方法。3、鏈式調(diào)用,需要通過DUtil類來進行:例如DUtil.init(mContext)
.url(url)
.path(Environment.getExternalStorageDirectory()
+
"/DUtil/")
.name(name.xxx)
.childTaskCount(3)
.build()
.start(callback);start()方法會返回DownloadManager類的實例,如果你不關(guān)心返回值,使用DownloadManger.getInstance(context)同樣可以得到DownloadManager類的實例,以便進行后續(xù)的暫停、繼續(xù)、取消等操作。關(guān)于callback可以使用DownloadCallback接口實現(xiàn)完整的回調(diào):new
DownloadCallback()
{
//開始
@Override
pub
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 外墻冬季施工方案
- 防滑地磚樓面施工方案
- 2025年天津法檢筆試試題及答案
- 2025年找貨運司機面試題及答案
- 低利率時代的投資和資產(chǎn)配置策略
- 噴射砂漿加固施工方案
- 清理植被灌木施工方案
- 鋼構(gòu)的施工方案
- 2025年唐山工業(yè)職業(yè)技術(shù)學院單招職業(yè)適應性測試題庫參考答案
- 2025年山東省濱州地區(qū)單招職業(yè)適應性測試題庫新版
- 最實用的渣土系數(shù)表
- 重癥病人營養(yǎng)支持ICU
- 工會組建工作實務課件
- 外浮頂儲罐·內(nèi)浮頂儲罐泡沫堰PPT
- 甘肅省平?jīng)鍪懈骺h區(qū)鄉(xiāng)鎮(zhèn)行政村村莊村名明細及行政區(qū)劃代碼
- (完整版)初中道德與法治課程標準
- 自動化腹膜透析(APD)的臨床應用課件
- 滌綸長絲生產(chǎn)標準工藝簡介
- 數(shù)字圖像處理-6第六章圖像去噪課件
- 監(jiān)理施工設計圖紙簽發(fā)表
- DB43∕T 801-2013 二次張拉低回縮鋼絞線豎向預應力短索錨固體系設計、施工和驗收規(guī)范
評論
0/150
提交評論