单线程下载,并实现断点续传

作者: 夏至 转载请保留以下申明,谢谢
http://blog.csdn.net/u011418943/article/details/56674086

现在网络框架越来越多,对于下载一个文件来说,用一个别人封装好的库,基本很容易就可以搞定,比如用okhttp3这个比较优秀的框架,里面它封装好的网络框架就非常优秀。
当然,有时候我们需要自定义我们自己的下载器,比如显示一些其他要实现其他属性,这个时候,就不得不自己去封装了。其实也不是太难,就是可能在考虑上欠缺了吧。
这篇博客是看完慕课网的异步下载笔记,不了解的可以先去看。连接如下:http://www.imooc.com/learn/363

废话不多说,先上效果图:
这里写图片描述

工程下载地址如下:
https://git.oschina.net/zhengshaorui/filedownloaduesdb.git

扩展之后,我们可以实现apk的在线更新功能,效果图如下所示:

这里写图片描述

工程下载地址如下:
https://git.oschina.net/zhengshaorui/ApkAutoUpdateDemo.git

首先,通过 Serioliable 传递参数,定义一个实体类:

/**
 * 用serializable就可以接受intent的参数了
 * @author zhengshaorui
 *
 */
public class FileInfo implements Serializable{

    private int id;
    private String apkurl;
    private String fileDir;
    private String fileName;
    private int length;
    private File Dir;
    private int finished;
    private int threadCount;



    public FileInfo() {
        super();
    }

    public FileInfo(int id, String apkurl, String fileDir, String fileName,
                    int length, File dir, int finished, int threadCount) {
        this.id = id;
        this.apkurl = apkurl;
        this.fileDir = fileDir;
        this.fileName = fileName;
        this.length = length;
        Dir = dir;
        this.finished = finished;
        this.threadCount = threadCount;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getApkurl() {
        return apkurl;
    }
    public void setApkurl(String apkurl) {
        this.apkurl = apkurl;
    }
    public String getFileName() {
        return fileName;
    }

    @Override
    public String toString() {
        return "FileInfo{" +
                "id=" + id +
                ", apkurl='" + apkurl + '\'' +
                ", fileDir='" + fileDir + '\'' +
                ", fileName='" + fileName + '\'' +
                ", length=" + length +
                ", Dir=" + Dir +
                ", finished=" + finished +
                ", threadCount=" + threadCount +
                '}';
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public int getFinished() {
        return finished;
    }
    public void setFinished(int finished) {
        this.finished = finished;
    }

    public String getFileDir() {
        return fileDir;
    }

    public void setFileDir(String fileDir) {
        this.fileDir = fileDir;
    }

    public File getDir() {
        return Dir;
    }

    public void setDir(File dir) {
        Dir = dir;
    }

    public int getThreadCount() {
        return threadCount;
    }

    public void setThreadCount(int threadCount) {
        this.threadCount = threadCount;
    }
}

这里你可能有疑问,threadcount是什么gui,还有其他乱七八糟的属性,别急,这个是为了接下来的多线程的封装的。

然后,main函数中,把参数传递给service

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fileDir = getFilesDir()+"/mydownload/";
        mFileInfo = new FileInfo(0,"http://images.infzm.com/mobile/infzmreader.apk",
                fileDir,"north.apk",0,null,0);
    }
    public void start(View view){
        Intent start = new Intent(this,DowaloadService.class);
        start.putExtra("fileinfo",mFileInfo);
        start.putExtra("service",1);
        startService(start);
    }
    public void stop(View view){
        Intent start = new Intent(this,DowaloadService.class);
        start.putExtra("fileinfo",mFileInfo);
        start.putExtra("service",0);
        startService(start);
    }

service 中,先启动线程检测文件的大小;把它保存到Fileinfo里面,再启动下城下载:

 public int onStartCommand(Intent intent, int flags, int startId) {
        FileInfo mFileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");
        int status = intent.getIntExtra("service",0);
        if (status == 1){
            Log.d(TAG, "start: "+mFileInfo);
        }else{
            Log.d(TAG, "stop: ");
        }
        new download(mFileInfo).start();
        return super.onStartCommand(intent, flags, startId);
    }
    class download extends  Thread{
        FileInfo mFileInfo;
        public download(FileInfo fileinfo){
            this.mFileInfo = fileinfo;
        }
        @Override
        public void run() {
            super.run();
            HttpURLConnection conn = null;
            try {
                URL url = new URL(mFileInfo.getApkurl());
                conn = (HttpURLConnection) url.openConnection();
                //加上这句是为了防止connection.getContentLength()获取不到
                conn.setRequestProperty("Accept-Encoding", "identity");
                conn.setRequestMethod("GET");
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(5000);
                if (conn.getResponseCode() == 200){
                    int length = conn.getContentLength();
                    if (length < 0) return;
                    else mFileInfo.setLength(length);
                }
                File dir = new File(mFileInfo.getFileDir());
                if (!dir.exists()){
                   dir.mkdir();
                }
                mFileInfo.setDir(dir);
                Log.d(TAG, "run: "+mFileInfo);
                new DownloadTask(mContext).download(mFileInfo);
            } catch (Exception e) {
                e.printStackTrace();
                Log.d(TAG, "error: "+e.toString());
            }finally {
                conn.disconnect();
            }
        }
    }

其中下载方法 DownloadTask 如下所示:

public class DownloadTask {
    private static final String TAG = "zsr";
    private Context mContext;
    private int downloadlength = 0;
    public DownloadTask(Context mContext) {
        this.mContext = mContext;
    }
    public void download(FileInfo fileInfo){
        new DownloadThread(fileInfo).start();
    }
    class DownloadThread extends Thread{
        FileInfo mFileInfo;
        public DownloadThread(FileInfo mFileInfo) {
            this.mFileInfo = mFileInfo;
        }
        @Override
        public void run() {
            super.run();
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            BufferedInputStream bis = null;
            try {
                URL url = new URL(mFileInfo.getApkurl());
                conn = (HttpURLConnection) url.openConnection();
                //加上这句是为了防止connection.getContentLength()获取不到
                conn.setRequestProperty("Accept-Encoding", "identity");
                conn.setRequestMethod("GET");
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(5000);
                int start = 0;
                int end = mFileInfo.getLength();
                //设置下载位置
                conn.setRequestProperty("Range", "bytes="+start+"-"+end);
                //文件写入位置
                File file = new File(mFileInfo.getDir(),mFileInfo.getFileName());
                raf = new RandomAccessFile(file,"rwd");
                raf.seek(start);
                int len = -1;
                byte[] bytes = new byte[1024*2];
                bis = new BufferedInputStream(conn.getInputStream());
                while( (len = bis.read(bytes)) != -1 ){
                    raf.write(bytes,0,len);
                    downloadlength += len;
                    Log.d(TAG, "run: "+downloadlength);
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.d(TAG, "downloaderror: "+e.toString());
            }finally {
                if (bis != null) try {
                    bis.close();
                    if (raf != null) raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (conn != null) conn.disconnect();
            }
        }
    }
}

log 和文件如下:
这里写图片描述
这里写图片描述

那么如何把文件保存到数据库中,实现断点续传呢 ,其实说白了,就是把上次的断点保存在文件、map或者数据库中。这里保存在数据库中
首先定义一个实体类,ThreadInfo:

public class ThreadInfo {
    private int id;
    private String url;
    private int start;
    private int end;
    private int finished;

    public ThreadInfo() {
        super();
    }

    public ThreadInfo(int id, String url, int start, int end, int finished) {
        super();
        this.id = id;
        this.url = url;
        this.start = start;
        this.end = end;
        this.finished = finished;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getStart() {
        return start;
    }
    public void setStart(int start) {
        this.start = start;
    }
    public int getEnd() {
        return end;
    }
    public void setEnd(int end) {
        this.end = end;
    }
    public int getFinished() {
        return finished;
    }
    public void setFinished(int finished) {
        this.finished = finished;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    @Override
    public String toString() {
        return "ThreadInfo [id=" + id + ", url=" + url + ", start=" + start
                + ", end=" + end + ", finished=" + finished + "]";
    }


}

ThreadInfo 是来记录线程信息的 ,所以,而数据的保存,我们用数据库来保存;首先,先定义表单:

public class DBhelper extends SQLiteOpenHelper{
    public static final String DB_NAME = "download.db";
    public static final int VERSION = 1;
    private static final String SQL_CREATE_TABLE = "create table if not exists thread_info(" +
            "_id integer primary key autoincrement," +
            "thread_id integer," +
            "url text," +
            "start integer," +
            "end integer," +
            "finished interger)";
    private static final String SQL_DROP = "drop table if exists thread_info";

    public DBhelper(Context context, String name, CursorFactory factory,
            int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        db.execSQL(SQL_CREATE_TABLE);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
        // TODO Auto-generated method stub
        db.execSQL(SQL_DROP);
        db.execSQL(SQL_CREATE_TABLE);
    }
}

上面的实现很简单,就是创建一个数据库,然后,我们需要给它添加 listener回调接口:

public interface DBTreadListener {
    /**
     * 插入线程信息
     * @param threadInfo
     */
    public void insertThread(ThreadInfo threadInfo);
    /**
     * 删除数据库,用两个标志位来判断
     * @param url
     * @param thread_id
     */
    public void deleteThread(String url, int thread_id);
    /**
     * 更新线程下载进度 finished
     * @param url
     * @param thread_id
     * @param finished
     */
    public void updateThread(String url, int thread_id, int finished);
    /**
     * 查询文件的线程信息,以一个List的形式返回
     * @param url
     * @return
     */
    public List<ThreadInfo> getThreads(String url);
    /**
     * 判断线程是否存在,如果存在则更新,如果不存在则创建
     * @param url
     * @return
     */
    public boolean isThreadExsits(String url, int thread_id);
}

回调接口中,我们要实现的方法就是数据库的 增删查改,还有就是检查该数据库,是否有线程和判断线程是否存在。接下来就是数据库的实现方法了:

/**
 * 数据库接口的实现
 * 
 * @author zhengshaorui
 * 
 */
public class DBThreadListenerImp implements DBTreadListener {
    private DBhelper mDBhelper = null;
    public DBThreadListenerImp(Context context) {
        // 在接口实现的时候,创建数据库表
        mDBhelper = new DBhelper(context, DBhelper.DB_NAME, null,
                DBhelper.VERSION);
    }
    public void insertThread(ThreadInfo threadInfo) {
        // TODO Auto-generated method stub
        SQLiteDatabase db = mDBhelper.getWritableDatabase();
        db.execSQL(
                "insert into thread_info(thread_id,url,start,end,finished) values(?,?,?,?,?)",
                new Object[] {threadInfo.getId(), threadInfo.getUrl(),
                        threadInfo.getStart(), threadInfo.getEnd(),
                        threadInfo.getFinished() });
        db.close();
    }
    @Override
    public void deleteThread(String url, int thread_id) {
        // TODO Auto-generated method stub
        SQLiteDatabase db = mDBhelper.getWritableDatabase();
        db.execSQL("delete from thread_info where url = ? and thread_id = ?",
                new Object[] { url, thread_id });
        db.close();
    }
    @Override
    public void updateThread(String url, int thread_id, int finished) {
        // TODO Auto-generated method stub
        Log.d("zsr", "finish: "+finished);
        SQLiteDatabase db = mDBhelper.getWritableDatabase();
        db.execSQL(
                "update thread_info set finished = ? where url = ? and thread_id = ?",
                new Object[] { finished, url, thread_id });
        db.close();
    }
    public List<ThreadInfo> getThreads(String url) {
        List<ThreadInfo> mList = new ArrayList<ThreadInfo>();
        SQLiteDatabase db = mDBhelper.getWritableDatabase();
        Cursor cursor = db.rawQuery("select * from thread_info where url = ?",
                new String[] { url });
        if (cursor.moveToFirst()) {
            do {
                ThreadInfo mThreadInfo = new ThreadInfo();
                mThreadInfo.setUrl(cursor.getString(cursor
                        .getColumnIndex("url")));
                mThreadInfo.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
                mThreadInfo.setStart(cursor.getInt(cursor
                        .getColumnIndex("start")));
                mThreadInfo.setEnd(cursor.getInt(cursor.getColumnIndex("end")));
                mThreadInfo.setFinished(cursor.getInt(cursor
                        .getColumnIndex("finished")));
                mList.add(mThreadInfo);
            } while (cursor.moveToNext());
        }
        db.close();
        cursor.close();
        return mList;
    }
    @Override
    public boolean isThreadExsits(String url, int thread_id) {
        boolean isExsits = false;
        SQLiteDatabase db = mDBhelper.getWritableDatabase();
        Cursor cursor = db.rawQuery(
                "select * from thread_info where url = ? and thread_id = ?",
                new String[] { url, thread_id + "" });
        isExsits = cursor.moveToNext();
        db.close();
        cursor.close();
        return isExsits;
    }
}

ok,这样,我们的续传的数据库就写好了,接下来就是对把线程信息给保存起来了;我们要修改一下我们的 DownloadTask 这个类:
首先,先初始化数据库:

private DBThreadListener mDbThreadListener;
    public DownloadTask(Context mContext) {
        this.mContext = mContext;
        mDbThreadListener = new DBThreadListenerImp(mContext);
    }

然后修改其中的download:

public void download(FileInfo fileInfo){
        mFileInfo = fileInfo;
        List<ThreadInfo> threadInfos = mDbThreadListener.getThreads(fileInfo.getApkurl()); 
        ThreadInfo mThreadInfo;
        if (threadInfos.size() == 0){ //第一次启动
            mThreadInfo = new ThreadInfo(0,fileInfo.getApkurl(),0,fileInfo.getLength(),0,fileInfo.getLength());
        }else{
            mThreadInfo = threadInfos.get(0); 
        }
        new DownloadThread(mThreadInfo).start();
    }

然后修改DownloadThread:

class DownloadThread extends Thread{
        ThreadInfo mThreadInfo;
        public DownloadThread(ThreadInfo threadinfo) {
            this.mThreadInfo = threadinfo;
        }
        @Override
        public void run() {
            super.run();
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            BufferedInputStream bis = null;
            //开始之前先检测是否数据库有线程信息
            if (!mDbThreadListener.isThreadExsits(mThreadInfo.getUrl(),mThreadInfo.getId())){
                mDbThreadListener.insertThread(mThreadInfo); //没有则把线程信息保存进去
            }
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                //加上这句是为了防止connection.getContentLength()获取不到
                conn.setRequestProperty("Accept-Encoding", "identity");
                conn.setRequestMethod("GET");
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(5000);
                //设置下载位置
                int start = mThreadInfo.getStart()+mThreadInfo.getFinished(); //起始位置
                int end = mThreadInfo.getEnd(); //结束位置
                conn.setRequestProperty("Range", "bytes="+start+"-"+end);
                //文件写入位置
                File file = new File(mFileInfo.getDir(),mFileInfo.getFileName());
                raf = new RandomAccessFile(file,"rwd");
                raf.seek(start);

                int len = -1;
                downloadlength += len; //主要是暂停之后,重新下载,获取以前的进度
                byte[] bytes = new byte[1024*2];
                bis = new BufferedInputStream(conn.getInputStream());
                while( (len = bis.read(bytes)) != -1 ){
                    raf.write(bytes,0,len);
                    downloadlength += len;
                    if (isPause) {
                        //数据库保存当前信息
                        mDbThreadListener.updateThread(mThreadInfo.getUrl(), mThreadInfo.getId(),
                                downloadlength);
                        return;
                    }
                    //以百分比的形式传递
                    Log.d(TAG, "run: "+downloadlength*100/mFileInfo.getLength());
                }
                //如果完成,则把以前的线程信息干掉
                mDbThreadListener.deleteThread(mThreadInfo.getUrl(),mThreadInfo.getId());
            } catch (Exception e) {
                e.printStackTrace();
                Log.d(TAG, "downloaderror: "+e.toString());
            }finally {
                if (bis != null) try {
                    bis.close();
                    if (raf != null) raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (conn != null) conn.disconnect();
            }
        }
    }

这样就完成了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值