




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具
這篇文章給大家介紹怎么在Android應(yīng)用添加一個(gè)下載工具,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。首先如果服務(wù)器文件支持?jǐn)帱c(diǎn)續(xù)傳,則我們需要實(shí)現(xiàn)的主要功能點(diǎn)如下:多線程、斷點(diǎn)續(xù)傳下載下載管理:開(kāi)始、暫停、繼續(xù)、取消、重新開(kāi)始如果服務(wù)器文件不支持?jǐn)帱c(diǎn)續(xù)傳,則只能進(jìn)行普通的單線程下載,而且不能暫停、繼續(xù)。當(dāng)然一般情況服務(wù)器文件都應(yīng)該支持?jǐn)帱c(diǎn)續(xù)傳吧!基本實(shí)現(xiàn)原理:接下來(lái)看看具體的實(shí)現(xiàn)原理,由于我們的下載是基于okhttp實(shí)現(xiàn)的,首先我們需要一個(gè)OkHttpManager類,進(jìn)行最基本的網(wǎng)絡(luò)請(qǐng)求封裝:public
class
OkHttpManager
{
省略
/**
*
異步(根據(jù)斷點(diǎn)請(qǐng)求)
*
*
@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;
}
/**
*
同步請(qǐng)求
*
*
@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();
}
/**
*
文件存在的情況下可判斷服務(wù)端文件是否已經(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請(qǐng)求時(shí)初始化證書(shū)
*
*
@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();
}
}
}這個(gè)類里包含了基本的超時(shí)配置、根據(jù)斷點(diǎn)信息發(fā)起異步請(qǐng)求、校驗(yàn)服務(wù)器文件是否有更新、https證書(shū)配置等。這樣網(wǎng)絡(luò)請(qǐng)求部分就有了。接下來(lái),我們還需要數(shù)據(jù)庫(kù)的支持,以便記錄下載文件的基本信息,這里我們使用SQLite,只有一張表:/**
*
download_info表建表語(yǔ)句
*/
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)";當(dāng)然還有對(duì)應(yīng)表的增刪改查工具類,具體的可參考源碼。由于需要下載管理,所以線程池也是必不可少的,這樣可以避免過(guò)多的創(chuàng)建子線程,達(dá)到復(fù)用的目的,當(dāng)然線程池的大小可以根據(jù)需求進(jìn)行配置,主要代碼如下:public
class
ThreadPool
{
//可同時(shí)下載的任務(wù)數(shù)(核心線程數(shù))
private
int
CORE_POOL_SIZE
=
3;
//緩存隊(duì)列的大小(最大線程數(shù))
private
int
MAX_POOL_SIZE
=
20;
//非核心線程閑置的超時(shí)時(shí)間(秒),如果超時(shí)則會(huì)被回收
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;
}
}接下來(lái)就是我們核心的下載類FileTask了,它實(shí)現(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());
}
}如果下載的目標(biāo)文件、記錄斷點(diǎn)的臨時(shí)文件、數(shù)據(jù)庫(kù)記錄都存在,則我們先判斷服務(wù)器文件是否有更新,如果沒(méi)有更新則根據(jù)之前的記錄直接開(kāi)始下載,否則需要先進(jìn)行斷點(diǎn)下載前的準(zhǔn)備。如果記錄文件不全部存在則需要先判斷是否支持?jǐn)帱c(diǎn)續(xù)傳,如果支持則按照斷點(diǎn)續(xù)傳的流程進(jìn)行,否則采用普通下載。首先看下prepareRangeFile()方法,在這里進(jìn)行斷點(diǎn)續(xù)傳的準(zhǔn)備工作: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)建新的目標(biāo)文件和臨時(shí)文件,childTaskCount代表文件需要通過(guò)幾個(gè)子任務(wù)去下載,這樣就可以得到每個(gè)子任務(wù)需要下載的任務(wù)大小,進(jìn)而得到具體的斷點(diǎn)信息并記錄到臨時(shí)文件中。文件下載我們采用MappedByteBuffer類,相比RandomAccessFile更加的高效。同時(shí)執(zhí)行onStart()方法將代表下載的準(zhǔn)備階段,具體細(xì)節(jié)后面會(huì)說(shuō)到。接下來(lái)看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ù)臨時(shí)文件保存的斷點(diǎn)信息發(fā)起childTaskCount數(shù)量的異步請(qǐng)求,如果響應(yīng)成功則通過(guò)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)中進(jìn)行目前文件的寫(xiě)入和將當(dāng)前下載到的位置保存到臨時(shí)文件:
saveBuffer.put(buffer,
0,
len);
tempBuffer.putLong(index
*
EACH_TEMP_SIZE,
tempBuffer.getLong(index
*
EACH_TEMP_SIZE)
+
len);同時(shí)調(diào)用onProgress()方法將進(jìn)度發(fā)送出去,其中取消、退出保存記錄、暫停需要中斷while循環(huán)。因?yàn)橄螺d是在子線程進(jìn)行的,但我們一般需要在UI線程根據(jù)下載狀態(tài)來(lái)更新UI,所以我們通過(guò)Handler將下載過(guò)程的狀態(tài)數(shù)據(jù)發(fā)送到UI線程:即調(diào)用handler.sendEmptyMessage()方法。最后FileTask類還有一個(gè)saveCommonFile()方法,即進(jìn)行不支持?jǐn)帱c(diǎn)續(xù)傳的普通下載。前邊我們提到了通過(guò)Handler將下載過(guò)程的狀態(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ù)當(dāng)前的下載狀態(tài)進(jìn)行相應(yīng)的操作。如果是START則需要將下載數(shù)據(jù)插入數(shù)據(jù)庫(kù),執(zhí)行初始化回調(diào)等;如果是PROGRESS則執(zhí)行下載進(jìn)度回調(diào);如果是CANCEL則刪除目標(biāo)文件、臨時(shí)文件、數(shù)據(jù)庫(kù)記錄并執(zhí)行對(duì)應(yīng)回調(diào)等;如果是PAUSE則更新數(shù)據(jù)庫(kù)文件記錄并執(zhí)行暫停的回調(diào)等;如果是FINISH則刪除臨時(shí)文件和數(shù)據(jù)庫(kù)記錄并執(zhí)行完成的回調(diào);如果是DESTROY則代表直接在Activity中下載,退出Activity則會(huì)更新數(shù)據(jù)庫(kù)記錄;最后的ERROR則對(duì)應(yīng)出錯(cuò)的情況。具體的細(xì)節(jié)可參考源碼。最后在DownloadManger類里使用線程池執(zhí)行下載操作:ThreadPool.getInstance().getThreadPoolExecutor().execute(fileTask);
//如果正在下載的任務(wù)數(shù)量等于線程池的核心線程數(shù),則新添加的任務(wù)處于等待狀態(tài)
if
(ThreadPool.getInstance().getThreadPoolExecutor().getActiveCount()
==
ThreadPool.getInstance().getCorePoolSize())
{
downloadCallback.onWait();
}以及判斷新添加的任務(wù)是否處于等待的狀態(tài),方便在UI層處理。到這里核心的實(shí)現(xiàn)原理就完了,更多的細(xì)節(jié)可以參考源碼。如何使用:DownloadManger是個(gè)單例類,在這里封裝在了具體的使用操作,我們可以根據(jù)url進(jìn)行下載的開(kāi)始、暫停、繼續(xù)、取消、重新開(kāi)始、線程池配置、https證書(shū)配置、查詢數(shù)據(jù)的記錄數(shù)據(jù)、獲得當(dāng)前某個(gè)下載狀態(tài)的數(shù)據(jù):開(kāi)始一個(gè)下載任務(wù)我們可以通過(guò)三種方式來(lái)進(jìn)行:1、通過(guò)DownloadManager類的start(DownloadDatadownloadData,DownloadCallbackdownloadCallback)方法,data可以設(shè)置url、保存路徑、文件名、子任務(wù)數(shù)量:2、先執(zhí)行DownloadManager類的setOnDownloadCallback(DownloadDatadownloadData,DownloadCallbackdownloadCallback)方法,綁定data和callback,再執(zhí)行start(Stringurl)方法。3、鏈?zhǔn)秸{(diào)用,需要通過(guò)DUtil類來(lái)進(jìn)行:例如DUtil.init(mContext)
.url(url)
.path(Environment.getExternalStorageDirectory()
+
"/DUtil/")
.name(name.xxx)
.childTaskCount(3)
.build()
.start(callback);start()方法會(huì)返回DownloadManager類的實(shí)例,如果你不關(guān)心返回值,使用DownloadManger.getInstance(context)同樣可以得到DownloadManager類的實(shí)例,以便進(jìn)行后續(xù)的暫停、繼續(xù)、取消等操作。關(guān)于callback可以使用DownloadCallback接口實(shí)現(xiàn)完整的回調(diào):new
DownloadCallback()
{
//開(kāi)始
@Override
pub
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 分布式光伏系統(tǒng)施工方案
- 定制戶外廣告牌施工方案
- 交通工程施工方案
- 陽(yáng)臺(tái)欄桿工地施工方案
- 鋼管懸挑大棚的施工方案
- 停復(fù)電專項(xiàng)施工方案
- 貸款離婚協(xié)議書(shū)
- 陽(yáng)泉一體式化糞池施工方案
- 公路新建改建工程施工方案
- 溫庭筠《望江南》ppt課件
- 口腔正畸學(xué)單詞
- 公共場(chǎng)所健康證體檢表
- 普通高等學(xué)校獨(dú)立學(xué)院教育工作合格評(píng)估指標(biāo)體系(第六稿)
- 內(nèi)襯修復(fù)用HTPO管材企標(biāo)
- 部編教材一年級(jí)下冊(cè)生字筆順筆畫(huà)
- 多維閱讀第13級(jí)—A Stolen Baby 小猩猩被偷走了
- 二維火收銀使用手冊(cè)
- 2018版公路工程質(zhì)量檢驗(yàn)評(píng)定標(biāo)準(zhǔn)分項(xiàng)工程質(zhì)量檢驗(yàn)評(píng)定表交通安全設(shè)施
- EN12680.3中文
- 歐科模塊化風(fēng)冷冷水熱泵機(jī)組報(bào)警代碼和維修步驟
評(píng)論
0/150
提交評(píng)論