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

先上代码

效果图

这里写图片描述

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中的多线程断点续传是一种技术,用于在文件传输过程中出现中断时能够自动恢复传输并继续未完成的部分。它的目的是提高文件传输的效率和稳定性。 在Unity中实现多线程断点续传的一种常见方式是使用协程和分块传输。首先,需要将大文件分成若干个固定大小的块,每个块独立传输。然后,使用协程开启多个线程,同时传输这些块。如果传输过程中出现中断,只需要记录已经传输完成的块和传输进度,下次继续传输时可以先检查已完成的块,并从断点处继续传输未完成的块。这样可以避免从头开始传输整个文件。 多线程断点续传的好处是可以更加高效地传输大文件。通过同时传输多个块,可以将文件传输时间大大缩短。而断点续传的功能则保证了即使传输中断,也能够从中断处继续传输,避免重新传输整个文件,减少了时间和资源的浪费。 然而,实现多线程断点续传也存在一些挑战。首先,需要确保多个线程之间的同步和互斥,以避免数据冲突和竞争条件。其次,需要处理可能出现的网络异常和传输错误,以保证传输的稳定性和可靠性。此外,为了方便管理和监控传输过程,也需要实现相关的进度条和错误处理机制。 总而言之,Unity中的多线程断点续传是一种优化文件传输的技术,通过分块传输和使用协程来实现在传输中断时能够自动恢复并继续传输。它可以提高传输效率和稳定性,适用于需要传输大文件的应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值