Android Multithreaded Download File Principle Overwhelming Resolution Introduction - --- File Download (3)

Keywords: OkHttp network Android Database

1. First let's create the download location - create the file based on the url.

  /**
 * <p>Title: FlieStorageManager</p >
 * <p>Description: TODO</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2018/2/5
 */
public class FlieStorageManager {

    public static FlieStorageManager sManager = new FlieStorageManager();

    private Context mContext;

    public static FlieStorageManager getInstance(){
        return sManager;
    }

    public void init(Context mContext){
        this.mContext = mContext;
    }

    public FlieStorageManager() {
    }

    /**
     * Return file based on url setting filename
     * @param url
     * @return
     */
    public File getFileByName(String url){
        File parent;
        // Determine if your phone has an SD card mounted
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            // SDCard/Android/data/your application package name/cache/directory can be obtained by the Context.getExternalCacheDir() method, which generally stores temporary cache data
            parent = mContext.getExternalCacheDir();
        }else {
            // No SD card saved under cache in system directory
            parent = mContext.getCacheDir();
        }

        String fileName = Md5Uills.generateCode(url);
        // Path, file name
        File file = new File(parent, fileName);
        if(!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return file;
    }

}

Our path is obviously in the cach e directory.

So let's just encapsulate a few requests for a framework for okhttp networking.
Then let's see how a simple single-threaded download works

/**
 * <p>Title: HttpManager</p >
 * <p>Description: Networking Request Tool Class </p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2018/2/5
 */
public class HttpManager {

    public static HttpManager mManager = new HttpManager();
    /** request was aborted*/
    public static final int NETWORK_ERROR_CODE = 1;
    /** Cannot get the total length of the file*/
    public static final int CONTENT_LENGTH_ERROR_CODE = 2;
    /** Downloaded tasks exist in the queue*/
    public static final int TASK_RUNNING_ERROR_CODE = 3;
    /** okhttp Request Class*/
    private OkHttpClient mClient;
    private Context mContext;

    private void init(Context mContext){
        this.mContext = mContext;
    }

    public HttpManager() {
        this.mClient = new OkHttpClient();
    }

    public static HttpManager getInstance(){
        return mManager;
    }

    /**
     * Synchronization Request
     *
     * @param url
     * @return
     */
    public Response syncRequest(String url) {
        Request request = new Request.Builder().url(url).build();
        try {
            return mClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Synchronization requests (downloads based on specified location)
     * Specify the download location based on the request header Range field
     */
    public Response syncRequestByRange(String url, long start, long end) {
        Request request = new Request.Builder().url(url)
                .addHeader("Range", "bytes=" + start + "-" + end)
                .build();
        try {
            return mClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * Asynchronous call
     * @param url
     * @param callback
     */
    public void asyncRequest(final String url, Callback callback) {
        Request request = new Request.Builder().url(url).build();
        mClient.newCall(request).enqueue(callback);
    }


    /**
     * Asynchronous request (single-threaded download)
     */
    public void asyncSingleThreadRequest(final String url, final DownLoadCallBack callback) {
        Request request = new Request.Builder().url(url).build();
        // Asynchronous request for okhttp
        mClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                if (!response.isSuccessful() && callback != null) {
                    callback.fail(NETWORK_ERROR_CODE, "request was aborted");
                }

                // Create a file from a url
                File file = FlieStorageManager.getInstance().getFileByName(url);

                // Create a byte array to read and write to
                byte[] buffer = new byte[1024 * 500];
                int len;
                FileOutputStream fileOut = new FileOutputStream(file);
                // Returned response to data stream
                InputStream inStream = response.body().byteStream();
                // Read the return stream and write it to the created file
                while ((len = inStream.read(buffer, 0, buffer.length)) != -1) {
                    fileOut.write(buffer, 0, len);
                    fileOut.flush();
                }
                // Write Complete
                callback.success(file);

            }
        });
    }
}

Then take a look at our callback interface:

/**
 * <p>Title: DownLoadCallBack</p >
 * <p>Description: TODO</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2018/2/5
 */
public interface DownLoadCallBack {

    void success(File file);

    void fail(int errorCode, String errorMessage);

    void progress(int progress);
}

You can see that HttpManager is a simple okhttp tool class that I sealed.This tool class has callbacks for synchronous requests and asynchronous callbacks.

I also want to thank you briefly for a single-threaded download example.That is, you call the asyncSingleThreadRequest() method directly, which is a single-threaded method.I've written all the notes in it, so I'm sure you can read them at a glance.

Single-threaded downloaded business process: Create a file from the url, then in the asynchronous request callback, convert the stream of response data through the **response.body().byteSream(), and write it in our file as a stream.

OK, let's move on.

We then create a multithreaded download management class:

/**
 * <p>Title: DownloadManager</p >
 * <p>Description: Download, Thread Management Class </p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2018/2/6
 */
public class DownloadManager {

    private static DownloadManager mManager = new DownloadManager();
    /** Number of Cores and Maximum Threads*/
    private final static int MAX_THREAD = 2;

    /**
     * Create Thread Pool
     * Parameter 1: Number of core threads
     * Parameter 2: Maximum number of thread pools
     * Parameter 3: Thread lifetime
     * Parameter 4: Set the time scale
     * Parameter 5: Ignore first
     * Principle: Thread pools create core threads first and throw exceptions if the maximum number is exceeded while executing
     */
    private static final ThreadPoolExecutor sThreadPool = new ThreadPoolExecutor(MAX_THREAD, MAX_THREAD, 60,
            TimeUnit.MICROSECONDS, new LinkedBlockingDeque<Runnable>(), new ThreadFactory() {

        private AtomicInteger mInteger = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable runnable) {
            // Specify Runnable and Thread Name
            Thread mThread = new Thread(runnable, "download thread #" + mInteger.getAndIncrement());
            return mThread;
        }
    });

    public static DownloadManager getInstance(){
        return mManager;
    }

    public DownloadManager() {
    }

    /**
     * Determine how long data is downloaded per thread and download it multithreaded
     */
    public void downLoad(final String url, final DownLoadCallBack callBack){
        HttpManager.getInstance().asyncRequest(url, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if(response == null && callBack != null) {
                    callBack.fail(HttpManager.NETWORK_ERROR_CODE, "There's a problem with the network");
                    return;
                }

                long length = response.body().contentLength();
                if(length == -1) {
                    // Cannot get the total length of the file
                    callBack.fail(HttpManager.CONTENT_LENGTH_ERROR_CODE, "contenLength -1");
                    return;
                }

                processDownload(url, length, callBack);
            }
        });
    }

    /**
     * download
     * @param length     Download File Length
     */
    private void processDownload(String url, long length, DownLoadCallBack callBack) {
        // Calculate download size for each thread
        long threadDownloadSize = length / MAX_THREAD;
        // Assign each thread to download
        for(int i = 0; i < MAX_THREAD; i++) {
            // Calculate how many downloads each thread can download, such as 0-49 50-99 threads of length 1002. Here's the algorithm for calculating
            long startSize = i * threadDownloadSize;
            long endSize = (i + 1) * threadDownloadSize - 1;

            // Perform Download
            sThreadPool.execute(new DownloadRunnable(startSize, endSize, url, callBack));
        }
    }
}

Summary: First create a thread pool with a core thread thread thread and a maximum number of threads of 2 to manage threads.
Then we make an asynchronous request, access the downloaded file, get the total length of the file, divide the downloaded length of each thread equally according to the number of threads, and download the file through the thread pool.

OK, let's look at how the core thread class works.

/**
 * <p>Title: DownloadRunnable</p >
 * <p>Description: Download Execution Thread </p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2018/2/6
 */
public class DownloadRunnable  implements Runnable{

    /** Specify download start location*/
    private long mStart;
    /** Specify the download end location*/
    private long mEnd;
    /** Request url*/
    private String mUrl;
    /** Result Callback*/
    private DownLoadCallBack mCallBack;

    public DownloadRunnable(long mStart, long mEnd, String mUrl, DownLoadCallBack mCallBack) {
        this.mStart = mStart;
        this.mEnd = mEnd;
        this.mUrl = mUrl;
        this.mCallBack = mCallBack;
    }

    @Override
    public void run() {
        // Result response returned after download
        Response response = HttpManager.getInstance().syncRequestByRange(mUrl, mStart, mEnd);

        if(response == null && mCallBack != null) {
            mCallBack.fail(HttpManager.NETWORK_ERROR_CODE, "There's a problem with the network");
            return;
        }

        // Get locally downloaded stored files
        File file = FlieStorageManager.getInstance().getFileByName(mUrl);

        // Multiple threads write data to the location specified by the file (because it is a multithreaded download, multiple threads are bound to be readable, writable and modifiable to a file)
        try {
            // Parameter 1: File parameter 2 for specified operation: Readable, writable, modifiable
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd");
            // Specify an offset, where downloads start
            randomAccessFile.seek(mStart);
            byte[] buffer = new byte[1024];
            int len = 0;
            InputStream inputStream = response.body().byteStream();
            // Read the returned data and write it to a local file
            while((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
                randomAccessFile.write(buffer, 0, len);
            }

            // Download Successful
            mCallBack.success(file);

        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This is Runnable running through the thread pool. See the thread run method, which creates a file through the url and converts it into a RandomAccessFile file with the purpose of "writing data to the location specified by the file by multiple threads (because it is a multithreaded download, multiple threads are bound to read, write and modify a file)" and then the file is passed through the seek methodTo specify where to start downloading the file.So different threads download different locations to OK.However, it is important to note that simultaneous Download with multiple threads is a synchronous operation.

OK multithreaded download is complete, then we just need to call it directly where you need it to achieve multithreaded download.
Example (multithreaded download, download complete displayed on ImageView):

   private void multipleDownFileImage() {
        DownloadManager.getInstance().downLoad("http://szimg.mukewang.com/5763765d0001352105400300-360-202.jpg", new DownLoadCallBack() {
            @Override
            public void success(final File file) {
                Log.e("file", "file success: " + file.getAbsolutePath());
                Log.e("file", "file : " + file.length());

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
                        image.setImageBitmap(bitmap);
                    }
                });
            }

            @Override
            public void fail(int errorCode, String errorMessage) {

            }

            @Override
            public void progress(int progress) {

            }
        });
    }

If you need direct copy code, I'll show you some optimizations, store the downloaded data in the database, and then continue downloading.Coming soon!

Posted by kmutz22 on Fri, 17 May 2019 15:09:07 -0700