Android開發(fā)實(shí)戰(zhàn):5分鐘帶你搞懂OkHttp斷點(diǎn)上傳

經(jīng)常會(huì)有同學(xué)問:文件的斷點(diǎn)上傳如何實(shí)現(xiàn)劳殖?

斷點(diǎn)上傳/下載凸郑,這是在客戶端經(jīng)常遇到的場景,當(dāng)我們需要上傳或下載一個(gè)大文件時(shí),都會(huì)考慮使用斷點(diǎn)續(xù)傳的方式辨赐。

斷點(diǎn)上傳相較于斷點(diǎn)下載來說优俘,最大的區(qū)別就在于斷點(diǎn)位置的記錄,上傳記錄在服務(wù)端掀序,下載記錄在客戶端帆焕,因此,客戶端需要在上傳前不恭,通過接口去拿到文件的斷點(diǎn)位置叶雹,然后在上傳時(shí),將文件輸入流跳轉(zhuǎn)到斷點(diǎn)位置

一换吧、準(zhǔn)備工作

對(duì)于文件上傳折晦,其實(shí)就是打開文件的輸入流,不停的讀取數(shù)據(jù)到byte數(shù)組中沾瓦,隨后寫出到服務(wù)端满着;那客戶端要做的就是跳過已經(jīng)上傳的部分,也就是直接跳到斷點(diǎn)位置贯莺,這樣就可以從斷點(diǎn)位置去讀取數(shù)據(jù)风喇,也就達(dá)到了斷點(diǎn)上傳的目的。

偽代碼如下:

String filePath = "...";
long skipSize = 100;  //假設(shè)斷點(diǎn)位置是 100 byte
InputStream input = input = new FileInputStream(filePath);
input.skip(skipSize)  //跳轉(zhuǎn)到斷點(diǎn)位置

然而缕探,OkHttp并沒有直接提供設(shè)置斷點(diǎn)的方法魂莫,所以需要客戶端自定義RequestBody,取名為FileRequestBody爹耗,如下:

//為簡化閱讀耙考,已省略部分代碼
public class FileRequestBody extends RequestBody {

    private final File file;
    private final long skipSize;  //斷點(diǎn)位置
    private final MediaType mediaType;

    public FileRequestBody(File file, long skipSize, @Nullable MediaType mediaType) {
        this.file = file;
        this.skipSize = skipSize;
        this.mediaType = mediaType;
    }

    @Override
    public long contentLength() throws IOException {
        return file.length() - skipSize;
    }

    @Override
    public void writeTo(@NotNull BufferedSink sink) throws IOException {
        InputStream input = null;
        Source source = null;
        try {
            input = new FileInputStream(file);
            if (skipSize > 0) {
                input.skip(skipSize); //跳到斷點(diǎn)位置
            }
            source = Okio.source(input);
            sink.writeAll(source);
        } finally {
            OkHttpCompat.closeQuietly(source, input);
        }
    }
}

為方便閱讀,以上省略部分源碼潭兽,FileRequestBody類完整源碼

有了FileRequestBody類琳骡,我們只需要傳入一個(gè)斷點(diǎn)位置,剩下的工作就跟普通的文件上傳一樣讼溺。 接下來,直接進(jìn)入代碼實(shí)現(xiàn)最易。

二怒坯、代碼實(shí)現(xiàn)

2.1 獲取斷點(diǎn)位置

首先,需要服務(wù)端提供一個(gè)接口藻懒,通過userId去查找該用戶未上傳完成的任務(wù)列表剔猿,代碼如下:

RxHttp.get("/.../getToUploadTask")
    .add("userId", "88888888")
    .asList<ToUploadTask>()
    .subscribe({
        //成功回調(diào),這里通過 it 拿到 List<ToUploadTask>
    }, {
        //異虫揖#回調(diào)
    });

其中ToUploadTask類如下:

//待上傳任務(wù)
data class ToUploadTask(
    val md5: String,          //文件的md5归敬,用于驗(yàn)證文件的唯一性
    val filePath: String,     //文件在客戶端的絕對(duì)路徑
    val skipSize: Long = 0    //斷點(diǎn)位置
)

注:md5、filePath 這兩個(gè)參數(shù)需要客戶端在文件上傳時(shí)傳遞給服務(wù)端,用于對(duì)文件的校驗(yàn)汪茧,防止文件錯(cuò)亂

2.2 斷點(diǎn)上傳

有了待上傳任務(wù)椅亚,客戶端就可以執(zhí)行斷點(diǎn)上傳操作,OkHttp代碼如下:

fun uploadFile(uploadTask: ToUploadTask) {
    //1.校驗(yàn)文件是否存在
    val file = File(uploadTask.filePath)
    if (!file.exists() && !file.isFile) return
    //2.校驗(yàn)文件的 md5 值
    val fileMd5 = FileUtils.getFileMD5ToString(file)
    if (!fileMd5.equals(uploadTask.md5)) return
    //3.構(gòu)建請(qǐng)求體
    val fileRequestBody = FileRequestBody(file, uploadTask.skipSize, BuildUtil.getMediaType(file.name))
    val multipartBody = MultipartBody.Builder()
        .addFormDataPart("userId", "88888888")
        .addFormDataPart("md5", fileMd5)
        .addFormDataPart("filePath", file.absolutePath)
        .addFormDataPart("file", file.name, fileRequestBody) //添加文件body
        .build()
    //4.構(gòu)建請(qǐng)求
    val request = Request.Builder()
        .url("/.../uploadFile")
        .post(multipartBody)
        .build()
    //5.執(zhí)行請(qǐng)求
    val okClient = OkHttpClient.Builder().build()
    okClient.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            //異巢瘴郏回調(diào)
        }
        override fun onResponse(call: Call, response: Response) {
            //成功回調(diào)
        }
    })
}

FIleUtils源碼呀舔,BuildUtil源碼

當(dāng)然,考慮到很少人會(huì)直接使用OkHttp扩灯,所以這里也貼出RxHttp的實(shí)現(xiàn)代碼媚赖,很簡單,僅需構(gòu)建一個(gè)UpFile對(duì)象即可珠插,就可很方便的監(jiān)聽上傳進(jìn)度惧磺,代碼如下:

fun uploadFile(uploadTask: ToUploadTask) {                            
    //1.校驗(yàn)文件是否存在                                                      
    val file = File(uploadTask.filePath)                               
    if (!file.exists() && !file.isFile) return                         
    //2.校驗(yàn)文件的 md5 值                                                      
    val fileMd5 = FileUtils.getFileMD5ToString(file)                   
    if (!fileMd5.equals(uploadTask.md5)) return                        
    val upFile = UpFile("file", file, file.name, uploadTask.skipSize)  
    //3.直接上傳
    RxHttp.postForm("/.../uploadFile")    
        .add("userId", "88888888")                                     
        .add("md5", fileMd5)                                           
        .add("filePath", file.absolutePath)                            
        .addFile(upFile)                                               
        .upload(AndroidSchedulers.mainThread()) {                       
            //上傳進(jìn)度回調(diào)           
        }                                                              
        .asString()                                                    
        .subscribe({                                                    
            //成功回調(diào)                                                            
        }, {                                                            
            //異常回調(diào)                                                           
        })                                                             
}                                                                      

小結(jié)

斷點(diǎn)上傳相較普通的文件上傳捻撑,客戶端多了一個(gè)斷點(diǎn)的設(shè)置磨隘,大部分工作量在服務(wù)端,服務(wù)端不僅需要處理文件的拼接邏輯布讹,還需記錄未上傳完成的任務(wù)琳拭,并通過接口暴露給客戶端。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末描验,一起剝皮案震驚了整個(gè)濱河市白嘁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膘流,老刑警劉巖絮缅,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異呼股,居然都是意外死亡耕魄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門彭谁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吸奴,“玉大人,你說我怎么就攤上這事缠局≡虬拢” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵狭园,是天一觀的道長读处。 經(jīng)常有香客問我,道長唱矛,這世上最難降的妖魔是什么罚舱? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任井辜,我火速辦了婚禮,結(jié)果婚禮上管闷,老公的妹妹穿的比我還像新娘粥脚。我一直安慰自己,他們只是感情好渐北,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布阿逃。 她就那樣靜靜地躺著,像睡著了一般赃蛛。 火紅的嫁衣襯著肌膚如雪恃锉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天呕臂,我揣著相機(jī)與錄音破托,去河邊找鬼。 笑死歧蒋,一個(gè)胖子當(dāng)著我的面吹牛土砂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谜洽,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萝映,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了阐虚?” 一聲冷哼從身側(cè)響起序臂,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎实束,沒想到半個(gè)月后奥秆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咸灿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年构订,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片避矢。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悼瘾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出审胸,到底是詐尸還是另有隱情分尸,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布歹嘹,位于F島的核電站,受9級(jí)特大地震影響温技,放射性物質(zhì)發(fā)生泄漏你稚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一走哺、第九天 我趴在偏房一處隱蔽的房頂上張望怎抛。 院中可真熱鬧卑吭,春花似錦、人聲如沸马绝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽富稻。三九已至掷邦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椭赋,已是汗流浹背抚岗。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哪怔,地道東北人宣蔚。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像认境,于是被迫代替她去往敵國和親胚委。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容