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); } }