Android multi-task library download using HttpURLConnection for network transmission, the use of multi-thread for multi-task management. Can real-time update download progress, speed, download status, remaining time estimates.

1. Define model (DownloadInfoModel) for the downloaded data and status.

The status includes wait status, download status, download success status, download failure status, and task removal status.

public class DownloadInfoModel {
    public static final int DOWNLOADING_STATUS = 0;// Download status
    public static final int WAIT_STATUS = 1;// Wait state
    public static final int DOWNLOAD_FAILD_STATUS = 2;// Failed to download
    public static final int DOWNLOAD_SUCCESS_STATUS = 3;// Download succeeded status
    public static final int DOWNLOAD_REMOVE_STATUS = 4;// Delete the download
    private String downloadId = "";// Download the task id
    private String remoteUrl = "";// Remote download address
    private String savePath = "";// Save path folder
    private String fileName = "";// Save the file name (not including the extension)
    private String fileExtname = "";// The extension of the saved file
    private DownloadCallback downloadCallback;

    private int percentage = 0;// Download progress 0-100
    private long speed = 0;// Download speed byte/s
    private long lestTime = 0;// The remaining time
    private long len = 0;//
    private long fileLength = 0;// File size
    private int status = WAIT_STATUS;// Download status

    // Omit the getter and setter here
}
Copy the code

2. Define an interface for downloading progress callback (DownloadCallback).

See the comments for a detailed description of callbacks.

public interface DownloadCallback {
    /** * Downloading * <p> * // *@paramLen Current file size * // *@paramFileLength Total file size * // *@paramRemoteUrl Download path * // *@paramFileName indicates the fileName * // *@paramPercentage Indicates the percentage of downloads (0-100) * // *@paramSpeed Download speed * // *@paramLestTime Remaining time */
    void onDownloading(DownloadInfoModel downloadInfoModel);

    /** * start downloading **@param downloadInfoModel
     */
    void onStartDownload(DownloadInfoModel downloadInfoModel);

    /** * Download end call interface **@param downloadInfoModel
     */
    void onFinishDownload(DownloadInfoModel downloadInfoModel);

    /** * Download error **@param downloadInfoModel
     * @param e
     */
    void onErrorDownload(DownloadInfoModel downloadInfoModel, Exception e);

    /** * stop downloading **@param downloadInfoModel
     * @param isDelete
     */
    void onStopDownload(DownloadInfoModel downloadInfoModel, boolean isDelete);

    /** * if there is already a file **@param downloadInfoModel
     */
    void hasExists(DownloadInfoModel downloadInfoModel);

    /** * Delete single message **@param model
     */
    void onRemoveDownload(DownloadInfoModel model);
}
Copy the code

3. Add a time utility class (DownloadTimeUtils) to handle the display of the remaining time.

public class DownloadTimeUtils {
    public static long SECOND_TIME = 1 * 1000;
    public static long MIN_TIME = 60 * 1000;
    public static long HOUR_TIME = 60 * 60 * 1000;
    public static long DAY_TIME = 24 * 60 * 60 * 1000;

    public static String getStringByLongTime(long time) {
        String timeString = "";
        DecimalFormat df = new DecimalFormat("# 00");
        if(time ! =0) {
            if (time > 0 && time < SECOND_TIME) {
                timeString = "00:00:01";
            } else if (time >= SECOND_TIME && time < MIN_TIME) {
                // Within a minute, greater than one second
                timeString = "00:00:" + df.format(time / SECOND_TIME);
            } else if (time >= MIN_TIME && time < HOUR_TIME) {
                // Within an hour, greater than one minute
                int min = 0;
                int second = 0;
                min = (int) (time / MIN_TIME);
                second = (int) (time % MIN_TIME) / 1000;
                timeString = "00:" + df.format(min) + ":" + df.format(second);
            } else if (time >= HOUR_TIME && time < DAY_TIME) {
                // In a day, more than one hour
                int min = 0;
                int hour = 0;
                int second = 0;
                hour = (int) (time / HOUR_TIME);
                min = (int) ((time % HOUR_TIME) / MIN_TIME);
                second = (int) (time % MIN_TIME) / 1000;
                timeString = df.format(hour) + ":" + df.format(min) + ":" + df.format(second);
            } else {
                timeString = "More than a day"; }}returntimeString; }}Copy the code

4. (core) write a file download utility class (DownSingleRunnable), implement the Runnable interface, used to join the thread pool. Write the download operation in the run method, get various download status, such as download speed, remaining time, download progress, etc., refer to the DownloadInfoModel class for status information description.

public class DownSingleRunnable implements Runnable {
    private final static String TAG = "DownSingleRunnable";
    public static final int START_MSG = 0;
    public static final int DOWNLOADING_MSG = 1;
    public static final int FINISH_DOWNLOAD_MSG = 2;
    public static final int ERROR_DOWNLOAD_MSG = 3;
    public static final int EXISTS_DOWNLOAD_MSG = 4;
    public static final int STOP_DOWNLOAD_MSG = 5;
    public static final int REMOVE_DOWNLOAD_MSG = 6;
    private DownloadInfoModel model;
    private long countTime = 1000;// Timing calculation speed
    private DownloadCallback downloadCallback;
    private boolean isDownloading = false;// Whether it is downloading
    private boolean isStop = false;// Whether the pause ends
    private boolean isDelete = false;

    public DownSingleRunnable(DownloadInfoModel model) {
        this.model = model;
        this.downloadCallback = model.getDownloadCallback();
    }

    public DownloadInfoModel getModel(a) {
        return model;
    }

    public void setModel(DownloadInfoModel model) {
        this.model = model;
    }

    /** * stop downloading **@param isDelete
     */
    public void stopDownload(boolean isDelete) {

        Log.e(TAG, "stopDownload " + model.getDownloadId());
        isDownloading = false;
        isStop = true;
        this.isDelete = isDelete;
        handler.sendEmptyMessage(STOP_DOWNLOAD_MSG);
    }

    /** * Get the size of the downloaded content from the original file **@param urlConnection
     * @return* /
    private long getContentLengthFromHeader(URLConnection urlConnection) {
        List values = urlConnection.getHeaderFields().get("content-Length");
        if(values ! =null && !values.isEmpty()) {

            String sLength = (String) values.get(0);

            if(sLength ! =null) {
                return Long.parseLong(sLength, 10); }}return -1;
    }

    @Override
    public void run(a) {
        if (model.getStatus() == DOWNLOAD_REMOVE_STATUS) {
            Log.e(TAG, "The task has been removed" + model.getDownloadId());
            return;
        }
        Log.e(TAG, "" + model.getDownloadId());
        File file = null;
        BufferedInputStream bin = null;
        OutputStream out = null;
        try {
            // Establish a connection
            HttpURLConnection httpURLConnection = null;
            URL url = new URL(model.getRemoteUrl());
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestProperty("Charset"."UTF-8");// Set the character encoding
            httpURLConnection.setRequestProperty("Accept-Encoding"."identity");
            httpURLConnection.setReadTimeout(1000);// Disconnect the network
            httpURLConnection.connect();

            long fileLength = httpURLConnection.getContentLength();
            if (fileLength == -1) {
                // Large file download mechanism
                fileLength = getContentLengthFromHeader(httpURLConnection);
            }
            String filePathUrl = httpURLConnection.getURL().getFile();
            String fileFullName = model.getFileName() + "." + model.getFileExtname();

            bin = new BufferedInputStream(httpURLConnection.getInputStream());
            String path = model.getSavePath() + File.separatorChar + fileFullName;
            file = new File(path);
            // Create folder without folder
            if(! file.getParentFile().exists()) { file.getParentFile().mkdirs(); }// If the file already exists
            if (file.exists()) {
                handler.sendEmptyMessage(EXISTS_DOWNLOAD_MSG);
                model.setFileName(model.getFileName() + "(1)");
                fileFullName = model.getFileName() + "." + model.getFileExtname();
                path = model.getSavePath() + File.separatorChar + fileFullName;
                file = new File(path);
            }
            Log.d(TAG, "filePathUrl = " + filePathUrl + " fileFullName = " + fileFullName
                    + " path = " + path + " fileLength = " + fileLength);
            out = new FileOutputStream(file);
            long size = 0;
            long len = 0;
            byte[] buf = new byte[1024];
            long lastTime = System.currentTimeMillis();// The last time the speed was calculated
            long lastLen = 0;// The last length is used to calculate the speed
            long speed = 0;/ / speed
            long lestTime = 0;// the remaining time is ms
            handler.sendEmptyMessage(START_MSG);
            isDownloading = true;
            while((size = bin.read(buf)) ! = -1) {

                if(! isDownloading) {break;
                }
                len += size;
                long nowTime = System.currentTimeMillis();// Get the current time
                if (lastLen == 0) {
                    lastLen = len;
                }
                // The speed is calculated only after countTime
                if ((nowTime - lastTime) >= countTime) {
                    speed = (len - lastLen) * 1000 / (nowTime - lastTime);
                    lestTime = (fileLength - len) * 1000 / speed;
                    lastTime = nowTime;
                    lastLen = len;
                    model.setLen(len).setFileLength(fileLength).setPercentage((int) (len * 100 / fileLength))
                            .setSpeed(speed).setLestTime(lestTime);
                    handler.sendEmptyMessage(DOWNLOADING_MSG);
                }
                out.write(buf, 0, (int) size);
            }
            Log.e(TAG, "finish " + model.getDownloadId());
            // Download complete
            if (!isStop) {
                handler.sendEmptyMessage(FINISH_DOWNLOAD_MSG);
            }
        } catch (Exception e) {
            // Disconnection will enter the current error
            Message message = new Message();
            message.what = ERROR_DOWNLOAD_MSG;
            message.obj = e;
            handler.sendMessage(message);
        } finally {
            Log.e(TAG, "finally ");
            try {
                if(bin ! =null) {
                    bin.close();
                }
                if(out ! =null) { out.close(); }}catch(IOException e1) { e1.printStackTrace(); }}}private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case START_MSG:
                    if (null! = downloadCallback) { model.setStatus(DownloadInfoModel.DOWNLOADING_STATUS); downloadCallback.onStartDownload(model); }break;
                case DOWNLOADING_MSG:
                    if (null! = downloadCallback) { downloadCallback.onDownloading(model); }break;
                case FINISH_DOWNLOAD_MSG:
                    if (null! = downloadCallback) { model.setStatus(DownloadInfoModel.DOWNLOAD_SUCCESS_STATUS); downloadCallback.onFinishDownload(model); }break;
                case ERROR_DOWNLOAD_MSG:
                    if (null! = downloadCallback) { model.setStatus(DownloadInfoModel.DOWNLOAD_FAILD_STATUS); downloadCallback.onErrorDownload(model, (Exception) msg.obj); }break;
                case EXISTS_DOWNLOAD_MSG:
                    if (null! = downloadCallback) { downloadCallback.hasExists(model); }break;
                case STOP_DOWNLOAD_MSG:
                    if (null! = downloadCallback) { downloadCallback.onStopDownload(model, isDelete); }break;
                case REMOVE_DOWNLOAD_MSG:
                    if (null! = downloadCallback) { model.setStatus(DOWNLOAD_REMOVE_STATUS); downloadCallback.onRemoveDownload(model); }break;
                default:
                    break; }}};/** * Clears a single list */
    public void removeList(a) { handler.sendEmptyMessage(REMOVE_DOWNLOAD_MSG); }}Copy the code

5. (core) write an action tool class (DownloadThreadHelper), used to handle multi-task download, internal use is the thread pool (newFixedThreadPool), can be set in the external simultaneously download number, by changing the size of the thread pool.

public class DownloadThreadHelper {
    private int maxDownloadNum = 1;// Maximum number of simultaneous downloads
    private ArrayList<DownloadInfoModel> downloadInfoModels;
    private ArrayList<DownSingleRunnable> downSingleRunnables;
    ExecutorService executorService;

    public int getMaxDownloadNum(a) {
        return maxDownloadNum;
    }

    public DownloadThreadHelper setMaxDownloadNum(int maxDownloadNum) {
        this.maxDownloadNum = maxDownloadNum;
        return this;
    }

    public ArrayList<DownloadInfoModel> getDownloadInfoModels(a) {
        return downloadInfoModels;
    }

    public DownloadThreadHelper setDownloadInfoModels(ArrayList<DownloadInfoModel> downloadInfoModels) {
        this.downloadInfoModels = downloadInfoModels;
        return this;
    }

    /** * enable thread pool download file */
    public void startDownloadFiles(a) {
        executorService = Executors.newFixedThreadPool(maxDownloadNum);

        downSingleRunnables = new ArrayList<>();
        for (DownloadInfoModel model : downloadInfoModels) {
            DownSingleRunnable downSingleRunnable = new DownSingleRunnable(model);
            downSingleRunnables.add(downSingleRunnable);
        }
        for (DownSingleRunnable runnable : downSingleRunnables) {
            executorService.execute(runnable);
        }
        executorService.shutdown();// do not allow other threads to join
    }

    /** * Stop a single download **@param downloadId
     */
    public void stopDownloadSingleFile(String downloadId) {
        if (isEmpty()) {
            return;
        }
        for (DownSingleRunnable runnable : downSingleRunnables) {
            if (runnable.getModel().getDownloadId().equals(downloadId)) {
                runnable.stopDownload(true);
                break; }}}/** * stop all downloads */
    public void stopAllFile(a) {
        if (isEmpty()) {
            return;
        }
        for (DownSingleRunnable runnable : downSingleRunnables) {
            runnable.stopDownload(false); }}/** * delete from list, change state **@param downloadId
     */
    public void removeList(String downloadId) {
        if (isEmpty()) {
            return;
        }
        for (DownSingleRunnable runnable : downSingleRunnables) {
            if (runnable.getModel().getDownloadId().equals(downloadId)) {
                runnable.removeList();
                break; }}}/** * Determine whether the thread pool is empty **@return* /
    private boolean isEmpty(a) {
        if (null == downSingleRunnables) {
            return true;
        }
        if (downSingleRunnables.size() <= 1) {
            return true;
        }
        return false; }}Copy the code

The above is the complete package of the code base. The download library can be used as follows:

DownloadThreadHelper downloadThreadHelper = new DownloadThreadHelper();
ArrayList<DownloadInfoModel> downloadInfoModels = new ArrayList<>();
DownloadInfoModel model = new DownloadInfoModel();
// Set the download ID
model.setDownloadId(downloadId);
// Set the server file path
model.setRemoteUrl(remoteUrl);
// Set the storage path
model.setSavePath(savePath);
// Set the file name without the extension
model.setFileName(fileName);
// Set the file name extension
model.setFileExtname(fileExtname);
// Set the listener
model.setDownloadCallback(new DownloadCallback() {
    @Override
    public void onDownloading(DownloadInfoModel downloadInfoModel) {}@Override
    public void onStartDownload(DownloadInfoModel downloadInfoModel) {}@Override
    public void onFinishDownload(DownloadInfoModel downloadInfoModel) {}@Override
    public void onErrorDownload(DownloadInfoModel downloadInfoModel, Exception e) {}@Override
    public void onStopDownload(DownloadInfoModel downloadInfoModel, boolean isDelete) {}@Override
    public void hasExists(DownloadInfoModel downloadInfoModel) {}@Override
    public void onRemoveDownload(DownloadInfoModel model) {}});// Add one download task. If there are more than one, add more tasks
downloadInfoModels.add(model);
// Add all tasks
downloadThreadHelper.setDownloadInfoModels(downloadInfoModels);
// Start downloading
downloadThreadHelper.startDownloadFiles();
// Stop all downloading tasks
downloadThreadHelper.stopAllFile();
// Stop the specified task
downloadThreadHelper.stopDownloadSingleFile(downloadId);
Copy the code

The library can update real-time download progress, speed, download status, remaining time estimates. I didn’t go to the breakpoint for the time being, so I can add it later if I have time.