单线程断点续传(数据库方式)

来源:互联网 发布:淘宝店怎么运营起来 编辑:程序博客网 时间:2024/06/07 22:11

先上代码

效果图

这里写图片描述

public class MainActivity extends AppCompatActivity implements View.OnClickListener{    private TextView textView;    private Button mBtnstart;    private Button mBtnstop;    private ProgressBar progressBar;    //url必须带前缀,也就是协议,否则会报错    public static final String url = "http://www.imooc.com/mobile/imooc.apk";    private FileInfo fileInfo;    private BroadcastReceiver receiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            if(DownloadService.ACTION_UPDATE.equals(action)){                int finished = intent.getIntExtra("finished", 0);                progressBar.setProgress(finished);                textView.setText("下载进度"+String.valueOf(finished)+"%");            }        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView = (TextView) findViewById(R.id.textView);         mBtnstart = (Button) findViewById(R.id.btn_start);        mBtnstop = (Button) findViewById(R.id.btn_stop);        mBtnstop.setOnClickListener(this);        mBtnstart.setOnClickListener(this);        progressBar = (ProgressBar) findViewById(R.id.progressBar);        progressBar.setMax(100);        fileInfo = new FileInfo(0,url,"imooc.apk",0,0);        //注册广播        IntentFilter filter = new IntentFilter(DownloadService.ACTION_UPDATE);        registerReceiver(receiver, filter);    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.btn_start:                Intent intent = new Intent(this, DownloadService.class);                intent.setAction(DownloadService.ACTION_START);                intent.putExtra("fileinfo",fileInfo);                startService(intent);                break;            case R.id.btn_stop:                Intent intent2 = new Intent(this, DownloadService.class);                intent2.setAction(DownloadService.ACTION_STOP);                startService(intent2);                break;        }    }    @Override    protected void onDestroy() {        super.onDestroy();        unregisterReceiver(receiver);    }}
public class DownloadService extends Service {    private static final String TAG = "DownloanService";    public static final String ACTION_START = "ACTION_START";    public static final String ACTION_STOP = "ACTION_STOP";    public static final String ACTION_UPDATE = "ACTION_UPDATE";    public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory()            .getAbsolutePath()+"/downloads/";    public static final int MSG_INIT_THREAD = 0;    private DownloadTask mTask = null;    public static boolean isStarted = false;    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case MSG_INIT_THREAD:                    FileInfo fileInfo = (FileInfo) msg.obj;                    Log.d(TAG, "length: "+fileInfo.length);                    //将文件信息传给下载任务                    mTask = new DownloadTask(DownloadService.this,fileInfo);                    mTask.download();//启动任务下载                    break;            }        }    };    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        if(ACTION_START.equals(intent.getAction())){            //获取文件信息            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileinfo");            if(!isStarted) {                new initThread(fileInfo).start();                isStarted = true;            }        }else if(ACTION_STOP.equals(intent.getAction())){            if(mTask!=null){                mTask.isPause = true;                isStarted = false;            }        }        return super.onStartCommand(intent, flags, startId);    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return null;    }    class initThread extends Thread{        private FileInfo fileInfo;        private RandomAccessFile raf;        private HttpURLConnection conn;        public initThread(FileInfo fileInfo){            this.fileInfo = fileInfo;        }        @Override        public void run() {            super.run();            try {                URL url = new URL(fileInfo.url);//www.imooc.com/mobile/imooc.apk                conn = (HttpURLConnection) url.openConnection();//打开链接                conn.setConnectTimeout(3000);                conn.setReadTimeout(3000);                int len = -1;                if(conn.getResponseCode() == 200){                    len = conn.getContentLength();//获取服务器文件长度                }                if(len < 0){                    return;                }                File dir = new File(DOWNLOAD_PATH);                if(!dir.exists()){                    dir.mkdir();//目录不存在创建目录                }                File file = new File(dir,fileInfo.fileName);                //在指定路径下创建一个个服务器文件大小一样的文件                raf = new RandomAccessFile(file,"rwd");                raf.setLength(len);//设置临时文件的长度为服务器文件长度                fileInfo.length = len;//设置文件信息                handler.obtainMessage(MSG_INIT_THREAD,fileInfo).sendToTarget();            }catch (Exception e){                e.printStackTrace();            }finally {                if(conn!=null)                conn.disconnect();                try {                    if(raf!=null)                    raf.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }}
public class DownloadTask {    private Context mContext;    //要下载的文件信息,    private FileInfo fileInfo;    private ThreadDao dao;    public boolean isPause = false;    //完成的进度    private int finished;    public DownloadTask(Context mContext, FileInfo fileInfo) {        this.mContext = mContext;        this.fileInfo = fileInfo;        dao = new ThreadDaoImpl(mContext);    }    public void download(){        //每次下载任务前,根据url查询线程信息        List<ThreadInfo> threadInfos = dao.queryThread(fileInfo.url);        ThreadInfo threadInfo;        if(threadInfos.size() == 0){//第一次,创建文件信息            threadInfo = new ThreadInfo(0,0,fileInfo.length,fileInfo.url,0);        }else{            //以后从集合中取出文件信息            threadInfo = threadInfos.get(0);        }            new DownloadThread(threadInfo).start();    }    class DownloadThread extends Thread{        private ThreadInfo threadinfo;        private RandomAccessFile raf;        private HttpURLConnection conn;        public DownloadThread(ThreadInfo threadInfo){            this.threadinfo = threadInfo;        }        @Override        public void run() {            super.run();            //第一次数据库中不存在信息,向数据库写入信息            if(!dao.isExists(threadinfo.url, threadinfo.id)){                dao.insertThread(threadinfo);            }            //设置下载位置            try {                URL url = new URL(threadinfo.url);//下载链接                conn = (HttpURLConnection) url.openConnection();                conn.setReadTimeout(5000);                conn.setConnectTimeout(4000);                //当前下载位置等于起始位置加上已经下载的进度                int start = threadinfo.start+ threadinfo.finished;                //下载的范围为起始位置到文件长度,因为是单线程下载                conn.setRequestProperty("Range","byte = "+start+"-"+ threadinfo.end);                File file = new File(DownloadService.DOWNLOAD_PATH,fileInfo.fileName);                raf = new RandomAccessFile(file,"rwd");                raf.seek(start);//指定从某个位置起                Intent intent = new Intent(DownloadService.ACTION_UPDATE);                finished += threadinfo.finished;//更新完成的进度                //开始下载                if(conn.getResponseCode() == 200){                    //读取数据                    int len = -1;                    long time = System.currentTimeMillis();                    InputStream stream = conn.getInputStream();                    byte[] buffer = new byte[1024<<2];//每次赌徒多少个字节                    while ((len = stream.read(buffer))!=-1){                        //写入文件                            raf.write(buffer,0,len);                        finished += len;                        if(System.currentTimeMillis() - time >200) {                            time = System.currentTimeMillis();                            //通知Activity更新进度条                            intent.putExtra("finished", finished *100/ threadinfo.end);                                mContext.sendBroadcast(intent);                        }                        //下载暂停,保存进度到数据库                        if(isPause){                            //将当前的信息保存到数据库                            dao.updateThread(threadinfo.url, threadinfo.id+"",finished);                            return;                        }                    }                    //下载完成删除删除下载信息                    dao.deleteThread(threadinfo.url, threadinfo.id+"");                }            } catch (Exception e) {                e.printStackTrace();                dao.updateThread(threadinfo.url, threadinfo.id+"",finished);                DownloadService.isStarted = false;            }finally {                {                    conn.disconnect();                    try {                        raf.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }        }    }}
public class DBHelper extends SQLiteOpenHelper {    public static final String DB_NAME = "download.db";    public static final int DB_VERSION = 1;    //id 必须是integer,否则会报错    public static final String CREATE_TABLE = "create table thread_info(id integer primary key autoincrement," +            "thread_id integer,url text,start integer,end integer,finished integer)";   public static final String DROP_TABLE = "drop table if exists thread_info";    public DBHelper(Context context) {        super(context, DB_NAME, null, DB_VERSION);    }    @Override    public void onCreate(SQLiteDatabase db) {        db.execSQL(CREATE_TABLE);    }    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        db.execSQL(DROP_TABLE);        db.execSQL(CREATE_TABLE);    }}
public class FileInfo implements Serializable{    public  int id;    public String url;    public String fileName;    public int finished;    public int length;    public FileInfo() {    }    public FileInfo(int id, String url, String fileName, int finished, int length) {        this.id = id;        this.url = url;        this.fileName = fileName;        this.finished = finished;        this.length = length;    }    @Override    public String toString() {        return "FileInfo{" +                "id=" + id +                ", url='" + url + '\'' +                ", fileName='" + fileName + '\'' +                ", finished=" + finished +                ", length=" + length +                '}';    }}
public class ThreadInfo {    public int id;    public int start;    public int end;    public String url;    public int finished;    public ThreadInfo() {    }    public ThreadInfo(int id, int start, int end, String url,int finished) {        this.id = id;        this.start = start;        this.end = end;        this.url = url;        this.finished = finished;    }    @Override    public String toString() {        return "ThreadInfo{" +                "id=" + id +                ", start=" + start +                ", end=" + end +                ", url='" + url + '\'' +                '}';    }}
public interface ThreadDao {    void insertThread(ThreadInfo threadInfo);    void deleteThread(String url,String thread_id);    void updateThread(String url,String thread_id,int finished);    List<ThreadInfo> queryThread(String url);     boolean isExists(String url,int thread_id);}
public class ThreadDaoImpl implements ThreadDao {    DBHelper dbHelper;    public ThreadDaoImpl(Context context){        dbHelper = new DBHelper(context);    }    @Override    public void insertThread(ThreadInfo threadInfo) {        SQLiteDatabase db = dbHelper.getReadableDatabase();        ContentValues values = new ContentValues();        values.put("thread_id",threadInfo.id);        values.put("url",threadInfo.url);        values.put("start",threadInfo.start);        values.put("end",threadInfo.end);        values.put("finished",threadInfo.finished);        db.insert("thread_info",null,values);        db.close();    }    @Override    public void deleteThread(String url, String thread_id) {        SQLiteDatabase db = dbHelper.getReadableDatabase();        int count = db.delete("thread_info","url = ? and thread_id = ?",new String[]{url,thread_id} );        System.out.println("========count: "+count);        db.close();    }    @Override    public void updateThread(String url, String thread_id, int finished) {        SQLiteDatabase db = dbHelper.getReadableDatabase();        ContentValues values = new ContentValues();        values.put("finished",finished);        db.update("thread_info",values,"url = ? and thread_id = ?",new String[]{url,thread_id});    }    @Override    public List<ThreadInfo> queryThread(String url) {        List<ThreadInfo> list = new ArrayList<>();        SQLiteDatabase db = dbHelper.getReadableDatabase();        Cursor cursor = db.query("thread_info",null,"url = ?",                new String[]{url},null,null,null);            while (cursor.moveToNext()){                ThreadInfo threadInfo = new ThreadInfo();                threadInfo.id = cursor.getInt(cursor.getColumnIndex("thread_id"));                threadInfo.url= cursor.getString(cursor.getColumnIndex("url"));                threadInfo.start = cursor.getInt(cursor.getColumnIndex("start"));                threadInfo.end = cursor.getInt(cursor.getColumnIndex("end"));                threadInfo.finished = cursor.getInt(cursor.getColumnIndex("finished"));                list.add(threadInfo);            }        cursor.close();        db.close();        return list;    }    public boolean isExists(String url,int thread_id){        SQLiteDatabase db = dbHelper.getReadableDatabase();        Cursor cursor = db.query("thread_info",null,"url = ? and thread_id = ?",                new String[]{url,thread_id+""},null,null,null);        boolean exists = cursor.moveToNext();        cursor.close();        db.close();        return exists;    }}

总结

涉及到的知识点
Activity和Service交互:Activity通过Intent向Service传递,
Service通过广播,另外还可以选择Handler或者进程间通信方式

文件如何下载的;从数据库中取出文件上次下载的位置,使用Range属性去请求

  //当前下载位置等于起始位置加上已经下载的进度                int start = threadinfo.start+ threadinfo.finished;                //下载的范围为起始位置到文件长度,因为是单线程下载                conn.setRequestProperty("Range","byte = "+start+"-"+ threadinfo.end);

使用RandomAccessFile的seek方法从指定位置开始写

 raf = new RandomAccessFile(file,"rwd");                raf.seek(start);//指定从某个位置起

进度如何更新的:在下载文件过程中,通过广播把当前下载的进度发送给Activity,更新界面

如何断点续传的;
下载暂停的时候把进度保存到数据库中去,通过线程的url,id,

  //下载暂停,保存进度到数据库                        if(isPause){                            //将当前的信息保存到数据库                            dao.updateThread(threadinfo.url, threadinfo.id+"",finished);                            return;                        }

下次继续下载的时候从数据库中查找线程信息,接着上次进度下载

  //每次下载任务前,根据url查询线程信息        List<ThreadInfo> threadInfos = dao.queryThread(fileInfo.url);        ......          finished += threadinfo.finished;//更新完成的进度,必须要加上数据库中查询到的文件信息

Service在整个过程中起到什么作用?
其实在Activity中开启线程下载也是可以的,那我们为什么要使用Service呢?
因为Activity属于一个前台的组件,只要是前台组件就有可能被用户关闭,也有可能切换到后台,被系统回收,一旦Activity被回收,那么在Activity中启动的线程就无法跟踪,管理,会导致很多问题,没法对线程进行操作。
Service属于后台的组件,不是直接和用户交互的,用户没法直接关闭,Service优先级比较高,一般不会被系统回收,线程的操作放在Service里面比较保险。

最后给出源码下载位置

点此下载源码

0 0
原创粉丝点击