Android網絡編程之——文件斷點下載(暫停/繼續/重新下載)

一:關於斷點下載所涉及到的知識點

1.對SQLite的增刪改查(主要用來保存當前任務的一些信息) 
2.HttpURLConnection的請求配置

HttpURLConnection connection = null;
//設置下載請求屬性
connection.setRequestProperty();

3.RandomAccessFile 對文件進行寫入

RandomAccessFile rwd = null;
//從文件的某一位置寫入
rwd.seek();

4.基本的I/O流操作,以及邏輯處理 

二:第一步我們先來創建一張表用來保存我們的下載信息

public class DbHelper extends SQLiteOpenHelper {

    public static String TABLE = "file";//表名

    public DbHelper(Context context) {
        super(context, "download.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //文件名,下載地址,下載文件的總長度,當前下載完成長度
        db.execSQL("create table file(fileName varchar,url varchar,length integer,finished integer)");
    }
}

三:第二步同時既然是對數據庫的操作,那我們在DbHelper.class中來寫好幾個公用方法

    /**
     * 插入一條下載信息
     */
    public void insertData(SQLiteDatabase db, FileInfo info) {
        ContentValues values = new ContentValues();
        values.put("fileName", info.getFileName());
        values.put("url", info.getUrl());
        values.put("length", info.getLength());
        values.put("finished", info.getFinished());
        db.insert(TABLE, null, values);
    }

    /**
     * 是否已經插入這條數據
     */
    public boolean isExist(SQLiteDatabase db, FileInfo info) {
        Cursor cursor = db.query(TABLE, null, "url = ?", new String[]{info.getUrl()}, null, null, null, null);
        boolean exist = cursor.moveToNext();
        cursor.close();
        return exist;
    }

    /**
     * 查詢已經存在的一條信息
     */
    public FileInfo queryData(SQLiteDatabase db, String url) {
        Cursor cursor = db.query(TABLE, null, "url = ?", new String[]{url}, null, null, null, null);
        FileInfo info = new FileInfo();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex("fileName"));
                int length = cursor.getInt(cursor.getColumnIndex("length"));
                int finished = cursor.getInt(cursor.getColumnIndex("finished"));
                info.setStop(false);
                info.setFileName(fileName);
                info.setUrl(url);
                info.setLength(length);
                info.setFinished(finished);
            }
            cursor.close();
        }
        return info;
    }


    /**
     * 恢復一條下載信息
     */
    public void resetData(SQLiteDatabase db, String url) {
        ContentValues values = new ContentValues();
        values.put("finished", 0);
        values.put("length", 0);
        db.update(TABLE, values, "url = ?", new String[]{url});
    }

3.從上面方法中可以看出來還有一個FileInfo對象,沒錯這是自己創建的一個下載任務實體類一起來看看吧

//保存下載任務信息
public class FileInfo {
    private String fileName;//文件名
    private String url;//下載地址
    private int length;//文件大小
    private int finished;//下載以已完成進度
    private boolean isStop = false;//是否暫停下載
    private boolean isDownLoading = false;//是否正在下載
    //......
    //剩下的都是對應的get and set 方法就不貼出來了,自動生成就好了

四:第三步我們創建一個類DownLoaderManger來管理我們的下載任務包括、添加下載任務、開始下載、暫停下載、重新下載

public class DownLoaderManger {

    public static String FILE_PATH = Environment.getExternalStorageDirectory() + "/azhong";//文件下載保存路徑
    private DbHelper helper;//數據庫幫助類
    private SQLiteDatabase db;
    private OnProgressListener listener;//進度回調監聽
    private Map<String, FileInfo> map = new HashMap<>();//保存正在下載的任務信息
    private static DownLoaderManger manger;

    private DownLoaderManger(DbHelper helper, OnProgressListener listener) {
        this.helper = helper;
        this.listener = listener;
        db = helper.getReadableDatabase();
    }

    /**
     * 單例模式
     *
     * @param helper   數據庫幫助類
     * @param listener 下載進度回調接口
     * @return
     */
    public static synchronized DownLoaderManger getInstance(DbHelper helper, OnProgressListener listener) {
        if (manger == null) {
            synchronized (DownLoaderManger.class) {
                if (manger == null) {
                    manger = new DownLoaderManger(helper, listener);
                }
            }
        }
        return manger;
    }

    /**
     * 開始下載任務
     */
    public void start(String url) {
        db = helper.getReadableDatabase();
        FileInfo info = helper.queryData(db, url);
        map.put(url, info);
        //開始任務下載
        new DownLoadTask(map.get(url), helper, listener).start();
    }

    /**
     * 停止下載任務
     */
    public void stop(String url) {
        map.get(url).setStop(true);
    }

    /**
     * 重新下載任務
     */
    public void restart(String url) {
        stop(url);
        try {
            File file = new File(FILE_PATH, map.get(url).getFileName());
            if (file.exists()) {
                file.delete();
            }
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        db = helper.getWritableDatabase();
        helper.resetData(db, url);
        start(url);
    }

    /**
     * 獲取當前任務狀態
     */
    public boolean getCurrentState(String url) {
        return map.get(url).isDownLoading();
    }

    /**
     * 添加下載任務
     *
     * @param info 下載文件信息
     */
    public void addTask(FileInfo info) {
        //判斷數據庫是否已經存在這條下載信息
        if (!helper.isExist(db, info)) {
            db = helper.getWritableDatabase();
            helper.insertData(db, info);
            map.put(info.getUrl(), info);
        } else {
            //從數據庫獲取最新的下載信息
            db = helper.getReadableDatabase();
            FileInfo o = helper.queryData(db, info.getUrl());
            if (!map.containsKey(info.getUrl())) {
                map.put(info.getUrl(), o);
            }
        }
    }
}

五:上面代碼中OnProgressListener接口,當然還有一個最最重要的DownLoadTask了這裏面就是實現瞭如何斷點下載的,下面來一起看下里面的實現邏輯吧。。。

//下載進度接口
public interface OnProgressListener {

    void updateProgress(int max, int progress);
}

六:重點–下載線程

/**
 * 下載文件線程
 * 從服務器獲取需要下載的文件大小
 */
public class DownLoadTask extends Thread {
    private FileInfo info;
    private SQLiteDatabase db;
    private DbHelper helper;//數據庫幫助類
    private int finished = 0;//當前已下載完成的進度
    private OnProgressListener listener;//進度回調監聽

    public DownLoadTask(FileInfo info, DbHelper helper, OnProgressListener listener) {
        this.info = info;
        this.helper = helper;
        this.db = helper.getReadableDatabase();
        this.listener = listener;
        info.setDownLoading(true);
    }

    @Override
    public void run() {
        getLength();
        HttpURLConnection connection = null;
        RandomAccessFile rwd = null;
        try {
            URL url = new URL(info.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(3000);
            //從上次下載完成的地方下載
            int start = info.getFinished();
            //設置下載位置(從服務器上取要下載文件的某一段)
            connection.setRequestProperty("Range", "bytes=" + start + "-" + info.getLength());//設置下載範圍
            //設置文件寫入位置
            File file = new File(DownLoaderManger.FILE_PATH, info.getFileName());
            rwd = new RandomAccessFile(file, "rwd");
            //從文件的某一位置開始寫入
            rwd.seek(start);
            finished += info.getFinished();
            if (connection.getResponseCode() == 206) {//文件部分下載,返回碼爲206
                InputStream is = connection.getInputStream();
                byte[] buffer = new byte[1024 * 4];
                int len;
                while ((len = is.read(buffer)) != -1) {
                    //寫入文件
                    rwd.write(buffer, 0, len);
                    finished += len;
                    info.setFinished(finished);
                    //更新界面顯示
                    Message msg = new Message();
                    msg.what = 0x123;
                    msg.arg1 = info.getLength();
                    msg.arg2 = info.getFinished();
                    handler.sendMessage(msg);
                    //停止下載
                    if (info.isStop()) {
                        info.setDownLoading(false);
                        //保存此次下載的進度
                        helper.updateData(db, info);
                        db.close();
                        return;
                    }
                }
                //下載完成
                info.setDownLoading(false);
                helper.updateData(db, info);
                db.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            try {
                if (rwd != null) {
                    rwd.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 首先開啓一個線程去獲取要下載文件的大小(長度)
     */
    private void getLength() {
        HttpURLConnection connection = null;
        try {
            //連接網絡
            URL url = new URL(info.getUrl());
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(3000);
            int length = -1;
            if (connection.getResponseCode() == 200) {//網絡連接成功
                //獲得文件長度
                length = connection.getContentLength();
            }
            if (length <= 0) {
                //連接失敗
                return;
            }
            //創建文件保存路徑
            File dir = new File(DownLoaderManger.FILE_PATH);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            info.setLength(length);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
            try {
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 更新進度
     */
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0x123:
                    if (listener != null) {
                        listener.updateProgress(msg.arg1, msg.arg2);
                    }
                    break;
            }
        }
    };
}

七:下載流程—>首先獲取要下載文件的總長度—>然後指定從上次結束的位置開始下載文件。客官閱讀需仔細哦,精華都在註釋裏面哦!個人認爲重點部分如下兩個:

//設置下載位置(從服務器上取要下載文件的某一段)
connection.setRequestProperty("Range", "bytes=" + start + "-" + info.getLength());//設置下載範圍

RandomAccessFile rwd = new RandomAccessFile(file, "rwd");
//從文件的某一位置開始寫入
rwd.seek(start);

八:上面做了一系列準備工作之後,就可以正式開始下載了讓我們一起來瞧瞧

1.給主佈局界面放兩個按鈕,來開始/暫停/重新下載

<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.azhong.downloader.MainActivity">

    <com.azhong.downloader.view.NumberProgressBar
        android:id="@+id/pb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="開始下載" />

    <Button
        android:id="@+id/restart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="重新下載" />
</LinearLayout>

2.關於NumberProgressBar的使用可以移駕至這裏 
3.記得在清單文件中加入 網絡訪問和內存讀寫權限哦! 
4.既然我們封裝了那麼久,那肯定用起來就會很簡單了

public class MainActivity extends AppCompatActivity implements OnProgressListener {

    private NumberProgressBar pb;//進度條
    private DownLoaderManger downLoader = null;
    private FileInfo info;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pb = (NumberProgressBar) findViewById(R.id.pb);
        final Button start = (Button) findViewById(R.id.start);//開始下載
        final Button restart = (Button) findViewById(R.id.restart);//重新下載
        final DbHelper helper = new DbHelper(this);
        downLoader = DownLoaderManger.getInstance(helper, this);
        info = new FileInfo("Kuaiya482.apk", "http://downloadz.dewmobile.net/Official/Kuaiya482.apk");
        downLoader.addTask(info);
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (downLoader.getCurrentState(info.getUrl())) {
                    downLoader.stop(info.getUrl());
                    start.setText("開始下載");
                } else {
                    downLoader.start(info.getUrl());
                    start.setText("暫停下載");
                }
            }
        });
        restart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downLoader.restart(info.getUrl());
                start.setText("暫停下載");
            }
        });
    }

    @Override
    public void updateProgress(final int max, final int progress) {
        pb.setMax(max);
        pb.setProgress(progress);
    }
}

九:斷點下載的知識點在文章開頭已經給出,剩下的都是加入了自己的邏輯處理和代碼封裝。感興趣的夥伴們一定要自己動手嘗試着去寫,不然你以爲你看了一遍會了其實到後來就完全不會了……Demo下載傳送門

發佈了7 篇原創文章 · 獲贊 19 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章