Android Multithread Breakpoint Download

Keywords: Android network Google Mobile

Project Profile:

This project demo ah Android file multithreaded breakpoint Download

Detailed introduction:

The application involves only:
1. Use of HttpURLConnection
2. The use of multithreading
3. File breakpoint Download
4. Use of image progress bar
5. Use of text progress bar

The application download interface, click the button to start downloading files, and display the download progress on the progress bar (including text progress and image progress)

Be careful:

1. The image progress bar, which Google has officially encapsulated, does not need to be executed in the UI thread. It automatically transfers data from the sub-thread to the UI thread, so users can refresh the progress bar directly in the sub-thread.
2. The text progress is actually a percentage calculated by the user himself and then displayed on the TextView control, which involves refreshing the UI, so the user needs to transfer the data to the UI thread and then perform the refresh operation.
3. Random Access File is stored in random files in multithreaded downloads, otherwise coverage may occur, and Random Access File can be updated synchronously. So try to use Random Access File in your program instead of using other streams
4. In multithreading, we must pay attention to thread safety and use synchronous statement blocks and static variables.
5. A synchronized parameter cannot be an empty parameter, otherwise it will never execute the statement block.

Steps:

1. Create an android project with only four components in the layout file, as shown in the following figure:

2. Add a code to MainActivity:

public class MainActivity extends Activity {

    private EditText ed_path;

    // Graphic progress bar
    private ProgressBar pb;

    // Text progress
    private TextView tv;

    // Define a variable that represents the total number of threads
    private static int THREAD_COUNT = 5;

    // Define variables that represent the number of threads that have been downloaded
    private static int THREAD_COMPLETE = 0;

    // Define a variable that acts as a lock, is used for program synchronization, and can be arbitrary strings
    private static String LOCK = "lock";

    // Define a variable that represents the length of the current file download
    private int currentTotal = 0;

    Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 0:
                Toast.makeText(getApplicationContext(), "An unknown error occurred", 0).show();
                break;
            case 1:
                Toast.makeText(getApplicationContext(), "Unable to connect to the network", 0).show();
                break;
            case 2:
                Toast.makeText(getApplicationContext(), "SD Card capacity is insufficient or unable to read and write", 0).show();
                break;
            case 3:
                Toast.makeText(getApplicationContext(), "File downloaded", 0).show();
                // After downloading, it is compulsory to set the text progress to 100%.
                tv.setText("100%");
                break;
            case 4:
                // Refresh text progress bar
                // In the process of calculation, it may exceed the range of int, and it is better to turn strong into long.
                // Large file downloads may result in a 99% final text progress, so when the download is complete, change it manually.
                int max = pb.getMax();
                int pro = pb.getProgress();
                tv.setText((long) pro * 100 / max + "%");
                break;

            }
        }
    };

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

        pb = (ProgressBar) findViewById(R.id.pb);
        ed_path = (EditText) findViewById(R.id.ed_path);
        tv = (TextView) findViewById(R.id.tv);
    }

    // After clicking the button, start downloading
    public void download(View view) {

        // Set progress bar to empty and text progress to 0%.
        pb.setProgress(0);
        tv.setText("0%");

        final String path = ed_path.getText().toString();

        // Open a sub-thread, get the basic information of the file to download, and set the starting and ending positions of each thread.
        Thread thread = new Thread() {
            @SuppressLint("NewApi")
            @Override
            public void run() {

                try {
                    URL url = new URL(path);

                    // Get the object of HttpURLConnection and set properties
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    conn.setRequestMethod("POST");

                    if (conn.getResponseCode() == 200) {
                        // Get the total length of resources to download
                        int length = conn.getContentLength();

                        // Setting the Maximum Length of the Progress Bar
                        pb.setMax(length);

                        // Determine if the SD card space is adequate. Since there may be not only the length of the file, but also the amount of other resources, the SD capacity is slightly larger than the resource file.
                        boolean b = CheckSD.checkCapcity(length);

                        if (b) {

                            // Set the size of the target file and the size of the target file. The target file is placed on the SD card here. So, random file streams are used here.
                            File targetFile = new File(Environment.getExternalStorageDirectory(), getFileName(path));
                            RandomAccessFile raf = new RandomAccessFile(targetFile, "rwd");
                            raf.setLength(length);
                            raf.close();

                            // Calculate the number of bytes each thread needs to download
                            int everyLength = length / THREAD_COUNT;

                            // Determine the start and end positions of each thread
                            for (int i = 0; i < THREAD_COUNT; i++) {
                                int startIndex = i * everyLength;
                                int endIndex = (i + 1) * everyLength - 1;

                                // If it is the last thread, there may be a remainder to write the location of the last thread to death.
                                if (i == THREAD_COUNT - 1) {
                                    endIndex = length - 1;
                                }

                                // Open threads, ready to download
                                new Thread(new DownLoad(i, url, startIndex, endIndex, targetFile)).start();
                            }
                        } else {
                            // SD card space is insufficient or SD card can not read and write, send messages to the main thread
                            handler.sendEmptyMessage(2);
                        }

                    } else {
                        // Unable to connect to the network and send messages to the main thread
                        handler.sendEmptyMessage(1);
                    }
                } catch (Exception e) {
                    // An error occurred in the request, sending a message to the main thread
                    handler.sendEmptyMessage(0);
                    e.printStackTrace();
                }

            }

        };
        thread.start();

    }

    /**
     * Based on a given URL string, the last digits of the string are intercepted as the name of the downloaded file
     */
    protected String getFileName(String path) {
        int index = path.lastIndexOf("/");
        String fileName = path.substring(index + 1);
        return fileName;
    }

    /**
     * Create an internal class that opens each subthread to download resource files
     */
    class DownLoad implements Runnable {

        int threadID;
        URL url;
        int startIndex;
        int endIndex;
        File targetFile;

        /**
         * @param threadID
         *            Current thread name
         * @param url
         *            URL of resources
         * @param startIndex
         *            Starting position
         * @param endIndex
         *            Termination position
         * @param targetFile
         *            Target file
         */
        public DownLoad(int threadID, URL url, int startIndex, int endIndex, File targetFile) {
            super();
            this.threadID = threadID;
            this.url = url;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.targetFile = targetFile;
        }

        @Override
        public void run() {
            try {

                // Create a temporary progress file that records the number of bytes that the current thread has downloaded. The file is named directly after the thread and is located in the same location as the target file.
                File progressFile = new File(targetFile.getParent(), threadID + ".txt");

                // Determine whether temporary progress documents exist
                if (progressFile.exists()) {

                    // Remove the data from the temporary progress file and change the starting location of the current thread Download
                    BufferedReader br = new BufferedReader(new FileReader(progressFile));
                    int data = Integer.parseInt(br.readLine());
                    br.close();
                    startIndex += data;

                    // Display the number of bytes downloaded by all current threads in the progress bar
                    currentTotal += data;
                    pb.setProgress(currentTotal);

                    // Send a message to the main thread to update the text progress
                    handler.sendEmptyMessage(4);

                }

                Log.i("HHH", threadID + "  The interval is:   " + startIndex + "-----" + endIndex);

                // Get the object of HttpURLConnection and set properties
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                conn.setRequestMethod("POST");

                // Set the interval of request data. If not, the system defaults to request from scratch. So, the data requested by each thread is from scratch and cannot be downloaded by multiple threads.
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);

                // Determine the response code. The response code for requesting partial resources is 206
                if (conn.getResponseCode() == 206) {

                    // Set the location where the file is written to move to startIndex. If the write location is not specified, the file will be overwritten.
                    RandomAccessFile raf = new RandomAccessFile(targetFile, "rwd");
                    raf.seek(startIndex);

                    // Get the input stream in the resource
                    InputStream is = conn.getInputStream();

                    // Read and write
                    int i = 0;
                    int currentProgress = 0;// Record where the current thread has been downloaded
                    int len = 0;
                    byte[] buffer = new byte[1024];
                    while ((len = is.read(buffer)) != -1) {
                        raf.write(buffer, 0, len);
                        currentProgress += len;
                        currentTotal += len;

                        i++;
                        // Write the total number of bytes that the current thread has downloaded to the temporary progress file
                        // Every update operation is required to be written to the progress temporary file synchronously, so it is better to store the stream with random files.
                        // It takes time to record every read and write that the UI has been updated, so record it every once in a while (here, record every 30K download)
                        if (i % 30 == 1 || len != 1024) {
                            RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
                            progressRaf.write((currentProgress + "").getBytes());
                            progressRaf.close();

                            // Setting progress bar and text progress
                            pb.setProgress(currentTotal);
                            handler.sendEmptyMessage(4);
                        }

                    }

                    is.close();
                    raf.close();

                    // The current thread downloads completed, recording the number of threads completed
                    THREAD_COMPLETE++;
                    Log.i("HHH", threadID + "Threads downloaded, the current number of completed threads:  " + THREAD_COMPLETE);

                } else {
                    handler.sendEmptyMessage(1);
                }
            } catch (Exception e) {
                handler.sendEmptyMessage(0);
            }

            // When all threads are finished, a message is sent to the main thread and all temporary progress files are deleted. Synchronization block is needed here
            synchronized (LOCK) {
                if (THREAD_COMPLETE == THREAD_COUNT) {
                    for (int j = 0; j < THREAD_COUNT; j++) {
                        File f = new File(targetFile.getParent(), j + ".txt");
                        if (f.exists()) {
                            f.delete();
                            THREAD_COMPLETE--;
                        }
                    }
                    // Send a message to the main thread and download it.
                    handler.sendEmptyMessage(3);
                }
            }
        }
    }
}

3. In which the code:

boolean b = CheckSD.checkCapcity(length);

This is a self-written tool class, which is used to determine whether the SD card space in the current mobile phone is enough.
The code is as follows:

@SuppressLint("NewApi")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class CheckSD {

    // Judging SD Card Status
    public static boolean checkStatus() {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return true;
        }
        return false;
    }

    // Judging whether the SD card capacity is adequate
    public static boolean checkCapcity(int length) {

        boolean b = false;

        if (checkStatus()) {
            // The path of input sd card
            String path = Environment.getExternalStorageDirectory().getPath();

            // Read the storage space of sd card
            // Calling C's System Library Statfs
            StatFs fs = new StatFs(path);

            long availableBolocks = 0;
            long bolockSize = 0;
            // Judge the version level of the current Android system.
            if (Build.VERSION.SDK_INT >= 18) {
                availableBolocks = fs.getAvailableBlocksLong();
                bolockSize = fs.getBlockSizeLong();
            } else {
                availableBolocks = fs.getAvailableBlocks();
                bolockSize = fs.getBlockSize();
            }

            long available = availableBolocks * bolockSize;
            if (available > length) {
                b = true;
            }
        }
        return b;
    }

}

4. Finally, add permissions in the manifest file:

 <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

In the layout file, I downloaded the URL s of the resource files directly to the first and second lines (actually this is an EditText component), "Click the button" is actually a Button component, the gray bar is a ProgressBar component, and "0%" is a TextView component. Because the background is black, the borders of these components can not be seen clearly.

Posted by aussie_clint on Thu, 18 Apr 2019 17:18:33 -0700