1. 程式人生 > >OKHttp實現大檔案的斷點續傳

OKHttp實現大檔案的斷點續傳

本文的亮點:

(1)網路請求用OKHttp進行下載大檔案

(2)實現了大檔案的斷點續傳

(3)取消下載時,刪除已經下載的檔案。

實現效果圖:

             

直接給出工程:

(1)定義一個介面:DownloadListener.java

package com.example.servicebestpractice;

/**
 * Created by Administrator on 2017/2/23.
 */
public interface DownloadListener {


    /**
     * 通知當前的下載進度
     * @param progress
     */
    void onProgress(int progress);

    /**
     * 通知下載成功
     */
    void onSuccess();

    /**
     * 通知下載失敗
     */
    void onFailed();

    /**
     * 通知下載暫停
     */
    void onPaused();

    /**
     * 通知下載取消事件
     */
    void onCanceled();

}
(2)開啟一個非同步下載任務:DownloadTask.java
package com.example.servicebestpractice;

import android.os.AsyncTask;
import android.os.Environment;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by Administrator on 2017/2/23.
 */

/**
 * String 在執行AsyncTask時需要傳入的引數,可用於在後臺任務中使用。
 * Integer 後臺任務執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位。
 * Integer 當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值型別。
 */
public class DownloadTask extends AsyncTask<String,Integer,Integer> {

    public static final int TYPE_SUCCESS=0;

    public static final int TYPE_FAILED=1;

    public static final int TYPE_PAUSED=2;

    public static final int TYPE_CANCELED=3;

    private DownloadListener listener;

    private boolean isCanceled=false;

    private boolean isPaused=false;

    private int lastProgress;

    public DownloadTask(DownloadListener listener) {
        this.listener = listener;
    }

    /**
     * 這個方法中的所有程式碼都會在子執行緒中執行,我們應該在這裡處理所有的耗時任務。
     * @param params
     * @return
     */
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is=null;
        RandomAccessFile savedFile=null;
        File file=null;
        long downloadLength=0;   //記錄已經下載的檔案長度
        //檔案下載地址
        String downloadUrl=params[0];
        //下載檔案的名稱
        String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        //下載檔案存放的目錄
        String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        //建立一個檔案
        file=new File(directory+fileName);
        if(file.exists()){
            //如果檔案存在的話,得到檔案的大小
            downloadLength=file.length();
        }
        //得到下載內容的大小
        long contentLength=getContentLength(downloadUrl);
        if(contentLength==0){
            return TYPE_FAILED;
        }else if(contentLength==downloadLength){
            //已下載位元組和檔案總位元組相等,說明已經下載完成了
            return TYPE_SUCCESS;
        }
        OkHttpClient client=new OkHttpClient();
        /**
         * HTTP請求是有一個Header的,裡面有個Range屬性是定義下載區域的,它接收的值是一個區間範圍,
         * 比如:Range:bytes=0-10000。這樣我們就可以按照一定的規則,將一個大檔案拆分為若干很小的部分,
         * 然後分批次的下載,每個小塊下載完成之後,再合併到檔案中;這樣即使下載中斷了,重新下載時,
         * 也可以通過檔案的位元組長度來判斷下載的起始點,然後重啟斷點續傳的過程,直到最後完成下載過程。
         */
        Request request=new Request.Builder()
                .addHeader("RANGE","bytes="+downloadLength+"-")  //斷點續傳要用到的,指示下載的區間
                .url(downloadUrl)
                .build();
        try {
            Response response=client.newCall(request).execute();
            if(response!=null){
                is=response.body().byteStream();
                savedFile=new RandomAccessFile(file,"rw");
                savedFile.seek(downloadLength);//跳過已經下載的位元組
                byte[] b=new byte[1024];
                int total=0;
                int len;
                while((len=is.read(b))!=-1){
                    if(isCanceled){
                        return TYPE_CANCELED;
                    }else if(isPaused){
                        return TYPE_PAUSED;
                    }else {
                        total+=len;
                        savedFile.write(b,0,len);
                        //計算已經下載的百分比
                        int progress=(int)((total+downloadLength)*100/contentLength);
                        //注意:在doInBackground()中是不可以進行UI操作的,如果需要更新UI,比如說反饋當前任務的執行進度,
                        //可以呼叫publishProgress()方法完成。
                        publishProgress(progress);
                    }

                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                if(is!=null){
                    is.close();
                }
                if(savedFile!=null){
                    savedFile.close();
                }
                if(isCanceled&&file!=null){
                    file.delete();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    /**
     * 當在後臺任務中呼叫了publishProgress(Progress...)方法之後,onProgressUpdate()方法
     * 就會很快被呼叫,該方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值就可以
     * 對介面進行相應的更新。
     * @param values
     */
    protected void onProgressUpdate(Integer...values){
        int progress=values[0];
        if(progress>lastProgress){
            listener.onProgress(progress);
            lastProgress=progress;
        }
    }

    /**
     * 當後臺任務執行完畢並通過Return語句進行返回時,這個方法就很快被呼叫。返回的資料會作為引數
     * 傳遞到此方法中,可以利用返回的資料來進行一些UI操作。
     * @param status
     */
    @Override
    protected void onPostExecute(Integer status) {
        switch (status){
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }

    public void  pauseDownload(){
        isPaused=true;
    }

    public void cancelDownload(){
        isCanceled=true;
    }

    /**
     * 得到下載內容的大小
     * @param downloadUrl
     * @return
     */
    private long getContentLength(String downloadUrl){
        OkHttpClient client=new OkHttpClient();
        Request request=new Request.Builder().url(downloadUrl).build();
        try {
            Response response=client.newCall(request).execute();
            if(response!=null&&response.isSuccessful()){
                long contentLength=response.body().contentLength();
                response.body().close();
                return contentLength;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  0;
    }

}

(3)開啟一個服務:DownloadService.java
package com.example.servicebestpractice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

import java.io.File;

/**
 * 為了保證DownloadTask可以一直在後臺執行,我們還需要建立一個下載的服務。
 */
public class DownloadService extends Service {

    private DownloadTask downloadTask;

    private String downloadUrl;

    private DownloadListener listener=new DownloadListener() {

        /**
         * 構建了一個用於顯示下載進度的通知
         * @param progress
         */
        @Override
        public void onProgress(int progress) {
            //NotificationManager的notify()可以讓通知顯示出來。
            //notify(),接收兩個引數,第一個引數是id:每個通知所指定的id都是不同的。第二個引數是Notification物件。
            getNotificationManager().notify(1,getNotification("Downloading...",progress));
        }

        /**
         * 建立了一個新的通知用於告訴使用者下載成功啦
         */
        @Override
        public void onSuccess() {
            downloadTask=null;
            //下載成功時將前臺服務通知關閉,並建立一個下載成功的通知
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("Download Success",-1));
            Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();
        }

        /**
         *使用者下載失敗
         */
        @Override
        public void onFailed() {
            downloadTask=null;
            //下載失敗時,將前臺服務通知關閉,並建立一個下載失敗的通知
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("Download Failed",-1));
            Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();
        }

        /**
         * 使用者暫停
         */
        @Override
        public void onPaused() {
            downloadTask=null;
            Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_SHORT).show();
        }

        /**
         * 使用者取消
         */
        @Override
        public void onCanceled() {
            downloadTask=null;
            //取消下載,將前臺服務通知關閉,並建立一個下載失敗的通知
            stopForeground(true);
            Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();
        }
    };

    private DownloadBinder mBinder=new DownloadBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 為了要讓DownloadService可以和活動進行通訊,我們建立了一個DownloadBinder物件
     */
    class DownloadBinder extends Binder{

        /**
         * 開始下載
         * @param url
         */
        public void  startDownload(String url){
           if(downloadTask==null){
               downloadUrl=url;
               downloadTask=new DownloadTask(listener);
               //啟動下載任務
               downloadTask.execute(downloadUrl);
               startForeground(1,getNotification("Downloading...",0));
               Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
           }
        }

        /**
         * 暫停下載
         */
        public void pauseDownload(){
            if(downloadTask!=null){
                downloadTask.pauseDownload();
            }
        }

        /**
         * 取消下載
         */
        public void cancelDownload(){
            if(downloadTask!=null){
                downloadTask.cancelDownload();
            }else {
                if(downloadUrl!=null){
                    //取消下載時需要將檔案刪除,並將通知關閉
                    String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                    File file=new File(directory+fileName);
                    if(file.exists()){
                        file.delete();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
                }
            }

        }



    }

    /**
     * 獲取NotificationManager的例項,對通知進行管理
     * @return
     */
    private NotificationManager getNotificationManager(){
        return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    }

    /**
     *
     * @param title
     * @param progress
     * @return
     */
    private Notification getNotification(String title,int progress){
        Intent intent=new Intent(this,MainActivity.class);
        //PendingIntent是等待的Intent,這是跳轉到一個Activity元件。當用戶點選通知時,會跳轉到MainActivity
        PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);
        /**
         * 幾乎Android系統的每一個版本都會對通知這部分功能進行獲多或少的修改,API不穩定行問題在通知上面凸顯的尤其嚴重。
         * 解決方案是:用support庫中提供的相容API。support-v4庫中提供了一個NotificationCompat類,使用它可以保證我們的
         * 程式在所有的Android系統版本中都能正常工作。
         */
        NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
        //設定通知的小圖示
        builder.setSmallIcon(R.mipmap.ic_launcher);
        //設定通知的大圖示,當下拉系統狀態列時,就可以看到設定的大圖示
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
        //當通知被點選的時候,跳轉到MainActivity中
        builder.setContentIntent(pi);
        //設定通知的標題
        builder.setContentTitle(title);
        if(progress>0){
            //當progress大於或等於0時,才需要顯示下載進度
            builder.setContentText(progress+"%");
            builder.setProgress(100,progress,false);
        }
        return builder.build();
    }
}

(4)MainActivity.java
package com.example.servicebestpractice;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection connection=new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder=(DownloadService.DownloadBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startDownload=(Button) findViewById(R.id.start_download);
        startDownload.setOnClickListener(this);
        Button pauseDownload=(Button) findViewById(R.id.pause_download);
        pauseDownload.setOnClickListener(this);
        Button cancelDownload=(Button)findViewById(R.id.cancel_download);
        cancelDownload.setOnClickListener(this);
        Intent intent=new Intent(this,DownloadService.class);
        //這一點至關重要,因為啟動服務可以保證DownloadService一直在後臺執行,繫結服務則可以讓MaiinActivity和DownloadService
        //進行通訊,因此兩個方法的呼叫都必不可少。
        startService(intent);  //啟動服務
        bindService(intent,connection,BIND_AUTO_CREATE);//繫結服務
        /**
         *執行時許可權處理:我們需要再用到許可權的地方,每次都要檢查是否APP已經擁有許可權
         * 下載功能,需要些SD卡的許可權,我們在寫入之前檢查是否有WRITE_EXTERNAL_STORAGE許可權,沒有則申請許可權
         */
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }
    }

    public void onClick(View v){
        if(downloadBinder==null){
            return;
        }
        switch (v.getId()){
            case R.id.start_download:
                //String url="http://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                String url="http://10.0.2.2:8080/ChromeSetup.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    /**
     * 使用者選擇允許或拒絕後,會回撥onRequestPermissionsResult
     * @param requestCode  請求碼
     * @param permissions
     * @param grantResults  授權結果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {
       switch (requestCode){
           case 1:
               if(grantResults.length>0&&grantResults[0]!= PackageManager.PERMISSION_GRANTED){
                   Toast.makeText(this,"拒絕許可權將無法使用程式",Toast.LENGTH_SHORT).show();
                   finish();
               }
           break;
       }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除繫結服務
        unbindService(connection);
    }
}
(5)簡單的佈局檔案:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.servicebestpractice.MainActivity">

    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Download"/>

    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause Download"/>
    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel Download"/>

</LinearLayout>
(6)AndroidMainfest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicebestpractice">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

</manifest>
(7)build.gradle
 compile 'com.squareup.okhttp3:okhttp:3.4.1'
(8)最後給出專案下載地址:

https://github.com/Microstrong0305/OKHttp_DownloadFile

相關推薦

php實現檔案斷點下載例項

require_once('download.class.php'); date_default_timezone_set('Asia/Shanghai'); error_reporting(E_STRICT); function errorHandler($errno, $errs

java http檔案斷點

1,專案調研 因為需要研究下斷點上傳的問題。找了很久終於找到一個比較好的專案。 在GoogleCode上面,程式碼弄下來超級不方便,還是配置hosts才好,把程式碼重新上傳到了github上面。 效果: 上傳中,顯示進度,時間,百分比。 點選【Pause

一分鐘實現檔案斷點——斷點框架

本人先前的部落格有對多檔案分段斷點續傳的功能進行詳細的介紹,如果你有興趣可以先閱讀Android多檔案斷點續傳(一)——資料封裝以及介面實現。本人在先前的基礎上對程式碼進行了封裝,本帖主要介紹如何整合封裝好的框架快速實現多檔案分段斷點續傳功能。 先看效果圖

webUploader檔案斷點學習心得

一、準備材料:Uploader.swf、webuploader.css、webuploader.js,其中Uploader.swf只在初始化webUploader時用到,其餘兩個檔案在頁面引用即可。下載地址:https://github.com/fex-team/webup

檔案斷點

win10 node: v8.2.1 npm: v5.3.0 multer: v1.3.0 使用 1.由於對multer v1.3.0做了修改,所以不可以通過npm install multer這種形式,需要使用到修改過multer包去覆蓋原來的。 2

stream外掛跨域檔案斷點實戰+自定義限速

JAVA跨域大檔案斷點續傳+自定義限速 前言:本文所講到的內容完全獨創,遇坑很多,但是本著資源共享的原則,將兩個星期的成果分享給大家,請尊重別人的勞動成果,轉載請註明出處:http://blog.c

OKHttp實現檔案斷點

本文的亮點: (1)網路請求用OKHttp進行下載大檔案 (2)實現了大檔案的斷點續傳 (3)取消下載時,刪除已經下載的檔案。 實現效果圖:               直接給出工程: (1)定義一個介面:DownloadListener.java package com

網頁內實現檔案分片上斷點

最近做公司的專案,需要在後臺控制系統中新增一個功能-------向伺服器傳送程式更新包;這些程式更新包大小不固定,但基本都在1G到4G之間,剛開始還真是難倒我了,因為之前的專案中沒有上傳過這麼大的檔案,還要斷點續傳,後來經過查資料,寫DEMO,這個問題終於解決了; 解決辦法: 使用XMLHt

【轉】Android-使用Socket進行檔案斷點

在Android中上傳檔案可以採用HTTP方式,也可以採用Socket方式,但是HTTP方式不能上傳大檔案,這裡介紹一種通過Socket方式來進行斷點續傳的方式,服務端會記錄下檔案的上傳進度,當某一次上傳過程意外終止後,下一次可以繼續上傳,這裡用到的其實還是J2SE裡的知識。   這個上傳程式的

Android 實現多執行緒下載檔案+斷點

                            Android 多執行緒下載檔案+斷點續傳       在專案快要結束的時候,發現了app沒有版本更新的功能,於是找到一些過去的資料,在app上應用完成了版本更新,現在記錄一下apk的下載,也就是如何通過多執行緒將ap

java檔案斷點的簡單實現

import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.aw

springmvc mybatis fileupload實現檔案斷點

為什麼要斷點續傳:在傳輸較大檔案沒傳輸完成時若出現斷網或者伺服器異常等情況則檔案會上傳失敗,使用者需要重新開始上傳檔案,這樣會使使用者體驗十分不好,所以需要有斷點續傳。斷點續傳好的方法是將檔案分為N個片段進行上傳,這樣即使後面的片段還未上傳完畢之前已上傳的片段也會得以保留。

HttpURLConnection實現檔案斷點

首先 client端: [java] view plain copy print? HttpURLConnection conn = null;          BufferedInputStream fin = null;          Buffere

IOS 下載檔案斷點原理與實現(附原始碼)

在網路狀況不好的情況下,對於檔案的傳輸,我們希望能夠支援可以每次傳部分資料。首先從檔案傳輸協議FTP和TFTP開始分析, FTP是基於TCP的,一般情況下建立兩個連線,一個負責指令,一個負責資料;而TFTP是基於UDP的,由於UDP傳輸是不可靠的,雖然傳輸速度很快,但對於普通的檔案像PDF這種,少了一個

使用Socket進行檔案斷點

在Android中上傳檔案可以採用HTTP方式,也可以採用Socket方式,但是HTTP方式不能上傳大檔案,這裡介紹一種通過Socket方式來進行斷點續傳的方式,服務端會記錄下檔案的上傳進度,當某一次上傳過程意外終止後,下一次可以繼續上傳,這裡用到的其實還是J2SE裡的知

【飛秋教程】檔案斷點

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

(QT) C++ 版本IM通訊軟體(客戶端+伺服器文字聊天、檔案斷點、線上使用者搜尋)

緊接著上一節課程,這次的作業是要求實現一個簡易版的“QQ”,可支援“軟體需求”所列出的功能。當時由於圖方便便選擇了QTCPSocket進行整個過程的通訊(事後才知道有多坑)。服務端介面比較簡單,就幾個按鈕一個進度條,主要在客戶端實現了基本的功能和介面。整個學習和

檔案斷點 js+php

/** js*/function PostFile(file, i, t) {      console.log(1);    var name = file.name,   //檔名   size = file.size,   type = file.type,

阿里雲OSS單檔案斷點+前端 簡單展示(springmvc架構)

業務沒有需要多檔案一起上傳,所以這裡只是單檔案,多檔案的話也是在獲得File的地方變成List即可,多個迴圈,多一些執行緒,網上有程式碼 一、pom.xml <dependencys> <dependenc

使用原生Java Web來實現檔案的上

BigFileUpload 目錄 背景介紹 專案介紹 使用說明 獲取程式碼 需要知識點 啟動專案 專案示範 核心講解 功能分析 分塊上傳 秒傳功能 斷點續傳 總結 背景介紹 這個專案是在朋友的一次面試中,面試人提出了一個問題.