美文网首页
Android-单线程断点续传

Android-单线程断点续传

作者: 有腹肌的豌豆Z | 来源:发表于2020-09-19 10:09 被阅读0次

    断点续传的原理 看上一篇
    Android-断点续传

    下面的例子是下载的断点续传,断点续传很简单就两点:
    1.网络请求的时候,请求指定位置的数据,这个用到了网络请求的Range

    conn.setRequestProperty("Range", "bytes=" + 500 + "-" + 1000);
    

    网络请求可以使用httpURLconnection或者OkHttpClient
    2.获取到数据之后,将新的数据拼接到目标文件之后,这个需要使用到
    RandomAccessFile 。
    3.本地数据库保存上一次请求的位置,下载信息,以便下一次继续当前之后请求数据,不需要从头开始请求数据。这里可以使用SQLite GreenDao 等。

    手写一个单线程断点续传

    ThreadInfo,下载任务信息 保存到数据库
    public class ThreadInfo {
    
        private int id;         // ID
        private String url;     // 下载地址
        private long start;     // 开始长度
        private long end;       // 目标文件的总长度
        private long finished;  // 完成的长度
    
        public ThreadInfo() {
    
        }
    
        public ThreadInfo(int id, String url, long start, long end, long finished) {
            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 String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public long getStart() {
            return start;
        }
    
        public void setStart(long start) {
            this.start = start;
        }
    
        public long getEnd() {
            return end;
        }
    
        public void setEnd(long end) {
            this.end = end;
        }
    
        public long getFinished() {
            return finished;
        }
    
        public void setFinished(long finished) {
            this.finished = finished;
        }
    
        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer("ThreadInfo{");
            sb.append("id=").append(id);
            sb.append(", url='").append(url).append('\'');
            sb.append(", start='").append(start).append('\'');
            sb.append(", end='").append(end).append('\'');
            sb.append(", finish=").append(finished);
            sb.append('}');
            return sb.toString();
        }
    }
    
    
    FileInfo ,封装的下载任务信息
    public class FileInfo implements Serializable {
    
        private int id;             // ID
        private String url;         // 下载地址
        private String fileName;    // 文件名
        private long length;        // 文件大小
        private long finish;        // 完成的大小
    
        public FileInfo() {
    
        }
    
        public FileInfo(int id, String url, String fileName, long length, long finish) {
            this.id = id;
            this.url = url;
            this.fileName = fileName;
            this.length = length;
            this.finish = finish;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getFileName() {
            return fileName;
        }
    
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
    
        public long getLength() {
            return length;
        }
    
        public void setLength(long length) {
            this.length = length;
        }
    
        public long getFinish() {
            return finish;
        }
    
        public void setFinish(long finish) {
            this.finish = finish;
        }
    
        @Override
        public String toString() {
            final StringBuffer sb = new StringBuffer("FileInfo{");
            sb.append("id=").append(id);
            sb.append(", url='").append(url).append('\'');
            sb.append(", fileName='").append(fileName).append('\'');
            sb.append(", length=").append(length);
            sb.append(", finish=").append(finish);
            sb.append('}');
            return sb.toString();
        }
    }
    
    
    DownLoadService,开启一个服务下载任务
    public class DownLoadService extends Service {
    
    
        public static final String FILE_INFO = "fileinfo";
    
    
        /**
         * action
         */
        private static final int MSG_INIT = 0;                          //初始化
        public static final String ACTION_START = "ACTION_START";       //开始下载
        public static final String ACTION_PAUSE = "ACTION_PAUSE";       //暂停下载
        public static final String ACTION_FINISHED = "ACTION_FINISHED"; //结束下载
        public static final String ACTION_UPDATE = "ACTION_UPDATE";     //更新UI
    
        /**
         * 下载路径 这里保存在SD卡里面
         */
        public static final String DOWNLOAD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/downloads/";
    
        /**
         * 执行下载的那个任务 里面有线程 网络请求 数据保存
         */
        private DownTask mDownloadTask;
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // 获得Activity传来的参数
            if (ACTION_START.equals(intent.getAction())) {
                FileInfo fileInfo = (FileInfo) intent.getSerializableExtra(FILE_INFO);
                new InitThread(fileInfo).start();
            } else if (ACTION_PAUSE.equals(intent.getAction())) {
                FileInfo fileInfo = (FileInfo) intent.getSerializableExtra(FILE_INFO);
                if (mDownloadTask != null) {
                    mDownloadTask.isPause = true;
                }
            }
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            // 资源回收关闭
        }
    
        @SuppressLint("HandlerLeak")
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MSG_INIT) {
                    FileInfo fileinfo = (FileInfo) msg.obj;
                    // TODO 启动下载任务
                    mDownloadTask = new DownTask(DownLoadService.this, fileinfo);
                    mDownloadTask.startDownTask();
                }
            }
        };
    
    
        /**
         * 初始化子线程 这一步的作用是 获取下载目标的信息
         */
        private class InitThread extends Thread {
    
            private FileInfo tFileInfo;
    
            public InitThread(FileInfo tFileInfo) {
                this.tFileInfo = tFileInfo;
            }
    
            @Override
            public void run() {
                HttpURLConnection conn = null;
                RandomAccessFile raf = null;
                try {
                    //连接网络文件
                    URL url = new URL(tFileInfo.getUrl());
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(3000);
                    conn.setRequestMethod("GET");
                    int length = -1;
    
                    //获取目标文件长度
                    if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                        length = conn.getContentLength();
                    }
    
                    if (length < 0) {
                        return;
                    }
    
                    File dir = new File(DOWNLOAD_PATH);
                    if (!dir.exists()) {
                        dir.mkdir();
                    }
                    //在本地创建文件
                    File file = new File(dir, tFileInfo.getFileName());
                    raf = new RandomAccessFile(file, "rwd");
    
                    //设置本地文件长度
                    raf.setLength(length);
                    tFileInfo.setLength(length);
    
                    // 发消息
                    mHandler.obtainMessage(MSG_INIT, tFileInfo).sendToTarget();
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (conn != null && raf != null) {
                            raf.close();
                            conn.disconnect();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    
    
    DownTask,真正的下载任务在这里 ,当前类主要有两个任务 保存下载的数据,保存下载的进度
    public class DownTask {
    
        private Context mContext = null;
        private FileInfo mFileInfo = null;
        private ThreadDAOImpl mThreadDAOImpe = null;
        private long mFinished = 0;
        public boolean isPause = false;
        private static final String TAG = "DownTask";
    
    
        /**
         * 构造函数
         *
         * @param mContext  上下文
         * @param mFileInfo 下载详情
         */
        public DownTask(Context mContext, FileInfo mFileInfo) {
            this.mContext = mContext;
            this.mFileInfo = mFileInfo;
            mThreadDAOImpe = new ThreadDAOImpl(mContext);
        }
    
    
        /**
         * 开始任务 开启一个子线程去执行任务
         */
        public void startDownTask() {
            // 本地数据库获取到所有的下载信息
            List<ThreadInfo> threadInfos = mThreadDAOImpe.getThread(mFileInfo.getUrl());
            ThreadInfo info;
            if (threadInfos.size() == 0) {
                info = new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);
            } else {
                info = threadInfos.get(0);
            }
    
            Thread a = new DownloadThread(info);
            a.start();
        }
    
    
        /**
         * 这个是下载的线程
         */
        private class DownloadThread extends Thread {
    
            private ThreadInfo threadInfo = null;
    
            public DownloadThread(ThreadInfo threadInfo) {
                this.threadInfo = threadInfo;
            }
    
            @Override
            public void run() {
                //如果数据库中,不存在记录就要插入数据
                if (!mThreadDAOImpe.isExists(threadInfo.getUrl(), threadInfo.getId())) {
                    mThreadDAOImpe.insertThread(threadInfo);
                }
    
                HttpURLConnection connection = null;
                RandomAccessFile raf = null;
                InputStream is = null;
    
    
                try {
                    URL url = new URL(threadInfo.getUrl());
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setConnectTimeout(3000);
                    connection.setRequestMethod("GET");
    
                    //设置下载位置
                    long start = threadInfo.getStart() + threadInfo.getFinished();
                    Log.e(TAG, "run: 继续下载进度为" + start);
    
                    // TODO 重点 就在这里了 指定位置开始下载
                    connection.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
    
                    //设置文件写入位置
                    File file = new File(DownLoadService.DOWNLOAD_PATH, mFileInfo.getFileName());
                    raf = new RandomAccessFile(file, "rwd");
                    raf.seek(start);
    
                    //设置广播
                    Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                    //从上次停止的地方继续下载
                    mFinished += threadInfo.getFinished();
                    Log.e(TAG, "上次下载的进度:" + mFinished);
    
                    if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
                        is = connection.getInputStream();
                        byte[] buffer = new byte[4096];
                        int len = -1;
                        long time = System.currentTimeMillis();
    
                        // TODO 保存下载的数据
                        while ((len = is.read(buffer)) != -1) {
    
                            Log.i(TAG, "run: 一次数据读写 ");
    
                            //下载暂停时,保存进度
                            if (isPause) {
                                Log.e(TAG, "run: 进度为:" + mFinished);
                                mThreadDAOImpe.updateThread(mFileInfo.getUrl(), threadInfo.getId(), mFinished);
                                // TODO 暂停之后 直接返回 线程结束
                                return;
                            }
    
                            raf.write(buffer, 0, len);
                            mFinished += len;
    
                            if (System.currentTimeMillis() - time > 500) {
                                time = System.currentTimeMillis();
                                intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
                                Log.e(TAG, "run: 这里发送广播了" + mFinished + " -- " + mFileInfo.getLength());
                                mContext.sendBroadcast(intent);
                            }
                        }
    
                        intent.putExtra("finished", (long) 100);
                        mContext.sendBroadcast(intent);
                        mThreadDAOImpe.deleteThread(mFileInfo.getUrl(), mFileInfo.getId());
    
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e(TAG, "run: ");
                } finally {
                    try {
                        if (is != null)
                            is.close();
                        if (raf != null)
                            raf.close();
                        if (connection != null)
                            connection.disconnect();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    
    
    ThreadDAO数据库访问接口
    public interface ThreadDAO {
    
        /**
         * 插入线程信息
         *
         * @param threadInfo 线程信息
         */
        void insertThread(ThreadInfo threadInfo);
    
        /**
         * 删除线程信息
         *
         * @param url       地址
         * @param thread_id id
         */
        void deleteThread(String url, int thread_id);
    
        /**
         * /**
         * 更新线程信息
         *
         * @param url       地址
         * @param thread_id id
         * @param finished  完成进度
         */
        void updateThread(String url, int thread_id, long finished);
    
        /**
         * 查询文件的线程信息
         *
         * @param url 地址
         * @return 信息
         */
        List<ThreadInfo> getThread(String url);
    
        /**
         * 判断是否存在
         *
         * @param url       地址
         * @param thread_id id
         * @return 是否存在
         */
        boolean isExists(String url, int thread_id);
    
    }
    
    
    ThreadDAOImpl
    public class ThreadDAOImpl implements ThreadDAO{
    
    
        private static final String TAG = "ThreadDAOImpl";
    
        private MyDBHelper myDBHelper;
    
        public ThreadDAOImpl(Context context) {
            this.myDBHelper =  MyDBHelper.getInstance(context);
        }
    
        /**
         * 数据库插入数据
         * @param threadInfo 线程信息
         */
        @Override
        public void insertThread(ThreadInfo threadInfo) {
            Log.e("insertThread: ", "insertThread");
            SQLiteDatabase db = myDBHelper.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();
        }
    
        /**
         * 删除下载好的文件下载信息
         * @param url       地址
         * @param thread_id id
         */
        @Override
        public void deleteThread(String url, int thread_id) {
            Log.e("deleteThread: ", "deleteThread");
            SQLiteDatabase db = myDBHelper.getWritableDatabase();
            db.execSQL("delete from  thread_info where url = ? and thread_id= ?",
                    new Object[]{url, thread_id});
            db.close();
        }
    
        /**
         * 更新下载进度到数据库中
         * @param url       地址
         * @param thread_id id
         * @param finished  完成进度
         */
        @Override
        public void updateThread(String url, int thread_id, long finished) {
            Log.e("updateThread: ", "updateThread 更新的进度为+"+finished+" 跟新的id为"+thread_id+"-- url为:"+url);
            SQLiteDatabase db = myDBHelper.getWritableDatabase();
            String sql = "update thread_info set finished = "+finished+" where url = '"+url+"' and thread_id = "+thread_id+";";
            db.execSQL("update thread_info set finished = ?  where url = ? and thread_id = ?",
                    new Object[]{finished, url, thread_id});
    
            Log.e(TAG, "updateThread: ----[[:"+sql );
    //        db.execSQL(sql);
            db.close();
        }
    
        /**
         * 查询数据库中下载某个url的线程列表
         * @param url 地址
         * @return
         */
        @Override
        public List<ThreadInfo> getThread(String url) {
            Log.e("getThread: ", "getThread");
            List<ThreadInfo> list = new ArrayList<>();
            SQLiteDatabase db = myDBHelper.getWritableDatabase();
            Cursor cursor = db.rawQuery("select * from thread_info where url=?", new String[]{url});
            while (cursor.moveToNext()) {
                ThreadInfo thread = new ThreadInfo();
                thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
                thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
                thread.setStart(cursor.getLong(cursor.getColumnIndex("start")));
                thread.setEnd(cursor.getLong(cursor.getColumnIndex("end")));
                thread.setFinished(cursor.getLong(cursor.getColumnIndex("finished")));
                list.add(thread);
            }
            cursor.close();
            db.close();
            return list;
        }
    
        /**
         * 判断下载指定url的线程是否存在
         * @param url       地址
         * @param thread_id id
         * @return
         */
        @Override
        public boolean isExists(String url, int thread_id) {
            SQLiteDatabase db = myDBHelper.getWritableDatabase();
            Cursor cursor = db.rawQuery("select * from thread_info where url=? and thread_id = ?",
                    new String[]{url, String.valueOf(thread_id)});
            boolean isExist = cursor.moveToNext();
            cursor.close();
            db.close();
            Log.e(TAG, "isExists: " + isExist);
            return isExist;
        }
    
    }
    
    
    MyDBHelper 数据库
    public class MyDBHelper extends SQLiteOpenHelper {
    
    
        /**
         * 数据库的名字
         */
        private static final String DB_NAME = "download.db";
    
        /**
         * 创建表
         */
        private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement," +
                "thread_id integer,url text,start long,end long,finished long)";
    
        /**
         *
         */
        private static final String SQL_DROP = "drop table if exists thread_info";
    
        /**
         * 当前数据库的版本
         */
        private static final int VERSION = 1;
    
    
        private static MyDBHelper myDBHelper;
    
        private MyDBHelper(Context context) {
            super(context, DB_NAME, null, VERSION);
        }
    
        public static MyDBHelper getInstance(Context context) {
            if (myDBHelper == null) {
                myDBHelper = new MyDBHelper(context);
            }
            return myDBHelper;
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(SQL_CREATE);
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL(SQL_DROP);
            db.execSQL(SQL_CREATE);
        }
    }
    
    

    okhttp 网络请求

    implementation 'com.squareup.okhttp3:okhttp:4.4.0'
    
    代码实现
     private void downLoadFile() {
            try {
                if (file == null) {
                    file = new File(rootFile, name);
                    raf = new RandomAccessFile(file, "rwd");
                } else {
                    downLoadSize = file.length();
                    if (raf == null) {
                        raf = new RandomAccessFile(file, "rwd");
                    }
                    raf.seek(downLoadSize);
                }
                totalSize = getContentLength(path);
                if (downLoadSize == totalSize) {
                    //已经下载完成
                    return;
                }
    
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url(path).
                        addHeader("Range", "bytes=" + downLoadSize + "-" + totalSize).build();
                Response response = client.newCall(request).execute();
                InputStream ins = response.body().byteStream();
                int len = 0;
                byte[] by = new byte[1024];
                long endTime = System.currentTimeMillis();
                while ((len = ins.read(by)) != -1 && isDown) {
                    raf.write(by, 0, len);
                    downLoadSize += len;
                    if (System.currentTimeMillis() - endTime > 1000) {
                        final double dd = downLoadSize / (totalSize * 1.0);
                        DecimalFormat format = new DecimalFormat("#0.00");
                        String value = format.format((dd * 100)) + "%";
                        Log.i("tag", "==================" + value);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                progress.onProgress((int) (dd * 100));
                            }
                        });
                    }
                }
                response.close();
            } catch (Exception e) {
                e.getMessage();
            }
    
        }
    
        /**
         * 通过OkhttpClient获取文件的大小
         *
         * @param url
         * @return
         * @throws IOException
         */
        public long getContentLength(String url) throws IOException {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            Response response = client.newCall(request).execute();
            long length = response.body().contentLength();
            response.close();
            return length;
        }
    

    相关文章

      网友评论

          本文标题:Android-单线程断点续传

          本文链接:https://www.haomeiwen.com/subject/knkryktx.html