最近在做云備份功能系谐。包含上傳和下載。網(wǎng)上的斷點(diǎn)上傳太復(fù)雜了魂奥,不穩(wěn)定菠剩,缺少重試機(jī)制等。我用最簡(jiǎn)單的方式交會(huì)你們
步驟和流程:
1.先分塊耻煤,把塊分好具壮!
總大小/每一塊的值?
2.md5是一塊文件的md5還是
都要上傳,獲取字節(jié)數(shù)組的md5
3. 多并發(fā)處理網(wǎng)絡(luò)請(qǐng)求
并行:CountDownLatch來(lái)監(jiān)聽(tīng)結(jié)果
當(dāng)使用多線(xiàn)程去下載或者上傳時(shí),由于多個(gè)線(xiàn)程互不干擾的執(zhí)行哈蝇,怎么判斷所有的線(xiàn)程是否執(zhí)行完畢呢棺妓?線(xiàn)程池沒(méi)有提供這樣的方法,那么只能自己去實(shí)現(xiàn)了买鸽。一般可以設(shè)置一個(gè)整形的標(biāo)志位涧郊,初始化為0,當(dāng)一個(gè)線(xiàn)程完成后就把這個(gè)標(biāo)志位+1眼五,然后判斷標(biāo)志位是否等于=任務(wù)的數(shù)量妆艘,等于就代表所有任務(wù)都執(zhí)行完成了,但是這樣感覺(jué)不是很優(yōu)雅看幼。java中提供了一個(gè)計(jì)數(shù)器批旺,我們可以使用CountDownLatch來(lái)判斷所有任務(wù)是否完成
串行:每次循環(huán),他都給我返回诵姜,即使是成功的也是汽煮,那么20多塊,用串行的方式棚唆,寫(xiě)同步請(qǐng)求暇赤,得到一個(gè)再用一個(gè)
4.進(jìn)度計(jì)算? ?(已上傳的塊數(shù)*每個(gè)塊數(shù)大小)
5.結(jié)束的判斷宵凌!怎么知道全部上傳完成了鞋囊?? ?判斷所有的塊都上傳成功了
6.重試機(jī)制? ? ? ? ? ? ? ? ? ? ?添加計(jì)算計(jì)算
7.取消上傳,有回調(diào)嗎瞎惫?是不是應(yīng)該給取消接口
okhttp的取消回調(diào)
8.斷點(diǎn)怎么實(shí)現(xiàn)的:? ? ? 一塊一塊溜腐,上傳的塊不會(huì)再次上傳
9.進(jìn)程不在,怎么知道失敗的個(gè)數(shù)和數(shù)量瓜喇?
由后臺(tái)返回?cái)?shù)據(jù)才行挺益。給我返回分塊的總數(shù),已經(jīng)完成的個(gè)數(shù)乘寒!還有每一個(gè)的編號(hào)望众!
/**
*分片上傳
*/
fun multiPartUpload(fileAccess: FileAccess, totalChunks: Int, chunkNumber: Int): UploadResult {
var uploadResult = UploadResult()
var uploadFile = File(transferItemModel.path)
val token = HttpRequestHelper.getToken()
var offset = chunkNumber.toLong() -1
? ? BNLog.d(TAG, "offset:" + offset)
var fileByte = fileAccess.getBlock(offset * FileAccess.CHUNK_SIZE, uploadFile)
var mutipartBody = MultipartBody.Builder()
mutipartBody.setType(MultipartBody.FORM)
mutipartBody.addFormDataPart("fileFolderId", transferItemModel.fileFolderId)
.addFormDataPart("uploadId", uploadId)
.addFormDataPart("totalChunks", totalChunks.toString())
.addFormDataPart("chunkNumber", chunkNumber.toString())
.addFormDataPart("isAutoComplete", 1.toString())
.addFormDataPart("identifier", MultiPartUtil.getBytesMd5(fileByte))
sumByte = fileByte.size +sumByte
? ? BNLog.d(TAG, "multiPartUpload 上傳字節(jié)大小:" + fileByte.size +"當(dāng)前已經(jīng)上傳的總大小:" +sumByte)
var body = RequestBody.create(MultipartBody.FORM, fileByte)
mutipartBody.addFormDataPart("file", uploadFile.name, body)
var request = Request.Builder().url(HttpPathEntity.HOST + HttpPathEntity.uploadPart.path)
.header("token", token)
.header("sign", HttpRequestHelper.DEFAULT_SIGN)
.header("deviceId", DeviceUtils.androidID)
.header("timestamp", System.currentTimeMillis().toString())
.post(mutipartBody.build())
.build()
var client = OkHttpClient()
call = client.newCall(request)
try {
var response =call.execute()
var responseBody = response.body()
if (response.isSuccessful) {
var content = response.body()?.string()
var result = JSONObject(content)
var resultCode = result.getInt("code")
var message = result.getString("message")
uploadResult.code = resultCode
uploadResult.msg = message
BNLog.d(TAG, " success responseBody:" + responseBody +"content:" + content)
}else {
BNLog.e(TAG, " fail responseBody:")
}
}catch (e: Exception) {
}
return uploadResult
}
suspend fun uploadFile() {
BNLog.d(TAG, "uploadFile filePath = " +transferItemModel.path +"threadName:" + Thread.currentThread().name)
var fileAccess = FileAccess(transferItemModel.path)
var totalChunks = fileAccess.chunkSize
? ? ? ? var retryTimes =DEFAULT_RETRY_TIME
? ? ? ? var isOk =false
? ? ? ? var successCount =0
? ? ? ? var totalFileByteSize = MultiPartUtil.getFileByteSize(transferItemModel.path)
BNLog.e(TAG, "文件的總長(zhǎng)度:" +transferItemModel.fileSize +"? 讀取的總字節(jié):" + totalFileByteSize)
while (retryTimes >0 && !isOk) {
for (iin 1 until (totalChunks +1)) {
BNLog.d(TAG, "uploadFile i=" + i)
if (i == totalChunks) {
BNLog.d(TAG, "上傳最后一塊:" + (totalChunks -1))
}else {
//? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? }
var uploadResult = multiPartUpload(fileAccess, totalChunks, i)
if (uploadResult.code == UploadResult.SUCCESS_CODE) {//上傳成功
? ? ? ? ? ? ? ? ? ? successCount++
var currentSize = successCount * FileAccess.CHUNK_SIZE//todo
? ? ? ? ? ? ? ? ? ? var progress = (100 * currentSize / totalFileByteSize).toInt()
BNLog.e(TAG, "progress:" + progress)
if (progress >100) {
progress =100
? ? ? ? ? ? ? ? ? ? }
this.ossListener?.onProgress(currentSize.toLong(), totalFileByteSize, progress)
}
//? ? ? ? ? ? ? ? return//test
? ? ? ? ? ? }
isOk = checkIsUploadSuccess(totalChunks, successCount)
isOk =true//test
? ? ? ? ? ? retryTimes--
if (isOk) {
BNLog.d(TAG, "uploadFile 全部上傳成功")
break
? ? ? ? ? ? }
}
retryTimes =0
? ? ? ? //上傳完成,判斷是否有失敗的塊
? ? ? ? if (isOk) {
BNLog.e(TAG, "一個(gè)文件上傳成功:" +transferItemModel.path)
this.ossListener?.onSuccess()
this.ossListener?.onReport(true)
var baonengId = UserCenterManage.getInstance(CloudServiceApp.getInstance()).getRefreshToken()
var uuid =transferItemModel.uuid
? ? ? ? ? ? var uuidserver = baonengId +"/" + uuid
syncFileMeta(transferItemModel.fileFolderId, transferItemModel.name, uuidserver, transferItemModel.fileSize.toString(), transferItemModel.md5, transferItemModel.mineType)
}else {
this.ossListener?.onFailure(999, "fail")
}
}
/***
* 是否所有的塊都成功了
*/
fun checkIsUploadSuccess(totalChunks: Int, successCount: Int): Boolean {
if (successCount >= totalChunks) {
return true
? ? }
return false
}
okhttp相關(guān)的:
問(wèn)題:
1.okhttp取消請(qǐng)求有回調(diào)嗎?
2.multipart/form-data是瀏覽器提交表單上傳文件的一種方式黍檩。
.addFormDataPart("uploadfile", uploadfile, RequestBody.create(MediaType.parse("*/*"), file)) // 第一個(gè)參數(shù)傳到服務(wù)器的字段名叉袍,第二個(gè)你自己的文件名,第三個(gè)MediaType.parse("*/*")和我們之前說(shuō)的那個(gè)type其實(shí)是一樣的
3刽酱、為什么response.body().string() 只能調(diào)用一次
我們可能習(xí)慣在獲取到Response對(duì)象后喳逛,先response.body().string()打印一遍log,再進(jìn)行數(shù)據(jù)解析棵里,卻發(fā)現(xiàn)第二次直接拋異常润文,其實(shí)直接跟源碼進(jìn)去看就發(fā)現(xiàn),通過(guò)source拿到字節(jié)流以后殿怜,直接給closeQuietly悄悄關(guān)閉了典蝌,這樣第二次再去通過(guò)source讀取就直接流已關(guān)閉的異常了。
publicfinal Stringstring()throws IOException{BufferedSource source=source();try{Charset charset=Util.bomAwareCharset(source,charset());returnsource.readString(charset);}finally{//這里講resource給悄悄close了Util.closeQuietly(source);}}
解決方案:1.內(nèi)存緩存一份response.body().string()头谜;2.自定義攔截器處理log骏掀。