首页 > 其他分享 >不使用第三方框架编写的多线程断线续传功能

不使用第三方框架编写的多线程断线续传功能

时间:2023-06-02 17:33:58浏览次数:142  
标签:续传 void private key 断线 多线程 public 下载 String


一、背景

最近需要个断线续传功能,但是觉得一些框架不太适合,所以基于原理编写了一个多线程断线续传功能

支持技术分享,但是复制和转发我的博客时候请标明出处,谢谢 javascript:void(0)

二、断线续传的个人理解:

1、断线续传在个人理解,其实就是在出现正常下载流程之外的事情的时候,保存好当前文件下载的进度,然后点击继续下载的时候,从上次的下载进度继续进行下载。

2、如何从上次下载进度继续进行下载呢? 主要就是设置头部信息进行告知实现的

setRequestProperty("Range", "bytes=" + progress + "-" + total);//设置下载范围

三、主要功能有

1、支持多线程断线续传

2、支持回调事件拓展,使用泛型定义对象,支持更加灵活的去拓展对象

3、如果要下载的资源在要保存的文件夹中存在,那么会自动进行下载位置校准和下载

4、支持自定义资源请求的方式(GET和POST方式)和请求超时时间

5、我编不下了,如果你发现了就帮我写上去,谢谢....... 效果图如下

下载3只是装饰,你可以换个地址和修改一下MainActivity的按钮监控那块的代码,抄下载下载1和下载2的代码即可 

不使用第三方框架编写的多线程断线续传功能_泛型

 

不使用第三方框架编写的多线程断线续传功能_多线程_02

后面我会完善个功能,只要连上网络就进行检查,然后自动进行资源下载,如果有需要可以给我留言

四、直接上源码讲解

篇幅太长,贴不了那么多,只贴8点 代码下载地址为:点击下载 源码里面DownLoadTask构造函数里面有个地方写错了,写死成了GET方式,如果下载源码的要使用,可以复制下面的DownLoadTask源码进去覆盖掉就好了

1、多线程实例,主要的内容都在这里了

//执行下载的线程
public class DownLoadTask implements Runnable {
    private static final String TAG = "DownLoadTask";
    public static final int CACHE_SIZE = 4 * 1024;//缓冲区间,4应该足够了
    public static final int DEFAULT_TIME_OUT = 5000;//单位是毫秒,默认是5秒,支持自定义
    //线程安全的资源列表,key是文件名称,value是下载实例
    private static ConcurrentHashMap<String, DownLoadEntity> mResourceMap = new ConcurrentHashMap<String, DownLoadEntity>();

    /**
     * @Description 停止下载
     * [@author](\姚旭民
     * [@date]( 2018/11/20 16:37
     */
    public static void stop(String key) throws NullPointerException {
        try {
            if (key == null)
                throw new NullPointerException();
            mResourceMap.get(key).setStop(true);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * [@param key 文件凭证
     * @Description 资源删除
     * @author 姚旭民
     * @date 2018/11/20 17:22
     */
    public static void remove(String key) throws NullPointerException {
        if (key == null || mResourceMap.get(key) == null)
            throw new NullPointerException("参数为null或者下载数据不存在");
        mResourceMap.get(key).setDelete(true);
    }

    //下载实体
    DownLoadEntity mDownLoadEntity;
    //回调对象,只要进行实现,就可以获得各种事件的观察回调,IDownLoadCallBack源码在 第2点 有贴出来
    IDownLoadCallBack mCallBack;
    //传输方式,是一个枚举类型,支持自定义传输
    TransmissionType mType;
    //下载的超时时间
    int mTimeout;

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack) {
        this(downLoadEntity, mCallBack, TransmissionType.TYPE_GET);
    }

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type) {
        this(downLoadEntity, mCallBack, type, DEFAULT_TIME_OUT);
    }

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type, int timeout) {
        this.mDownLoadEntity = downLoadEntity;
        this.mCallBack = mCallBack;
        this.mType = type;
        this.mTimeout = timeout;
        //数据存储
        mResourceMap.put(downLoadEntity.getKey(), downLoadEntity);
        Log.v(TAG, "存放数据进入键值对,key:" + downLoadEntity.getKey() + ",downLoadEntity:" + downLoadEntity);
    }

    @Override
    public void run() {
        //下载路径
        String downUrl = mDownLoadEntity.getDownUrl();
        //保存路径
        String savePath = mDownLoadEntity.getSavePath();
        //已经下载的进度
        long progress = mDownLoadEntity.getProgress();//已经下载好的长度
        long total = mDownLoadEntity.getTotal();//文件的总长度
        String key = mDownLoadEntity.getKey();
        HttpURLConnection connection = null;
        //有人可能觉得NIO 的 FileChannel 也可以的话,那么你也可以替换掉
        RandomAccessFile randomAccessFile = null;
        try {
            //设置文件写入位置
            File file = new File(savePath);
            //父类文件夹是否存在
            File fileParent = file.getParentFile();
            if (!fileParent.exists()) {//如果父类文件夹不存在,即创建文件夹
                Log.v(TAG, "父类文件夹:" + fileParent.getPath() + ",不存在,开始创建");
                fileParent.mkdirs();
            }

            if (file != null) {//这一步是针对于断线续传的文件,用于比对数据库和真实的数据,避免出现误差
                long fileSize = file.length();
                if (progress != fileSize) {//如果文件有问题,以实际下载的文件大小为准
                    Log.v(TAG, "文件传输节点不一致,开始修复传数据节点");
                    progress = fileSize;
                    mDownLoadEntity.setProgress(progress);
                }
            }

            int precent = (int) ((float) progress / (float) total * 100);
            //开始下载之前先回调开始下载的进度
            mCallBack.onNext(key, precent);

            URL url = new URL(downUrl);
            connection = (HttpURLConnection) url.openConnection();
            //请求方式默认为GET
            connection.setRequestMethod(mType.getType());
            //超时时间
            connection.setConnectTimeout(mTimeout);
            //从上次下载完成的地方下载
            //设置下载位置(从服务器上取要下载文件的某一段)
            connection.setRequestProperty("Range", "bytes=" + progress + "-" + total);//设置下载范围

            randomAccessFile = new RandomAccessFile(file, "rwd");
            //从文件的某一位置开始写入
            randomAccessFile.seek(progress);
            if (connection.getResponseCode() == 206) {//文件部分下载,返回码为206
                InputStream is = connection.getInputStream();
                byte[] buffer = new byte[CACHE_SIZE];
                //接收到的资源大小
                int len;
                while ((len = is.read(buffer)) != -1) {
                    //写入文件
                    randomAccessFile.write(buffer, 0, len);
                    progress += len;
                    precent = (int) ((float) progress / (float) total * 100);
                    //更新进度回调
                    mCallBack.onNext(key, precent);
                    //停止下载
                    if (mDownLoadEntity.isStop()) {
                        mDownLoadEntity.setProgress(progress);
                        mCallBack.onPause(mDownLoadEntity, key, precent, progress, total);
                        return;
                    }
                    //取消下载
                    if (mDownLoadEntity.isDelete()) {
                        mResourceMap.remove(key);
                        //文件删除
                        file.delete();
                        mCallBack.onDelete(mDownLoadEntity, key);
                        return;
                    }
                }
            }

            //资源删除
            mResourceMap.remove(mDownLoadEntity.getFileName());
            //下载完成
            mCallBack.onSuccess(mDownLoadEntity, key);
        } catch (Exception e) {
            //资源删除
            mResourceMap.remove(mDownLoadEntity.getFileName());
            mDownLoadEntity.setProgress(progress);
            //防止意外
            mDownLoadEntity.setStop(false);
            //失败原因回调
            mCallBack.onFail(mDownLoadEntity, key, e.toString());

            StringBuffer sb = new StringBuffer();
            Writer writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            e.printStackTrace(printWriter);
            Throwable cause = e.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.close();
            //异常的详细内容
            String result = writer.toString();
            Log.e(TAG, result);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                StringBuffer sb = new StringBuffer();
                Writer writer = new StringWriter();
                PrintWriter printWriter = new PrintWriter(writer);
                e.printStackTrace(printWriter);
                Throwable cause = e.getCause();
                while (cause != null) {
                    cause.printStackTrace(printWriter);
                    cause = cause.getCause();
                }
                printWriter.close();
                //异常的详细内容
                String result = writer.toString();
                Log.e(TAG, result);
            }
        }
    }
}

2、IDownLoadCallBack源码,这里的泛型主要是因为和公司一些业务有关,这里没有列出来,这里的泛型其实可以去掉的,因为基本这里没什么用的,T 都改成 DownLoadEntity实例即可

public interface IDownLoadCallBack<T> {
    /**
     * @param key     下载的文件的标识,主要用于显示的时候辨别是哪个文件在操作,由使用的人去定义
     * @param precent 已经下载的百分比 取值区间为 [0,100]
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onNext(String key, int precent);

    /**
     * @param t            下载的文件的实体封装类
     * @param key          下载的文件的标识,主要用于显示的时候辨别是哪个文件在操作,由使用的人去定义
     * @param precent      已经下载的百分比
     * @param downLoadSize 已经下载的长度
     * @param total        资源的总长度
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 10:48
     */
    public abstract void onPause(T t, String key, int precent, long downLoadSize, long total);

    /**
     * @Description 删除文件回调
     * @author 姚旭民
     * @date 2018/11/22 10:47
     *
     * @param t 操作的下载对象
     * @param key 文件凭证
     */
    public abstract void onDelete(T t, String key);

    /**
     * @param t   自定义的值
     * @param key 下载的文件的标识,主要用于显示的时候辨别是哪个文件在操作,由使用的人去定义
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onSuccess(T t, String key);

    /**
     * @param t      自定义的值
     * @param key    下载的文件的标识,主要用于显示的时候辨别是哪个文件在操作,由使用的人去定义
     * @param reason 失败原因
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onFail(T t, String key, String reason);

3、IDownLoadCallBack包装类继承,包装类用于包装泛型对象,其实这一步可以不要的,只是有点别的考虑,所以这样写

/**
 * @Description 包装类
 * @author 姚旭民
 * @date 2018/11/20 13:57
 */
public interface IResumeCallBack extends IDownLoadCallBack<ResumeEntity> {
}

4、ResumeEntity对象源码主要继承了DownLoadEntity(第5点),其他没什么的

public class ResumeEntity extends DownLoadEntity {
    public static enum STATUS {
        FAIL(-1),//下载失败
        DOWNLOAD(0),//下载中
        SUCCESS(1);//下载成功,可以使用
        private int value;

        private STATUS(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    ResumeEntity(builder builder) {
        this.fileName = builder.fileName;
        this.downUrl = builder.downUrl;
        this.savePath = builder.savePath;
        this.total = builder.total;
        this.progress = builder.progress;
        this.status = builder.status;
        this.key = builder.key;
    }

    //链式编程,防止对象不一致,用static修饰,避免被保留强引用
    public static class builder {
        private String fileName;
        private String downUrl;
        private String savePath;
        private long total;
        private long progress;
        private int status;
        private boolean stop;
        private String key;

        public builder fileName(String fileName) {
            this.fileName = fileName;
            return this;
        }

        public builder downUrl(String downUrl) {
            this.downUrl = downUrl;
            return this;
        }

        public builder savePath(String savePath) {
            this.savePath = savePath;
            return this;
        }

        public builder total(long total) {
            this.total = total;
            return this;
        }

        public builder progress(long progress) {
            this.progress = progress;
            return this;
        }

        public builder status(int status) {
            this.status = status;
            return this;
        }

        public builder stop(boolean stop) {
            this.stop = stop;
            return this;
        }

        public builder key(String key) {
            this.key = key;
            return this;
        }

        public ResumeEntity builder() {
            return new ResumeEntity(this);
        }
    }

    @Override
    public String toString() {
        return "{" +
                "fileName='" + fileName + '\'' +
                ", downUrl='" + downUrl + '\'' +
                ", savePath='" + savePath + '\'' +
                ", total=" + total +
                ", progress=" + progress +
                ", status=" + status +
                ", stop=" + stop +
                ", key='" + key + '\'' +
                '}';
    }
}

5、DownLoadEntity源码区域

public class DownLoadEntity {
    //资源文件的名称
    protected String fileName;
    //资源文件的下载路径
    protected String downUrl;
    //资源文件的保存完整路径
    protected String savePath;
    //下载的资源的总长度
    protected long total;
    //已经下载的进度
    protected long progress;
    //资源的状态 //下载的状况 1为下载成功,0为可下载, -1为下载失败 默认为0
    protected int status;
    //是否暂停下载 true为暂停下载, false代表可以下载, 默认为false
    protected boolean stop;
    //下载的文件的标识,让使用者更加灵活的去定义如何识别正在下载的文件
    protected String key;
    //是否删除下载的文件
    protected boolean delete;

    //这里是各种set和get,不花费篇幅粘贴了,直接用工具生成就好了
}

6、IDownLoadCallBack的实现类,我是不想每次都创建一个匿名类了,太长了也繁琐,我直接用activity去实现IDownLoadCallBack,感觉也挺好的,这里是随便写的activity,主要用来测试的,UI界面源码在第7点

public class MainActivity extends AppCompatActivity implements View.OnClickListener, IResumeCallBack, INetCallBack {
    private static final String TAG = "MainActivity";
    //数据库操作辅助类
    private ResumeDbHelper mHelper = ResumeDbHelper.getInstance();
    private ResumeService mResumeService = ResumeService.getInstance();
    private MainActivity mInstance = this;

    private Button downloadBtn1, downloadBtn2, downloadBtn3;
    private Button pauseBtn1, pauseBtn2, pauseBtn3;
    private Button cancelBtn1, cancelBtn2, cancelBtn3;
    private ProgressBar mProgress1, mProgress2, mProgress3;
    private String url1 = "http://192.168.1.103/2.bmp";
    private String url2 = "http://192.168.1.103/testzip.zip";
    private String url3 = "http://192.168.1.103/photo.png";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        try {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            NetReceiver.setCallBack(this);
            downloadBtn1 = bindView(R.id.main_btn_down1);
            downloadBtn2 = bindView(R.id.main_btn_down2);
            downloadBtn3 = bindView(R.id.main_btn_down3);

            pauseBtn1 = bindView(R.id.main_btn_pause1);
            pauseBtn2 = bindView(R.id.main_btn_pause2);
            pauseBtn3 = bindView(R.id.main_btn_pause3);

            cancelBtn1 = bindView(R.id.main_btn_cancel1);
            cancelBtn2 = bindView(R.id.main_btn_cancel2);
            cancelBtn3 = bindView(R.id.main_btn_cancel3);

            mProgress1 = bindView(R.id.main_progress1);
            mProgress2 = bindView(R.id.main_progress2);
            mProgress3 = bindView(R.id.main_progress3);

            downloadBtn1.setOnClickListener(this);
            downloadBtn2.setOnClickListener(this);
            downloadBtn3.setOnClickListener(this);

            pauseBtn1.setOnClickListener(this);
            pauseBtn2.setOnClickListener(this);
            pauseBtn3.setOnClickListener(this);

            cancelBtn1.setOnClickListener(this);
            cancelBtn2.setOnClickListener(this);
            cancelBtn3.setOnClickListener(this);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    @Override
    public void onClick(View v) {
        try {
            switch (v.getId()) {
                case R.id.main_btn_down1:
                    Log.d(TAG, "点击了下载1,url1:" + url1);
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            mResumeService.download("1", url1, FileConts.IMG_PATH, mInstance);
                        }
                    });
                    break;
                case R.id.main_btn_down2:
                    Log.d(TAG, "点击了下载2");
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            mResumeService.download("2", url2, FileConts.IMG_PATH, mInstance);
                        }
                    });
                    break;
                case R.id.main_btn_down3:
                    Log.d(TAG, "点击了下载3");

                    break;

                case R.id.main_btn_pause1:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "点击暂停1");
                            ResumeService.getInstance().stop("1");
                        }
                    });
                    break;
                case R.id.main_btn_pause2:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "点击暂停2");
                            ResumeService.getInstance().stop("2");
                        }
                    });
                    break;
                case R.id.main_btn_pause3:
//                ResumeService.getInstance().cancel(url3);
                    break;

                case R.id.main_btn_cancel1:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            ResumeService.getInstance().remove(url1, "1");
                        }
                    });
                    break;
                case R.id.main_btn_cancel2:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            ResumeService.getInstance().remove(url2, "2");
                        }
                    });
                    break;
                case R.id.main_btn_cancel3:
//                ResumeService.getInstance().cancel(url3);
                    break;
            }
        } catch (Exception e) {
            StringBuffer sb = new StringBuffer();
            Writer writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            e.printStackTrace(printWriter);
            Throwable cause = e.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.close();
            //异常的详细内容
            String result = writer.toString();
            Log.e(TAG, result);
        }
    }

    private <T extends View> T bindView(@IdRes int id) {
        View viewById = findViewById(id);
        return (T) viewById;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "onDestroy");
    }

//IDownLoadCallBack 接口 的各种回调 事件 开始
    //下载的进度回调
    @Override
    public void onNext(String key, int precent) {
        if ("1".equals(key)) {
            mProgress1.setMax(100);
            mProgress1.setProgress(precent);
        } else if ("2".equals(key)) {
            mProgress2.setMax(100);
            mProgress2.setProgress(precent);
        }
    }

    //下载的停止回调,同时会将暂停状态保存进入数据库
    @Override
    public void onPause(ResumeEntity resumeEntity, String key, int precent, long downLoadSize, long total) {
        Log.v(TAG, "onNext| 下载 暂停 回调方法,resumeEntity:" + resumeEntity);
        mHelper.update(resumeEntity);
    }

    //删除文件回调
    @Override
    public void onDelete(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onDelete| 下载 删除 回调方法,resumeEntity:" + resumeEntity);
        mHelper.delete(resumeEntity.getFileName());
    }

    //下载成功的回调
    @Override
    public void onSuccess(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onNext| 下载 成功 回调方法,resumeEntity:" + resumeEntity);
        resumeEntity.setStatus(ResumeEntity.STATUS.SUCCESS.getValue());
        mHelper.update(resumeEntity);
    }

    //下载失败的回调
    @Override
    public void onFail(ResumeEntity resumeEntity, String key, String reason) {
        Log.v(TAG, "onFail| 下载 失败 回调方法,resumeEntity:" + resumeEntity + ",reason:" + reason);
        resumeEntity.setStatus(ResumeEntity.STATUS.FAIL.getValue());
        mHelper.update(resumeEntity);
    }
    //IDownLoadCallBack 接口 的各种回调 事件 结束

    //网络状态回调区域,这里是我用来接着编写网络重连之后继续下载的东西的
    public void onStatusChange(String msg) {
        Log.v(TAG, "onStatusChange| 网络状态回调,内容为:" + msg);
    }
}

7、UI界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.yxm.resume.activity.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress1"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:progressDrawable="@drawable/progressbar" /> 进度条样式在第8点

        <Button
            android:id="@+id/main_btn_down1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下载1" />

        <Button
            android:id="@+id/main_btn_pause1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暂停1" />

        <Button
            android:id="@+id/main_btn_cancel1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消1" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress2"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <Button
            android:id="@+id/main_btn_down2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下载2" />

        <Button
            android:id="@+id/main_btn_pause2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暂停2" />

        <Button
            android:id="@+id/main_btn_cancel2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消2" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress3"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <Button
            android:id="@+id/main_btn_down3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下载3" />

        <Button
            android:id="@+id/main_btn_pause3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暂停3" />

        <Button
            android:id="@+id/main_btn_cancel3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消3" />
    </LinearLayout>
</LinearLayout>

8、进度条样式

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">

        <shape>

            <corners android:radius="5dip" />

            <gradient
                android:angle="0"
                android:centerColor="#ff5a5d5a"
                android:centerY="0.75"
                android:endColor="#ff747674"
                android:startColor="#ff9d9e9d" />
        </shape>
    </item>

    <item android:id="@android:id/secondaryProgress">

        <clip>

            <shape>

                <corners android:radius="5dip" />

                <gradient
                    android:angle="0"
                    android:centerColor="#80ffb600"
                    android:centerY="0.75"
                    android:endColor="#a0ffcb00"
                    android:startColor="#80ffd300" />
            </shape>
        </clip>
    </item>

    <item android:id="@android:id/progress">

        <clip>

            <shape>

                <corners android:radius="5dip" />

                <gradient
                    android:angle="0"
                    android:endColor="#8000ff00"
                    android:startColor="#80ff0000" />
            </shape>
        </clip>
    </item>

</layer-list>

标签:续传,void,private,key,断线,多线程,public,下载,String
From: https://blog.51cto.com/u_16145034/6404306

相关文章

  • SpringBoot大文件分片上传/多线程上传
    ​ 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数下面直接贴代码吧,一些难懂的我大部分都加上注释了:上传文件实体类:看得出来,实体类中已经有很多我们需要的功能了,还有实用的属性。如MD5秒传的信息。pub......
  • SpringMVC大文件分片上传/多线程上传
    ​ javaweb上传文件上传文件的jsp中的部分上传文件同样可以使用form表单向后端发请求,也可以使用ajax向后端发请求    1.通过form表单向后端发送请求         <formid="postForm"action="${pageContext.request.contextPath}/UploadServlet"method="post"e......
  • PHP大文件分片上传/多线程上传
    ​ PHP用超级全局变量数组$_FILES来记录文件上传相关信息的。1.file_uploads=on/off 是否允许通过http方式上传文件2.max_execution_time=30 允许脚本最大执行时间,超过这个时间就会报错3.memory_limit=50M 设置脚本可以分配的最大内存量,防止失控脚本占用过多内存,此......
  • .NET大文件分片上传/多线程上传
    ​ 一、概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载。在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了。一般断点下载时才用到Range和Content-Range实体头。HTTP协议本身不支持断点上传,需要自己实现。 二、Range  用于请求头......
  • C# Web大文件分片上传/多线程上传
    ​ 以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传  可以带参数        [HttpPost("upload")]        publicJsonResultuploadProject(I......
  • C#.NET大文件分片上传/多线程上传
    ​IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头。 一. 两个必要响应头Accept-Ranges、ETag        客户端每次提交下载请求时,服务端都要添加这两个响应头,以保证客户端和服务端将此下载识别为可以断点续传......
  • 项目场景:python pyqt5 threading 多线程 数据混乱
    项目场景:pythonpyqt5threading多线程数据混乱根据配置文件多线程调用接口#问题描述:参考http://www.starky.ltd/2019/09/23/pro-python-concurrency-with-multi-threading/多线程调用接口,取得数据回调修改页面上的值发生混乱不准#原因分析:运行线程数量过多没有顺序......
  • Java多线程 有ABC 3 个线程,线程C需要等待线程AB执行完成才能执行的实现方式
    https://blog.csdn.net/qq_42337969/article/details/118073089?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-1-118073089-blog-52536071.235^v36^pc_relevant_default_base3&depth_1-utm_sour......
  • 如何正确在多线程环境下更新UI_使用Platform的runLater方法
    如何正确在多线程环境下更新UI_使用Platform的runLater方法许多UI控件都提供了各种修改方法,比如我们可以修改Label上面的文本,进度条ProgressBar的进度。但我们必须保证修改UI的线程是JavaFX的UI线程,如果不是则会出现异常。那么我们如何在另一个线程中修改JavaFX的UI呢?......
  • Java多线程三(线程池执行完后再执行主线程)CountDownLatch
      我们在开发多线程的时候,有两种情况一种是我们处理好后,不用管结果。比如我需要查询某些数据然后存在数据库里。还有一种就是查询好数据(通过线程池),然后导出数据。这个就比较麻烦。因为我们要将数据通过多线程处理后,返回一个统一的结果。(由于多线程是在不同的时候执行数据),假如执......