close

我們經常會有一些需要顯示上傳進度的時候吧?

比如說上傳大檔案、影片、相片之類的

若是 Loading icon 轉太久也會讓 user 不耐煩的

所以,今天要講用 okhttp 時,怎麼取得進度 percentage

 

基上上,要做到這件事情

我們需要自訂一個 RequestBody 才可以

先來看一般的上傳方式

先訂義好 API Service

@Multipart
@POST("/media/upload")
suspend fun mediaUpload(
    @HeaderMap header: HashMap<String, String>,
    @Part media: MultipartBody.Part
): UploadMediaRs

 

然後取得一般 requestBody 方式如下

file.asRequestBody(MediaType())

這樣就可以用這個 requestBody 去打 API 了

 

但我們想要有進度的話

就需要自訂一個,如下

ProgressRequestBody(
    file,
    MediaType(),
    progressListener
)

 

可以看到我們多傳了一個 Listener 進去

這個就是為了回傳進度值的

接著看內部實作 ProgressRequestBody

class ProgressRequestBody(
    private val file: File,
    private val contentType: MediaType? = null,
    private val progressListener: (percentage: Float) -> Unit
) : RequestBody() {
    override fun contentType() = contentType

    override fun contentLength() = file.length()

    override fun writeTo(sink: BufferedSink) {

        if (sink is Buffer) return

        val source: Source = file.source()
        val buf = Buffer()
        var bytesWritten = 0L
        try {
            do {
                // 一次塞 1KB Buffer
                val readCount = source.read(buf, 1024L * 8)

                // 記錄進度
                bytesWritten += readCount

                // 回傳 progress 小數點後兩位
                progressListener(
                  BigDecimal(
                    bytesWritten.toDouble() / contentLength()*100
                  ).setScale(2, RoundingMode.DOWN)
                   .toFloat()
                )

                // 讀完離開
                if (readCount < 0) break

                // 將 Buffer 寫入
                sink.write(buf, readCount)
            } while (true)
        } catch (e: Exception) {
            PLog.e(e, "ProgressRequestBody.writeTo:")
        } finally {
            source.close()
            buf.clear()
        }
    }
}

 

很簡短的一個 Class

做的事就是,打開 File 然後一段段的寫入 Sink

在寫的過程中,回傳寫入的 percentage

大功告成

 

比較需要注意的是

writeTo 會被 call 兩次

這是因為我們用了 HttpLoggingInterceptor Body Level

它會 call writeTo 然後才是真正網路 call 的 writeTo

所以開頭我們才用一個 if 去擋掉一次 writeTo 喔

 

 

 

參考資料:

https://stackoverflow.com/questions/40691610/retrofit-2-requestbody-writeto-method-called-twice/42070678


arrow
arrow

    顏澤偉 發表在 痞客邦 留言(0) 人氣()