首页 > 代码库 > 用Service实现断点下载

用Service实现断点下载



总体的思路:

在下载文件时,将进度写入数据库,同时通知该ContentProvider的观察者更新页面,这个通知过程不要太频繁,我设置了10次,否则页面会灰常卡。

如果异常中断(网络中断或程序被kill)也没有关系,因为数据库中有记录,只要从该文件的断点位置继续下载就可以了。关键在于一切以数据库为准

就可以了。


同时要注意的是:

1、自始至终处理的都是同一个PO对象,没有直接用类的成员变量,因为下次启动线程,成员变量会变化,导致诡异的下载文件不对应。

2、启动线程后,将该线程保存进static类变量 downloaders 里,方便在Service类外进行停止线程。

3、因为有一个或多个下载进度的页面,所以在下载更新数据库的同时,进行了通知观察者的操作,当然也可以有其他的方法,比如,

1)在Activity里添加一个线程时刻扫描数据库等等,不过感觉没有这个好。

2)也有用更新进度的时候,不断发广播的方式,在Activity中注册广播接收者更新页


大家有更好的办法请回复。


红色字体部分为关键,其他都是浮云。。


/**
 *
 * 由于该Service用于多线程下载文件,需要确保原子性,一直处理一个DownLoadFilePO对象
 *
 * <p>detailed comment
 * @see
 * @since 1.0
 */
public class DownLoadService extends Service {
    private static final String TAG = "DownLoadFileService";
    private String downloadUrl;
    private boolean showToast;
    public static Map<String, DownLoadThread> downloaders = new HashMap<String, DownLoadThread>();

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        //只执行一次
        super.onCreate();
    }

    @Override
    @Deprecated
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        if (intent == null) {
            return;
        }
        downloadUrl = intent.getStringExtra("downloadUrl");
        showToast = intent.getBooleanExtra("showToast", false);

        DownLoadThread downLoadThread = new DownLoadThread(downloadUrl);
        Thread thread = new Thread(downLoadThread);
        thread.start();

    }

    /**
     *
     * 下载线程
     *
     * <p>detailed comment
     * @see
     * @since 1.0
     */
    public class DownLoadThread implements Runnable {
        String url;
        boolean downloading = true;

        DownLoadThread(String downloadUrl) {
            this.url = downloadUrl;
        }

        /**
         * 终止线程
         */
        public void stop() {
            this.downloading = false;
        }

        @Override
        public void run() {
            //查看是否已经在下载
            DownLoadFilePO po = queryByDownLoadUrl(url);
            if (po != null) {

                if (showToast) {
                    showToast(getResources().getString(R.string.this_file_is_downloaded));
                } else {
                    downloaders.put(downloadUrl, this);
                    showToast(getResources().getString(R.string.this_file_begin_download));
                    download(po);
                }
            } else {
                po = init(url);
                initProgress(po);
                downloaders.put(downloadUrl, this);
                download(po);
            }
        }

        /**
         * 下载
         */
        private void download(DownLoadFilePO po) {
            HttpURLConnection connection = null;
            RandomAccessFile randomAccessFile = null;
            InputStream is = null;
            try {
                URL url = new URL(downloadUrl);
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
                // 设置远程文件的下载范围,格式为Range:bytes x-y;
                connection.setRequestProperty("Range",
                        "bytes=" + po.getCurrent_size() + "-" + po.getTotal_size());
                //设置本地文件的下载开始位置
                File localFile = new File(po.getFile());
                randomAccessFile = new RandomAccessFile(localFile, "rwd");
                randomAccessFile.seek(po.getCurrent_size());
                is = connection.getInputStream();
                byte[] buffer = new byte[1024000];

                int length = -1;
                int count = 0;
                while ((length = is.read(buffer)) != -1) {
                    randomAccessFile.write(buffer, 0, length);
                    // 更新数据库中的下载信息
                    po.setCurrent_size(po.getCurrent_size() + length);
                    updateProgress(po);
                    // 用消息将下载信息传给进度条,对进度条进行更新
                    count++;
                    if (count % 10 == 0) {
                        getContentResolver().notifyChange(ConstantUtil.uri, null);//如果改变数据,则通知所有人
                        Log.v(TAG, po.getName() + "=======下载进度======" + po.getCurrent_size());
                    }
                    if (!downloading) {
                        Log.v(TAG, po.getName() + "=======暂停====" + po.getCurrent_size());
                        downloaders.remove(po.getDownloadUrl());
                        return;
                    }
                }
                //下载完成
                if (po.getCurrent_size() == po.getTotal_size()) {
                    finishProgress(po);
                }
                downloaders.remove(po.getDownloadUrl());
            } catch (Exception e) {
                getContentResolver().notifyChange(ConstantUtil.uri, null);//如果改变数据,则通知所有人
                this.stop();
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                    randomAccessFile.close();
                    connection.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 初始化
     */
    private DownLoadFilePO init(String downloadUrl) {
        DownLoadFilePO po = new DownLoadFilePO();

        try {
            if (!FileUtil.checkFileDir()) {
                return null;
            }

            URL url = new URL(downloadUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(5000);
            connection.setRequestMethod("GET");
            int fileSize = connection.getContentLength();
            //总大小
            String fileName = FileUtil.getFileNameByUrl(downloadUrl);
            try {
                fileName = URLDecoder.decode(fileName, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, e.getMessage());
            }
            File localFile = new File(ConstantUtil.downloadFileDir + fileName);
            //判断同名文件已经存在,如存在同名的文件,需修改文件名称 sample.mp4 ==>sample(1).mp4
            int i = 1;
            while (localFile.exists()) {
                int index = fileName.lastIndexOf(".");
                fileName = fileName.substring(0, index) + "(" + i + ")"
                        + fileName.substring(index, fileName.length());
                localFile = new File(ConstantUtil.downloadFileDir + fileName);
                i++;
            }
            localFile.createNewFile();
            i = 0;
            //            }
            // 本地访问文件
            RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");
            accessFile.setLength(fileSize);
            accessFile.close();
            connection.disconnect();

            po.setName(fileName);
            po.setDownloadUrl(downloadUrl);
            po.setCurrent_size(0L);
            po.setTotal_size(fileSize);
            po.setFile(localFile.getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return po;
    }

    /**
     * 向数据库中插入初始化数据
     */
    private void initProgress(DownLoadFilePO po) {
        ContentValues values = new ContentValues();
        values.put(DataBaseUtil.DOWNLOAD_FILE_NAME, po.getName());
        values.put(DataBaseUtil.CREATE_FILE_DATE, new Date().toString());
        values.put(DataBaseUtil.DOWNLOAD_FILE_TOTAL_SIZE, po.getTotal_size());
        values.put(DataBaseUtil.DOWNLOAD_FILE_CURRENT_SIZE, 0);
        values.put(DataBaseUtil.DOWNLOAD_FINISHED, false);
        values.put(DataBaseUtil.DOWNLOAD_URL, po.getDownloadUrl());
        values.put(DataBaseUtil.DONWLOAD_FILE_PATH, po.getFile());

        this.getContentResolver().insert(ConstantUtil.uri, values); //向ContentProvider插入数据 
    }

    /**
     *
     * 更新进度
     *
     *<p>detail comment
     *@see
     *@since
     *
     */
    private void updateProgress(DownLoadFilePO po) {
        ContentValues values = new ContentValues();
        values.put(DataBaseUtil.DOWNLOAD_FILE_CURRENT_SIZE, po.getCurrent_size());
        this.getContentResolver().update(ConstantUtil.uri, values,
                DataBaseUtil.DOWNLOAD_URL + "=?", new String[] { po.getDownloadUrl() });
    }

    /**
     *
     * 完成下载
     *
     *<p>detail comment
     *@param currentSize
     *@see
     *@since
     *
     */
    private void finishProgress(DownLoadFilePO po) {
        ContentValues values = new ContentValues();
        values.put(DataBaseUtil.DOWNLOAD_FILE_CURRENT_SIZE, po.getTotal_size());
        values.put(DataBaseUtil.DOWNLOAD_FINISHED, true);

        this.getContentResolver().update(ConstantUtil.uri, values,
                DataBaseUtil.DOWNLOAD_URL + "=?", new String[] { po.getDownloadUrl() });
        getContentResolver().notifyChange(ConstantUtil.uri, null);
    }

    /**
     *
     * 查看该url是否在下载
     *
     *<p>detail comment
     *@param url
     *@return
     *@see
     *@since
     *
     */
    private DownLoadFilePO queryByDownLoadUrl(String url) {
        Cursor cursor = this.getContentResolver().query(
                ConstantUtil.uri,
                new String[] { DataBaseUtil.DOWNLOAD_FILE_NAME,
                        DataBaseUtil.DOWNLOAD_FILE_CURRENT_SIZE,
                        DataBaseUtil.DOWNLOAD_FILE_TOTAL_SIZE, DataBaseUtil.DONWLOAD_FILE_PATH,
                        DataBaseUtil.DOWNLOAD_FINISHED }, DataBaseUtil.DOWNLOAD_URL + "=?",
                new String[] { url }, null);
        if (cursor.moveToNext()) {
            DownLoadFilePO po = new DownLoadFilePO();
            for (int i = 0; i < cursor.getCount(); i++) {
                cursor.moveToPosition(i);
                po.setName(cursor.getString(0));
                po.setCurrent_size(cursor.getLong(1));
                po.setTotal_size(cursor.getLong(2));
                po.setFile(cursor.getString(3));
                po.setDownloadUrl(url);
            }
            return po;
        }

        return null;
    }

    private void showToast(final String toast) {
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            public void run() {
                Toast.makeText(DownLoadService.this, toast, Toast.LENGTH_SHORT).show();
            }
        });
    }
}