【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具_(dá)第1頁(yè)
【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具_(dá)第2頁(yè)
【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具_(dá)第3頁(yè)
【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具_(dá)第4頁(yè)
【移動(dòng)應(yīng)用開(kāi)發(fā)技術(shù)】怎么在Android應(yīng)用添加一個(gè)下載工具_(dá)第5頁(yè)
已閱讀5頁(yè),還剩11頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論