一个多线程断点续传的案例

来源:互联网 发布:java项目视频教程 编辑:程序博客网 时间:2024/06/06 14:13

文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。

- 实现的原理

1.多线程下载:

首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang - 1,注意最后一段的位置是到filesize - 1;
通过Http协议的Range字段实现下载文件的分段;

通过Java类RandomAccessFile可以实现文件的随机访问,利用seek方法定位的文件的指定位置;

由HttpUrlConnection获取流来进行流的读写,实现文件的存储;

在下载过程中利用Handler来向外传递下载的信息。
2.断点续传:

对于每一个线程利用一个DownloadInfo类来保存下载的信息,每次在下载过程中向数据库更新信息(我也有想过只在下载暂停时进行更新,但那样的话我们的进程被杀掉时信息就无法保存下来)。在进行下载之前去访问数据库是否有记录存在,如果没有执行第一次下载的初始化,如果存在记录但下载文件不存在时,删掉数据库中的记录之后进行第一次下载的初始化,如果有记录且文件存在,则从数据库中取出信息。

- 执行下载操作的类DownloadTask

public DownloadTask(String downloadUrl, int threadNum, String fileptah, Context context) {//没有sd卡的时候,filepath传入“”空值        this.downloadUrl = downloadUrl;        this.threadNum = threadNum;        this.filePath = fileptah;        this.mContext = context;        this.downFileName = fileptah+".tmp  ";        sqlTool = new DownlaodSqlTool(mContext);    }    @Override    public void run() {        //super.run();        downloadInfos = sqlTool.getInfos(downloadUrl);        if (downloadInfos.size()== 0 ){            initFirst();        }        startThread();    }    private void initFirst() {        try {            Log.i(TAG,fiele+"-----------"+"第一次下载");            URL url = new URL(downloadUrl);            HttpURLConnection connection = (HttpURLConnection) url.openConnection();            connection.setRequestMethod("GET");            fileSize = connection.getContentLength();//获取文件的长度            File file = new File(downFileName);            if (!file.exists()) {                file.createNewFile();               }            // 本地访问文件            RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");            accessFile.setLength(fileSize);            accessFile.close();            connection.disconnect();            // 计算每条线程下载的数据长度            blockSize = (fileSize % threadNum) == 0 ? fileSize / threadNum                    : fileSize / threadNum + 1;            //保存每条线程下载的信息            for (int i = 0; i < threadNum - 1; i++) {                //线程id,开始位置,结束位置,完成的下载大小,下载地址                DownloadInfo info = new DownloadInfo(i, i * blockSize, (i + 1) * blockSize                        - 1, 0, downloadUrl);                downloadInfos.add(info);            }            //保存最后一条线程下载的信息            DownloadInfo info = new DownloadInfo(threadNum - 1, (threadNum - 1)                    * blockSize, fileSize - 1, 0, downloadUrl);            downloadInfos.add(info);            sqlTool.insertInfos(downloadInfos);//创建数据        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (ProtocolException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }    private void startThread() {        //开启多条线程去下载        if (downloadInfos != null) {            for (DownloadInfo infos : downloadInfos) {                Log.i(TAG,fiele+"-----------"+infos.getThreadId()+"开启下载线程");                FileDownloadThread thread =  new FileDownloadThread(this,downFileName,infos);                executorService.execute(thread);                downloadThreads.add(thread);            }        }    }    /**     * 暂停下载     */    public void pauseDownload(){        for(FileDownloadThread downloadThread : downloadThreads){            if (downloadThread!=null) {                downloadThread.setPause(true);                Log.i(TAG,fiele+"-----------"+downloadThread.threadId+"暂停了");            }        }    }    /**     * 开始下载     */    public void startDownload(){        downloadThreads.clear();        //downloadInfos.clear();        Log.i(TAG,"网络来了继续开始下");        //initThread();        //run();        startThread();    }    @Override    public void pauseCallBack(DownloadInfo threadBean) {        sqlTool.updataInfos(threadBean.getThreadId(), threadBean.getCompeleteSize(), threadBean.getUrl());//更新下载进度到数据库    }    @Override    public synchronized void threadDownLoadFinished(DownloadInfo downloadInfo) {        for( DownloadInfo info:downloadInfos){            if(info.getThreadId() == downloadInfo.getThreadId()){                //从列表中将已下载完成的线程信息移除                downloadInfos.remove(info);                Log.i(TAG,fiele+"-----------"+downloadInfo.getThreadId()+"下载完成了");                break;            }        }        if(downloadInfos.size()==0){//如果列表size为0 则所有线程已下载完成            //删除数据库中的信息            sqlTool.delete(downloadUrl);            sqlTool.closeDb();            File file = new File(downFileName);            File newf = new File(filePath);            file.renameTo(newf);        }    }

- 执行下载任务的子线程

  public FileDownloadThread(DownloadCallBack callBack, String file, DownloadInfo infos) {        this.mCallBack =callBack;        this.file = file;        this.threadId = infos.getThreadId();        this.startPos = infos.getStartPos();        this.endPos = infos.getEndPos();        this.downloadLength = infos.getCompeleteSize();        this.downloadUrl = infos.getUrl();        this.mDownloadInfo =infos;        blockSize = endPos - startPos + 1;    }    @Override    public void run() {        super.run();        HttpURLConnection connection = null;        BufferedInputStream bis = null;        RandomAccessFile raf = null;        try {            connection = (HttpURLConnection) new URL(downloadUrl).openConnection();            connection.setConnectTimeout(5000);            connection.setRequestMethod("GET");            connection.setAllowUserInteraction(true);            //设置当前线程下载的起点、终点(第一次下载的话,是开始位置+0,后面就是开始位置+已经下载的进度)            connection.setRequestProperty("Range", "bytes="                    + (startPos + downloadLength) + "-" + endPos);            raf = new RandomAccessFile(file, "rwd");            //从文件的指定位置开始读写            raf.seek(startPos + downloadLength);            byte[] buffer = new byte[1024];            bis = new BufferedInputStream(connection.getInputStream());            int len = -1;            while ((len = bis.read(buffer, 0, 1024)) != -1) {                raf.write(buffer, 0, len);//写入文件                downloadLength += len;//已下载的进度叠加                //sqlTool.updataInfos(threadId, downloadLength, downloadUrl);//更新下载进度到数据库                if(mPause){                    mCallBack.pauseCallBack(mDownloadInfo);                    return;                }                if (downloadLength >= blockSize) {//单个线程下载完成                  mCallBack.threadDownLoadFinished(mDownloadInfo);                }            }        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (bis != null) {                    bis.close();                }                raf.close();                connection.disconnect();            } catch (IOException e) {                e.printStackTrace();            }        }    }    public void setPause(boolean pause) {        mPause = pause;    }

- 保存下载信息的数据库操作

创建数据库的操作

/** * 利用数据库来记录下载信息 * @author acer */public class DownLoadHelper extends SQLiteOpenHelper{    private static final String SQL_NAME = "download.db";    private static final int DOWNLOAD_VERSION=1;    public DownLoadHelper(Context context) {        super(context, SQL_NAME, null, DOWNLOAD_VERSION);        // TODO Auto-generated constructor stub    }     /**     * 在download.db数据库下创建一个download_info表存储下载信息     */    @Override    public void onCreate(SQLiteDatabase db) {        db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "                + "start_pos integer, end_pos integer, compelete_size integer,url char)");    }    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {    }

数据库的增删改查

  /**     * 创建下载的具体信息     */    public void insertInfos(List<DownloadInfo> infos) {        SQLiteDatabase database = dbHelper.getWritableDatabase();        for (DownloadInfo info : infos) {            String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";            Object[] bindArgs = { info.getThreadId(), info.getStartPos(),                    info.getEndPos(), info.getCompeleteSize(), info.getUrl() };            database.execSQL(sql, bindArgs);        }    }    /**     * 得到下载具体信息     */    public List<DownloadInfo> getInfos(String urlstr) {        List<DownloadInfo> list = new ArrayList<DownloadInfo>();        SQLiteDatabase database = dbHelper.getWritableDatabase();        String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";        Cursor cursor = database.rawQuery(sql, new String[] { urlstr });        while (cursor.moveToNext()) {            DownloadInfo info = new DownloadInfo(cursor.getInt(0),                    cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),                    cursor.getString(4));            list.add(info);        }        return list;    }    /**     * 更新数据库中的下载信息     */    public void updataInfos(int threadId, int compeleteSize, String urlstr) {        SQLiteDatabase database = dbHelper.getWritableDatabase();        String sql = "update download_info set compelete_size=? where thread_id=? and url=?";        Object[] bindArgs = { compeleteSize, threadId, urlstr };        database.execSQL(sql, bindArgs);    }    /**     * 关闭数据库     */    public void closeDb() {        dbHelper.close();    }    /**     * 下载完成后删除数据库中的数据     */    public void delete(String url) {        SQLiteDatabase database = dbHelper.getWritableDatabase();        database.delete("download_info", "url=?", new String[] { url });    }

- 保存下载信息实体类

public class DownloadInfo {    /**     * 保存每个下载线程下载信息类     *      */private int threadId;// 下载器id    private int startPos;// 开始点    private int endPos;// 结束点    private int compeleteSize;// 完成度    private String url;// 下载文件的URL地址    public DownloadInfo(int threadId, int startPos, int endPos,                        int compeleteSize, String url) {        this.threadId = threadId;        this.startPos = startPos;        this.endPos = endPos;        this.compeleteSize = compeleteSize;        this.url = url;    }    public DownloadInfo() {    }    public String getUrl() {        return url;    }    public void setUrl(String url) {        this.url = url;    }    public int getThreadId() {        return threadId;    }    public void setThreadId(int threadId) {        this.threadId = threadId;    }    public int getStartPos() {        return startPos;    }    public void setStartPos(int startPos) {        this.startPos = startPos;    }    public int getEndPos() {        return endPos;    }    public void setEndPos(int endPos) {        this.endPos = endPos;    }    public int getCompeleteSize() {        return compeleteSize;    }    public void setCompeleteSize(int compeleteSize) {        this.compeleteSize = compeleteSize;    }    @Override    public String toString() {        return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos                + ", endPos=" + endPos + ", compeleteSize=" + compeleteSize                + "]";    }

- 下载完成的回调

public interface DownloadCallBack {    /**     * 暂停回调     * @param threadBean     */    void pauseCallBack(DownloadInfo threadBean);    /**     * 线程下载完毕     * @param threadBean     */    void threadDownLoadFinished(DownloadInfo threadBean);

Demo下载

原创粉丝点击