天天看點

Android斷點下載下傳,多線程下載下傳

斷點多線程下載下傳的幾個關鍵點:①:得到要下載下傳的檔案大小後,均分給幾個線程。②:使用RandomAccessFile類進行讀寫,可以指定開始寫入的位置。③:資料庫儲存下載下傳資訊,下一次繼續下載下傳的時候從資料庫取出資料,然後從上次下載下傳結束的地方開始。

這裡我使用了FinalDb的資料庫架構,同時在記憶體中存儲了一份所有線程的下載下傳資訊,負責時時更新和查詢下載下傳進度。我測試用的是百度雲網盤,檔案大小50M左右。注意,線程中指定了開始結束下載下傳位置的網絡請求成功的傳回碼是206,并不是200。

效果圖:

Android斷點下載下傳,多線程下載下傳
Android斷點下載下傳,多線程下載下傳

線程類:線程類負責具體的下載下傳,線程的下載下傳資訊被存儲到了資料庫。線程開始下載下傳時,根據線程ID查詢自己的存儲資訊,然後開始從指定的位置下載下傳和寫入檔案。完畢後根據自己的目前下載下傳結果設定自己目前的下載下傳狀态。時時的下載下傳進度存儲隻存儲到了記憶體,隻在本次下載下傳結束才存儲到資料庫。

public class DownloadThread extends Thread {

    /**
     * 資料庫操作工具
     */
    private FinalDb finalDb;

    /**
     * 下載下傳狀态:未開始
     */
    public static final int STATE_READY = 1;

    /**
     * 下載下傳狀态:下載下傳中
     */
    public static final int STATE_LOADING = 2;

    /**
     * 下載下傳狀态:下載下傳暫停中
     */
    public static final int STATE_PAUSING = 3;

    /**
     * 下載下傳狀态:下載下傳完成
     */
    public static final int STATE_FINISH = 4;

    /**
     * 下載下傳狀态
     */
    public int downloadState;

    /**
     * 線程ID
     */
    private int threadID;

    /**
     * 要下載下傳的URL路徑
     */
    private String url;

    /**
     * 本線程要下載下傳的檔案
     */
    public RandomAccessFile file;

    /**
     * 構造器
     */
    public DownloadThread(Context context, int threadID, String downloadUrl, RandomAccessFile randomAccessFile) {
        this.threadID = threadID;
        this.url = downloadUrl;
        this.file = randomAccessFile;
        finalDb = DBUtil.getFinalDb(context);
    }

    @Override
    public void run() {
        //資料庫查詢本線程下載下傳進度
        List<ThreadDownloadInfoBean> list = finalDb.findAllByWhere(ThreadDownloadInfoBean.class, "threadID='" + threadID + "'");
        //下載下傳資訊存放到記憶體
        if (list.get(0) != null) {
            MapUtil.map.put(threadID, list.get(0));
        }
        //取出實體類
        ThreadDownloadInfoBean bean = MapUtil.map.get(threadID);
        Utils.Print("bean:" + bean.toString());
        InputStream is;
        HttpURLConnection conn;
        try {
            Utils.Print("線程" + threadID + "開始連接配接");
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");
            //設定下載下傳開始和結束的位置
            conn.setRequestProperty("Range", "bytes=" + (bean.startDownloadPosition + bean.downloadedSize) + "-" + bean.endDownloadPosition);
            conn.connect();
            if (conn.getResponseCode() == 206) {
                //更改下載下傳狀态
                downloadState = STATE_LOADING;
                bean.downloadState = STATE_LOADING;
                Utils.Print("線程" + threadID + "連接配接成功");
                is = conn.getInputStream();
                // 1K的資料緩沖
                byte[] bs = new byte[1024];
                // 讀取到的資料長度
                int len;
                //從指定的位置開始下載下傳
                file.seek(bean.startDownloadPosition);
                // 循環讀取,當已經下載下傳的大小達到了指定的本線程負責的大小時跳出循環,線程之間負責的檔案首尾有重合的話沒有影響,因為寫入的内容時相同的
                while ((len = is.read(bs)) != -1) {
                    //不用在這個循環裡面更新資料庫
                    file.write(bs, 0, len);
                    //時時更新記憶體中的已下載下傳大小資訊
                    bean.downloadedSize += len;
                    //如果調用者暫停下載下傳,則跳出結束方法
                    if (downloadState == STATE_PAUSING) {
                        Utils.Print("線程" + threadID + "暫停下載下傳");
                        break;
                    }
                }
                is.close();
                file.close();
            } else {
                Utils.Print("線程" + threadID + "連接配接失敗");
            }
            conn.disconnect();
            //如果這個線程已經下載下傳完了自己負責的部分就修改下載下傳狀态
            if (bean.downloadedSize >= bean.downloadTotalSize) {
                bean.downloadState = STATE_FINISH;
            } else {
                bean.downloadState = STATE_PAUSING;
            }
            //記憶體中資訊更新至資料庫
            finalDb.update(bean, "threadID='" + bean.threadID + "'");
        } catch (IOException e) {
            Utils.Print("線程" + threadID + "IO異常");
            e.printStackTrace();
        }
    }
}
           

線程資訊的封裝類:負責存儲每個線程的ID,開始下載下傳的位置,結束下載下傳的位置,已經下載下傳的大小,下載下傳狀态等;這個類用FinalDb資料庫存儲到資料庫,一定要寫get,set方法和空參構造器

@Table(name = "ThreadInfo")
public class ThreadDownloadInfoBean {

    /**
     * id
     */
    public int id;

    /**
     * 線程ID
     */
    public int threadID;

    /**
     * 本線程時時下載下傳開始的位置
     */
    public long startDownloadPosition;

    /**
     * 本線程時時下載下傳結束的位置
     */
    public long endDownloadPosition;

    /**
     * 本線程負責下載下傳的檔案大小
     */
    public long downloadTotalSize;

    /**
     * 已經下載下傳了的檔案大小
     */
    public long downloadedSize;

    /**
     * 本線程的下載下傳狀态
     */
    public int downloadState;

    public ThreadDownloadInfoBean() {

    }

    public ThreadDownloadInfoBean(int downloadState, long downloadedSize, long downloadTotalSize, long endDownloadPosition, long startDownloadPosition, int threadID) {
        this.downloadState = downloadState;
        this.downloadedSize = downloadedSize;
        this.downloadTotalSize = downloadTotalSize;
        this.endDownloadPosition = endDownloadPosition;
        this.startDownloadPosition = startDownloadPosition;
        this.threadID = threadID;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getThreadID() {
        return threadID;
    }

    public void setThreadID(int threadID) {
        this.threadID = threadID;
    }

    public long getStartDownloadPosition() {
        return startDownloadPosition;
    }

    public void setStartDownloadPosition(long startDownloadPosition) {
        this.startDownloadPosition = startDownloadPosition;
    }

    public long getEndDownloadPosition() {
        return endDownloadPosition;
    }

    public void setEndDownloadPosition(long endDownloadPosition) {
        this.endDownloadPosition = endDownloadPosition;
    }

    public long getDownloadTotalSize() {
        return downloadTotalSize;
    }

    public void setDownloadTotalSize(long downloadTotalSize) {
        this.downloadTotalSize = downloadTotalSize;
    }

    public long getDownloadedSize() {
        return downloadedSize;
    }

    public void setDownloadedSize(long downloadedSize) {
        this.downloadedSize = downloadedSize;
    }

    public int getDownloadState() {
        return downloadState;
    }

    public void setDownloadState(int downloadState) {
        this.downloadState = downloadState;
    }

    @Override
    public String toString() {
        return "ThreadDownloadInfoBean{" +
                "id=" + id +
                ", threadID=" + threadID +
                ", startDownloadPosition=" + startDownloadPosition +
                ", endDownloadPosition=" + endDownloadPosition +
                ", downloadTotalSize=" + downloadTotalSize +
                ", downloadedSize=" + downloadedSize +
                ", downloadState=" + downloadState +
                '}';
    }
}
           

下載下傳工具類:這個類負責得到下載下傳檔案大小,配置設定線程下載下傳大小,管理下載下傳線程

public class DownUtil {

    /**
     * 資料庫操作工具
     */
    public FinalDb finalDb;

    /**
     * 下載下傳狀态:準備好
     */
    public static final int STATE_READY = 1;

    /**
     * 下載下傳狀态:下載下傳中
     */
    public static final int STATE_LOADING = 2;

    /**
     * 下載下傳狀态:暫停中
     */
    public static final int STATE_PAUSING = 3;

    /**
     * 下載下傳狀态:下載下傳完成
     */
    public static final int STATE_FINISH = 4;

    /**
     * 下載下傳狀态
     */
    public int downloadState;

    /**
     * context
     */
    private Context context;

    /**
     * 要下載下傳檔案的大小
     */
    public long fileSize;

    /**
     * 線程集合
     */
    private ArrayList<DownloadThread> threadList = new ArrayList<>();

    /**
     * 構造器
     */
    public DownUtil(Context context) {
        this.context = context;
        finalDb = DBUtil.getFinalDb(context);
        judgeDownState();
    }

    /**
     * 初始化時判斷下載下傳狀态
     */
    public void judgeDownState() {
        //取出資料庫中的下載下傳資訊,存儲到記憶體中
        List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
        if (list != null && list.size() == DownloadActivity.threadNum) {
            for (int i = 0; i < list.size(); i++) {
                MapUtil.map.put(i, list.get(i));
            }
        }
        //查詢SP中是否存儲過要下載下傳的檔案大小
        Long spFileSize = SPUtil.getInstance(context).getLong(DownloadActivity.fileName, 0L);
        long downloadedSize = getFinishedSize();
        //SP中或者資料庫中沒有查詢到說明從沒有進行過下載下傳
        if (spFileSize == 0 || downloadedSize == 0) {
            downloadState = STATE_READY;
        } else if (downloadedSize >= spFileSize) {
            downloadState = STATE_FINISH;
        } else {
            downloadState = STATE_PAUSING;
            fileSize = spFileSize;
        }
    }

    /**
     * 點選了開始按鈕
     */
    public void clickDownloadBtn() {
        if (downloadState == STATE_READY) {
            startDownload();
        } else if (downloadState == STATE_PAUSING) {
            continueDownload();
        }
    }

    /**
     * 進入應用第一次開始下載下傳
     */
    private void startDownload() {
        //開啟新線程,得到要下載下傳的檔案大小
        new Thread() {
            @Override
            public void run() {
                try {
                    HttpURLConnection conn;
                    conn = (HttpURLConnection) new URL(DownloadActivity.url).openConnection();
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    conn.setRequestMethod("GET");
                    conn.connect();
                    if (conn.getResponseCode() == 200) {
                        Utils.Print("DownUtil連接配接成功");
                        //得到要下載下傳的檔案大小
                        fileSize = conn.getContentLength();
                        //得到檔案名字尾名
                        String contentDisposition = new String(conn.getHeaderField("Content-Disposition").getBytes("ISO-8859-1"), "UTF-8");
                        String fileName = "下載下傳測試" + contentDisposition.substring(contentDisposition.lastIndexOf("."), contentDisposition.lastIndexOf("\""));
                        //得到存儲路徑
                        String sdCardPath = context.getExternalFilesDir(null).getPath();
                        DownloadActivity.fileName = sdCardPath + "/" + fileName;
                        SPUtil.getInstance(context).saveString(DownloadActivity.FILE_NAME, DownloadActivity.fileName);
                        SPUtil.getInstance(context).saveLong(DownloadActivity.fileName, fileSize);
                        /*
                         *  計算一下每個線程需要分擔的下載下傳檔案大小 比如 總下載下傳量為100 一共有三個線程
                         *  那麼 線程1負責0-32,線程2負責33-65,線程3負責66-99和100,
                         *  也就是說下載下傳總量除以線程數如果有餘數,那麼最後一個線程多下載下傳一個餘數部分
                         */
                        //每個線程均分的大小
                        long threadDownSize = fileSize / DownloadActivity.threadNum;
                        //線程均分完畢剩餘的大小
                        long leftDownSize = fileSize % DownloadActivity.threadNum;
                        //建立要寫入的檔案
                        RandomAccessFile file = new RandomAccessFile(DownloadActivity.fileName, "rw");
                        //設定檔案大小
                        file.setLength(fileSize);
                        //關閉
                        file.close();
                        for (int i = 0; i < DownloadActivity.threadNum; i++) {
                            Utils.Print("開啟線程" + i);
                            //指定每個線程開始下載下傳的位置
                            long startPosition = i * threadDownSize;
                            //指定每個線程負責下載下傳的大小,當現場是集合裡面最後一個線程的時候,它要增加leftDownSize的大小
                            threadDownSize = i == DownloadActivity.threadNum - 1 ? threadDownSize + leftDownSize : threadDownSize;
                            //存儲線程資訊
                            ThreadDownloadInfoBean bean = new ThreadDownloadInfoBean(DownloadThread.STATE_READY, 0, threadDownSize, startPosition + threadDownSize, startPosition, i);
                            finalDb.save(bean);
                            RandomAccessFile threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
                            threadList.add(new DownloadThread(context, i, DownloadActivity.url, threadFile));
                            threadList.get(i).start();
                        }
                        downloadState = STATE_LOADING;
                        downloadInfoListener.connectSuccess();
                    } else {
                        Utils.Print("DownUtil-連接配接失敗");
                        downloadInfoListener.connectFail();
                    }
                    conn.disconnect();
                } catch (IOException e) {
                    Utils.Print("DownUtil-IO異常");
                    downloadInfoListener.IOException();
                    e.printStackTrace();
                }
            }
        }.start();
    }

    /**
     * 繼續下載下傳
     */
    private void continueDownload() {
        List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
        for (int i = 0; i < DownloadActivity.threadNum; i++) {
            //目前線程已經下載下傳完了就不再開啟
            if (list.get(i).downloadState != DownloadThread.STATE_FINISH) {
                Utils.Print("重新開啟線程" + i);
                RandomAccessFile threadFile = null;
                try {
                    threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                DownloadThread downloadThread = new DownloadThread(context, i, DownloadActivity.url, threadFile);
                threadList.add(downloadThread);
                downloadThread.start();
            }
        }
        downloadState = STATE_LOADING;
        downloadInfoListener.connectSuccess();
    }

    /**
     * 點選了暫停的按鈕
     */
    public void clickPauseBtn() {
        if (downloadState == STATE_LOADING) {
            stopDownload();
        }
    }

    /**
     * 暫停下載下傳
     */
    private void stopDownload() {
        for (int i = 0; i < threadList.size(); i++) {
            if (threadList.get(i).downloadState == DownloadThread.STATE_LOADING) {
                threadList.get(i).downloadState = DownloadThread.STATE_PAUSING;
            }
        }
        downloadState = STATE_PAUSING;
        threadList.clear();
    }

    /**
     * 傳回此刻所有線程完成的下載下傳大小
     */
    public long getFinishedSize() {
        long totalSize = 0;
        for (int i = 0; i < DownloadActivity.threadNum; i++) {
            ThreadDownloadInfoBean bean = MapUtil.map.get(i);
            if (bean != null) {
                //如果該線程已經下載下傳的部分大于配置設定給它的部分多餘部分不予計算
                long addSize = bean.downloadedSize > bean.downloadTotalSize ? bean.downloadTotalSize : bean.downloadedSize;
                totalSize += addSize;
            }
        }
        return totalSize;
    }

    /**
     * 下載下傳資訊監聽器
     */
    private DownloadInfoListener downloadInfoListener;

    /**
     * 下載下傳資訊監聽器
     */
    public interface DownloadInfoListener {

        void connectSuccess();

        void connectFail();

        void IOException();
    }

    /**
     * 設定下載下傳資訊監聽器
     */
    public void setDownloadInfoListener(DownloadInfoListener downloadInfoListener) {
        this.downloadInfoListener = downloadInfoListener;
    }

}
           

頁面Activity:負責展示下載下傳進度等資訊,提供操作頁面

public class DownloadActivity extends BaseActivity {

    /**
     * 下載下傳位址輸入框
     */
    private EditText et_download_url;

    /**
     * 确定輸入位址的按鈕
     */
    private Button btn_download_geturl;

    /**
     * 進度條
     */
    private NumberProgressView np_download;

    /**
     * 開始下載下傳的按鈕
     */
    private Button btn_download_start;

    /**
     * 暫停下載下傳的按鈕
     */
    private Button btn_download_pause;

    /**
     * 取消下載下傳的按鈕
     */
    private Button btn_download_cancel;

    /**
     * 檔案資訊顯示
     */
    private TextView tv_download_file_info;

    /**
     * 下載下傳速度顯示
     */
    private TextView tv_download_speed;

    /**
     * 顯示下載下傳資訊
     */
    private TextView tv_download_speed_info;

    /**
     * 每隔一段時間重新整理下載下傳進度顯示
     */
    private final static int WHAT_INCREACE = 1;

    /**
     * 得到了檔案名稱
     */
    private final static int WHAT_GET_FILENAME = 2;

    /**
     * downUtil連接配接失敗
     */
    private final static int WHAI_CONNECT_FAIL = 3;

    /**
     * downUtilIO異常
     */
    private final static int WHAT_IO_EXCEPTION = 4;

    /**
     * 下載下傳工具
     */
    private DownUtil downUtil;

    /**
     * 需要開啟的線程數量
     */
    public static final int threadNum = 5;

    /**
     * 存放檔案路徑名稱的SP鍵名
     */
    public static final String FILE_NAME = "fileName";

    /**
     * 要下載下傳的檔案的url位址
     */
    public static String url = "";
    
    /**
     * 檔案下載下傳路徑和檔案名稱
     */
    public static String fileName;

    /**
     * 上次統計已經完成下載下傳的檔案大小
     */
    private long lastFinishedSize;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WHAT_INCREACE:
                    updateView();
                    break;
                case WHAT_GET_FILENAME:
                    tv_download_file_info.setText("下載下傳路徑:" + fileName);
                    break;
                case WHAI_CONNECT_FAIL:
                    tv_download_file_info.setText("連接配接失敗");
                    break;
                case WHAT_IO_EXCEPTION:
                    tv_download_file_info.setText("IO異常");
                    break;
            }
        }
    };

    /**
     * 更新視圖
     */
    private void updateView() {
        //目前已經完成下載下傳的檔案大小
        long currentFinishedSize = downUtil.getFinishedSize();
        //要顯示的下載下傳資訊的文字
        StringBuilder downloadInfo = new StringBuilder();
        tv_download_speed.setText("目前下載下傳速度:" + formateSize(currentFinishedSize - lastFinishedSize) + "/s");
        //本次統計比上次統計增加了,更新視圖,本次統計較上次統計沒有變化,不做視圖更新
        if (currentFinishedSize > lastFinishedSize) {
            lastFinishedSize = currentFinishedSize;
            downloadInfo.append("下載下傳進度:" + formateSize(currentFinishedSize) + "/" + formateSize(downUtil.fileSize) + "\n");
            for (int i = 0; i < threadNum; i++) {
                ThreadDownloadInfoBean bean = MapUtil.map.get(i);
                if (bean.downloadTotalSize != 0) {
                    downloadInfo.append("線程" + i + "下載下傳進度:" + bean.downloadedSize * 100 / bean.downloadTotalSize + "%    " + formateSize(bean.downloadedSize) + "/" + formateSize(bean.downloadTotalSize) + "\n");
                }
            }
            np_download.setProgress((int) (currentFinishedSize * 100 / downUtil.fileSize));
            tv_download_speed_info.setText(downloadInfo);
            //下載下傳完成後
            if (currentFinishedSize >= downUtil.fileSize) {
                tv_download_speed.setText("下載下傳完畢");
                handler.removeMessages(WHAT_INCREACE);
                return;
            }
        }
        handler.sendEmptyMessageDelayed(WHAT_INCREACE, 1000);
    }

    /**
     * 傳回子類要顯示的布局 R.layout......
     */
    @Override
    protected int childView() {
        return R.layout.activity_download;
    }

    /**
     * 強制子類實作該抽象方法,初始化各自的View
     */
    @Override
    protected void findChildView() {
        et_download_url = (EditText) findViewById(R.id.et_download_url);
        btn_download_geturl = (Button) findViewById(R.id.btn_download_geturl);
        np_download = (NumberProgressView) findViewById(R.id.np_download);
        btn_download_start = (Button) findViewById(R.id.btn_download_start);
        btn_download_pause = (Button) findViewById(R.id.btn_download_pause);
        btn_download_cancel = (Button) findViewById(R.id.btn_download_cancel);
        tv_download_file_info = (TextView) findViewById(R.id.tv_download_file_info);
        tv_download_speed = (TextView) findViewById(R.id.tv_download_speed);
        tv_download_speed_info = (TextView) findViewById(R.id.tv_download_speed_info);
    }

    /**
     * 強制子類實作該抽象方法,初始化各自資料
     */
    @Override
    protected void initChildData() {
        downUtil = new DownUtil(this);
        lastFinishedSize = downUtil.getFinishedSize();
        StringBuilder downloadInfo = new StringBuilder();
        fileName = SPUtil.getInstance(this).getString(FILE_NAME, null);
        if (fileName != null) {
            downloadInfo.append("下載下傳路徑:" + fileName);
            if (downUtil.downloadState == DownUtil.STATE_FINISH) {
                np_download.setProgress(100);
            } else if (downUtil.downloadState == DownUtil.STATE_PAUSING) {
                np_download.setProgress((int) (downUtil.getFinishedSize() * 100 / downUtil.fileSize));
            }
            tv_download_file_info.setText(downloadInfo);
        }
    }

    /**
     * 強制子類實作該抽象方法,設定自己的監聽器
     */
    @Override
    protected View[] setChildListener() {
        downUtil.setDownloadInfoListener(new DownUtil.DownloadInfoListener() {

            @Override
            public void connectSuccess() {
                //不能在此更新,需要到主線程重新整理UI
                handler.sendEmptyMessage(WHAT_GET_FILENAME);
                handler.sendEmptyMessage(WHAT_INCREACE);
            }

            @Override
            public void connectFail() {
                handler.sendEmptyMessage(WHAI_CONNECT_FAIL);
            }

            @Override
            public void IOException() {
                handler.sendEmptyMessage(WHAT_IO_EXCEPTION);
            }
        });
        return new View[]{btn_download_start, btn_download_pause, btn_download_geturl, btn_download_cancel};
    }

    /**
     * 子類在這個方法裡面實作自己View的點選監聽事件的相應
     *
     * @param v 父類傳遞到子類的點選的View
     */
    @Override
    protected void clickChildView(View v) {
        switch (v.getId()) {

            case R.id.btn_download_start:
                downUtil.clickDownloadBtn();
                break;

            case R.id.btn_download_pause:
                downUtil.clickPauseBtn();
                handler.removeMessages(WHAT_INCREACE);
                break;

            case R.id.btn_download_cancel:
                //停止發送消息
                handler.removeMessages(WHAT_INCREACE);
                //暫停下載下傳
                downUtil.clickPauseBtn();
                //重置狀态
                downUtil.downloadState = DownUtil.STATE_READY;
                //統計下載下傳的大小歸零
                lastFinishedSize = 0;
                //重置文本資訊
                tv_download_speed.setText("");
                tv_download_file_info.setText("");
                tv_download_speed_info.setText("");
                //進度條歸零
                np_download.setProgress(0);
                //清空記憶體資料
                MapUtil.map.clear();
                //sp清理
                SPUtil.getInstance(this).removeAll();
                //清空資料庫
                downUtil.finalDb.deleteAll(ThreadDownloadInfoBean.class);
                //删除檔案
                if (fileName != null) {
                    File file = new File(fileName);
                    if (file.exists()) {
                        boolean delete = file.delete();
                        if (delete) {
                            Utils.ToastS(this, "删除" + fileName + "成功");
                        } else {
                            Utils.ToastS(this, "删除失敗");
                        }
                    } else {
                        Utils.ToastS(this, "檔案不存在");
                    }
                } else {
                    Utils.ToastS(this, "檔案不存在");
                }
                break;

            case R.id.btn_download_geturl:
                String editTextUrl = et_download_url.getText().toString();
                if (editTextUrl.trim().equals("")) {
                    Utils.ToastS(this, "請輸入位址");
                } else {
                    url = editTextUrl;
                }
                break;
        }
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        downUtil.clickPauseBtn();
        super.onDestroy();
    }

    /**
     * 格式化資料大小
     */
    private String formateSize(long size) {
        return Formatter.formatFileSize(this, size);
    }

}
           

布局

<?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.testdemo.activity.DownloadActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/et_download_url"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="輸入下載下傳位址"/>

        <Button
            android:id="@+id/btn_download_geturl"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="确定"/>

    </LinearLayout>

    <com.example.testdemo.view.NumberProgressView
        android:id="@+id/np_download"
        android:layout_width="match_parent"
        android:layout_height="100dp">
    </com.example.testdemo.view.NumberProgressView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_download_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="開始下載下傳"/>

        <Button
            android:id="@+id/btn_download_pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暫停下載下傳"/>

        <Button
            android:id="@+id/btn_download_cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消下載下傳"/>

    </LinearLayout>

    <TextView
        android:id="@+id/tv_download_file_info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_download_speed"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_download_speed_info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>
           

下載下傳Activity的父類

public abstract class BaseActivity extends Activity {

    /**
     * 點選監聽器,子類也可以使用
     */
    protected BaseOnClickListener onClickListener = new BaseOnClickListener();

    /**
     * 在onCreate方法裡面,找到視圖,初始化資料,設定點選監聽器
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(childView());
        findView();
        initData();
        setListener();
    }

    /**
     * 傳回子類要顯示的布局 R.layout......
     */
    protected abstract int childView();

    /**
     * 找到需要的視圖---網址:https://www.buzzingandroid.com/tools/android-layout-finder/
     */
    private void findView() {
        findChildView();
    }

    /**
     * 初始化資料
     */
    private void initData() {
        // 初始化子類的資料
        initChildData();
    }

    /**
     * 對需要設定監聽 的視圖設定監聽
     */
    private void setListener() {
        // 子類實作setChildListene()方法,傳回一個View數組,裡面包含所有需要設定點選監聽的View,然後進行For循環,對裡面所有View設定監聽器
        if (setChildListener() == null || setChildListener().length == 0) {
            return;
        }
        View[] viewArray = setChildListener();
        for (View view : viewArray) {
            view.setOnClickListener(onClickListener);
        }
    }

    /**
     * 自定義的點選監聽類
     */
    protected class BaseOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            clickChildView(v);
        }
    }

    /**
     * 強制子類實作該抽象方法,初始化各自的View
     */
    protected abstract void findChildView();

    /**
     * 強制子類實作該抽象方法,初始化各自資料
     */
    protected abstract void initChildData();

    /**
     * 強制子類實作該抽象方法,設定自己的監聽器
     */
    protected abstract View[] setChildListener();

    /**
     * 子類在這個方法裡面實作自己View的點選監聽事件的相應
     *
     * @param v 父類傳遞到子類的點選的View
     */

    protected abstract void clickChildView(View v);

}
           

得到資料庫的操作類

public class DBUtil {

    public static FinalDb getFinalDb(final Context context) {

        FinalDb finalDb = FinalDb.create(context, "REMUXING.db", false, 1, new FinalDb.DbUpdateListener() {
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVirsion, int newVirsion) {

            }
        });
        return finalDb;
    }
}
           

記憶體中存儲下載下傳資訊的類

public class MapUtil {
    public static HashMap<Integer, ThreadDownloadInfoBean> map = new HashMap<>();
}
           

SP存儲的工具類

public enum SPUtil {

    SPUTIL;

    public static final String NOTIFICATIONBAR_HEIGHT = "notificationbar_height";

    private static SharedPreferences sp;

    public static SPUtil getInstance(Context context) {

        if (sp == null) {
            sp = context.getSharedPreferences("REMUXING", Context.MODE_PRIVATE);
        }
        return SPUTIL;
    }

    public void saveLong(String key, Long value) {
        sp.edit().putLong(key, value).apply();
    }

    public Long getLong(String key, Long defValue) {
        return sp.getLong(key, defValue);
    }

    public void saveString(String key, String value) {
        sp.edit().putString(key, value).apply();
    }

    public String getString(String key, String defValue) {
        return sp.getString(key, defValue);
    }

    public void removeAll() {
        sp.edit().clear().apply();
    }

}
           

工具類

public class Utils {

    /**
     * 列印
     */
    public static void Print(String message) {
        Log.e("TAG", "-----      " + message + "      ------") ;
    }

    /**
     * 短吐司
     */
    public static void ToastS(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}
           

進度條:這個可以忽略,用安卓原生的,代碼看我的另一篇部落格:http://blog.csdn.net/qq_27102463/article/details/51612999。