Multithreaded disconnected continuation function written without a third-party framework

1. Background

Recently, we need a disconnect continuation function, but feel that some frames are not suitable, so we write a multi-threaded disconnect continuation function based on principle

Support technology sharing, but please indicate the source when copying and forwarding my blog. Thank you https://my.oschina.net/grkj/blog/2907188

2. Personal understanding of disconnected biographies:

1. Discontinuous upload is understood by the individual that when something happens outside of the normal download process, save the progress of the current file download, and then click Continue Download to continue downloading from the last download progress.

2. How can I continue downloading from the last download progress? The main purpose is to set the header information to inform

setRequestProperty("Range", "bytes=" + progress + "-" + total);//Set Download Scope

3. The main functions are

1. Support multi-threaded disconnected transmission

2. Support callback event expansion, use generics to define objects, support more flexibility to expand objects

3. If the download resource exists in the folder to be saved, the download location will be calibrated and downloaded automatically

4. Support for custom resource requests (GET and POST) and request timeout

5. I can't compile it anymore. If you find it, write it for me. Thank you...

Later, I will complete a function, as long as I connect to the network to check, and then automatically download resources

4. Direct Source Explanation

It's too long to stick so much, just 7 o'clock The code download address is: Click Download There is a mistake in the DownLoadTask constructor in the source code, it is written dead as GET mode. If you want to use the download source code, you can copy the DownLoadTask source code below to overwrite it.

1. Multi-threaded instance, the main content is here

//Threads to perform Downloads
public class DownLoadTask implements Runnable {
    private static final String TAG = "DownLoadTask";
    public static final int CACHE_SIZE = 4 * 1024;//Buffer interval, 4 should be sufficient
    public static final int DEFAULT_TIME_OUT = 5000;//Unit is milliseconds, default is 5 seconds, support customization
    //Thread-safe resource list, key is file name, value is download instance
    private static ConcurrentHashMap<String, DownLoadEntity> mResourceMap = new ConcurrentHashMap<String, DownLoadEntity>();

    /**
     * @Description Stop Downloading
     * [@author](https://my.oschina.net/arthor) Yao Xumin
     * [@date](https://my.oschina.net/u/2504391) 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](https://my.oschina.net/u/2303379) key Document credentials
     * @Description Resource Delete
     * @author Yao Xumin
     * @date 2018/11/20 17:22
     */
    public static void remove(String key) throws NullPointerException {
        if (key == null || mResourceMap.get(key) == null)
            throw new NullPointerException("The parameter is null Or the download data does not exist");
        mResourceMap.get(key).setDelete(true);
    }

    //Download Entities
    DownLoadEntity mDownLoadEntity;
    //Callback object, as long as it is implemented, you can get observation callbacks for various events, IDownLoadCallBack source is posted at point 2
    IDownLoadCallBack mCallBack;
    //Transport mode, an enumeration type, supports custom transport
    TransmissionType mType;
    //Download timeout
    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;
        //data storage
        mResourceMap.put(downLoadEntity.getKey(), downLoadEntity);
        Log.v(TAG, "Store data into key-value pairs, key:" + downLoadEntity.getKey() + ",downLoadEntity:" + downLoadEntity);
    }

    @Override
    public void run() {
        //Download Path
        String downUrl = mDownLoadEntity.getDownUrl();
        //Save Path
        String savePath = mDownLoadEntity.getSavePath();
        //Progress Downloaded
        long progress = mDownLoadEntity.getProgress();//Downloaded Length
        long total = mDownLoadEntity.getTotal();//Total file length
        String key = mDownLoadEntity.getKey();
        HttpURLConnection connection = null;
        //If someone might think NIO's FileChannel is okay, then you can replace it
        RandomAccessFile randomAccessFile = null;
        try {
            //Set File Write Location
            File file = new File(savePath);
            //Does the parent folder exist
            File fileParent = file.getParentFile();
            if (!fileParent.exists()) {//Create a folder if the parent folder does not exist
                Log.v(TAG, "Parent folder:" + fileParent.getPath() + ",Does not exist, start creating");
                fileParent.mkdirs();
            }

            if (file != null) {//This step is for disconnected files, used to compare databases with real data, to avoid errors
                long fileSize = file.length();
                if (progress != fileSize) {//If there is a problem with the file, the actual downloaded file size will prevail
                    Log.v(TAG, "File transfer node inconsistency, start repairing data transfer node");
                    progress = fileSize;
                    mDownLoadEntity.setProgress(progress);
                }
            }

            int precent = (int) ((float) progress / (float) total * 100);
            //Callback the progress of starting the download before starting the download
            mCallBack.onNext(key, precent);

            URL url = new URL(downUrl);
            connection = (HttpURLConnection) url.openConnection();
            //Request method defaults to GET
            connection.setRequestMethod(mType.getType());
            //timeout
            connection.setConnectTimeout(mTimeout);
            //Download from the place where the last download was completed
            //Set the download location (take a section of the file you want to download from the server)
            connection.setRequestProperty("Range", "bytes=" + progress + "-" + total);//Set Download Scope

            randomAccessFile = new RandomAccessFile(file, "rwd");
            //Write from a location in the file
            randomAccessFile.seek(progress);
            if (connection.getResponseCode() == 206) {//Partial file download with return code 206
                InputStream is = connection.getInputStream();
                byte[] buffer = new byte[CACHE_SIZE];
                //Received resource size
                int len;
                while ((len = is.read(buffer)) != -1) {
                    //write file
                    randomAccessFile.write(buffer, 0, len);
                    progress += len;
                    precent = (int) ((float) progress / (float) total * 100);
                    //Update Progress Callback
                    mCallBack.onNext(key, precent);
                    //Stop Downloading
                    if (mDownLoadEntity.isStop()) {
                        mDownLoadEntity.setProgress(progress);
                        mCallBack.onPause(mDownLoadEntity, key, precent, progress, total);
                        return;
                    }
                    //Cancel Download
                    if (mDownLoadEntity.isDelete()) {
                        mResourceMap.remove(key);
                        //File Delete
                        file.delete();
                        mCallBack.onDelete(mDownLoadEntity, key);
                        return;
                    }
                }
            }

            //Resource Delete
            mResourceMap.remove(mDownLoadEntity.getFileName());
            //Download complete
            mCallBack.onSuccess(mDownLoadEntity, key);
        } catch (Exception e) {
            //Resource Delete
            mResourceMap.remove(mDownLoadEntity.getFileName());
            mDownLoadEntity.setProgress(progress);
            //Prevent accidents
            mDownLoadEntity.setStop(false);
            //Failure Reason Callback
            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();
            //Details of the exception
            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();
                //Details of the exception
                String result = writer.toString();
                Log.e(TAG, result);
            }
        }
    }
}

2. IDownLoadCallBack source code, the main design is implemented here, there are various event callbacks, if not appropriate, then inheritance will continue to write good

public interface IDownLoadCallBack<T> {
    /**
     * @param key     Identification of the downloaded file, used primarily to identify which file is being operated on while displaying, defined by the person who is using it
     * @param precent Percentage downloaded ranges from [0,100]
     * @Description
     * @author Yao Xumin
     * @date 2018/11/20 9:46
     */
    public abstract void onNext(String key, int precent);

    /**
     * @param t            Entity Encapsulation Class for Downloaded Files
     * @param key          Identification of the downloaded file, used primarily to identify which file is being operated on while displaying, defined by the person who is using it
     * @param precent      Percentage of Downloads
     * @param downLoadSize Downloaded Length
     * @param total        Total length of resource
     * @Description
     * @author Yao Xumin
     * @date 2018/11/20 10:48
     */
    public abstract void onPause(T t, String key, int precent, long downLoadSize, long total);

    /**
     * @Description Delete File Callback
     * @author Yao Xumin
     * @date 2018/11/22 10:47
     *
     * @param t Download object for operation
     * @param key Document credentials
     */
    public abstract void onDelete(T t, String key);

    /**
     * @param t   Custom Value
     * @param key Identification of the downloaded file, used primarily to identify which file is being operated on while displaying, defined by the person who is using it
     * @Description
     * @author Yao Xumin
     * @date 2018/11/20 9:46
     */
    public abstract void onSuccess(T t, String key);

    /**
     * @param t      Custom Value
     * @param key    Identification of the downloaded file, used primarily to identify which file is being operated on while displaying, defined by the person who is using it
     * @param reason Reasons for failure
     * @Description
     * @author Yao Xumin
     * @date 2018/11/20 9:46
     */
    public abstract void onFail(T t, String key, String reason);

3. IDownLoadCallBack wrapper class inheritance, wrapper class used to wrap generic objects

/**
 * @Description Packaging Class
 * @author Yao Xumin
 * @date 2018/11/20 13:57
 */
public interface IResumeCallBack extends IDownLoadCallBack<ResumeEntity> {
}

4. ResumeEntity object source mainly inherits DownLoadEntity (point 5), nothing else

public class ResumeEntity extends DownLoadEntity {
    public static enum STATUS {
        FAIL(-1),//Download failed
        DOWNLOAD(0),//Downloading
        SUCCESS(1);//Download successful, you can use
        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;
    }

    //Chain programming to prevent object inconsistencies, decorate with static, avoid strong references
    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 Source Area

public class DownLoadEntity {
    //Name of resource file
    protected String fileName;
    //Download path of resource file
    protected String downUrl;
    //Full path to save resource file
    protected String savePath;
    //Total length of downloaded resources
    protected long total;
    //Progress Downloaded
    protected long progress;
    //Status of the resource//Download Status 1 is download successful, 0 is downloadable, and -1 is download failure default to 0
    protected int status;
    //Whether to pause the download true is to pause the download, false means it can be downloaded, default is false
    protected boolean stop;
    //Identification of downloaded files, allowing users more flexibility in defining how to identify downloaded files
    protected String key;
    //Do you want to delete the downloaded files?
    protected boolean delete;

    //Here are set tings and get, pasted without space, just built with tools
}

6. The implementation class of IDownLoadCallBack. I don't want to create an anonymous class every time. It's too long and tedious. I use activity directly to implement IDownLoadCallBack. It feels good. This is a casual activity, mainly for testing. UI source code is at point 7.

public class MainActivity extends AppCompatActivity implements View.OnClickListener, IResumeCallBack, INetCallBack {
    private static final String TAG = "MainActivity";
    //Database Operations Auxiliary Class
    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, "Click Download 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, "Click Download 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, "Click Download 3");

                    break;

                case R.id.main_btn_pause1:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "Click Pause 1");
                            ResumeService.getInstance().stop("1");
                        }
                    });
                    break;
                case R.id.main_btn_pause2:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "Click Pause 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();
            //Details of the exception
            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");
    }

//Start of various callback events for IDownLoadCallBack interface
    //Progress callback for download
    @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);
        }
    }

    //Stop callback for download while saving pause state to database
    @Override
    public void onPause(ResumeEntity resumeEntity, String key, int precent, long downLoadSize, long total) {
        Log.v(TAG, "onNext| Download Pause Callback Method,resumeEntity:" + resumeEntity);
        mHelper.update(resumeEntity);
    }

    //Delete File Callback
    @Override
    public void onDelete(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onDelete| Download the Delete Callback method, resumeEntity:" + resumeEntity);
        mHelper.delete(resumeEntity.getFileName());
    }

    //Download successful callbacks
    @Override
    public void onSuccess(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onNext| Download Successful Callback Method,resumeEntity:" + resumeEntity);
        resumeEntity.setStatus(ResumeEntity.STATUS.SUCCESS.getValue());
        mHelper.update(resumeEntity);
    }

    //Callback for failed download
    @Override
    public void onFail(ResumeEntity resumeEntity, String key, String reason) {
        Log.v(TAG, "onFail| Download Failure Callback Method,resumeEntity:" + resumeEntity + ",reason:" + reason);
        resumeEntity.setStatus(ResumeEntity.STATUS.FAIL.getValue());
        mHelper.update(resumeEntity);
    }
    //End of various callback events for IDownLoadCallBack interface

    //Network Status Callback Area, here's what I use to continue downloading after I write a network reconnection
    public void onStatusChange(String msg) {
        Log.v(TAG, "onStatusChange| Network state callback for:" + msg);
    }
}

Keywords: Mobile network Database Programming

Added by sarmad_m_azad on Sat, 18 May 2019 20:33:14 +0300