Downloading Multiple files(Images etc..) in Android Simultaneously/or in Batches in Android using ThreadPoolExecutor.

By | June 10, 2016

CPUs with multiple cores have almost become the standard in Android devices. This means multiple operations doesn’t have to wait to be executed on a single thread. They can rather run on multiple threads in parallel where each threads gets executed on a single processor.

For example imagine an operation where multiple images have to be downloaded. The downloads and any decoding required for different images could happen concurrently on multiple threads speeding up the entire operation, leading to a faster app experience.

Android has an Executor framework using which you can automatically manage a pool of threads with a task queue. Multiple threads will run in parellel executing tasks from the queue. This article mainly focuses on using the ThreadPoolExecutor class mainly.

ThreadPoolExecutor Android

ThreadPoolExecutor

Let’s see how most basic constructor looks like:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue
);

The parameters are

corePoolSize – Minimum number of threads to keep in the pool. Initially there are zero threads in the pool, but as tasks are added to the queue, new threads are created. If there are idle threads but the thread count is lower than the corePoolSize, then new threads will keep on being created.
maximumPoolSize – Maximum number of threads to allow in the pool. If this is more than the corePoolSize and the current number of threads is >= corePoolSize, then new worker threads will be created only if the queue is full.
keepAliveTime – When number of threads is greater than the core, the noncore threads (excess idle threads) will wait for time defined by this parameter for new tasks before terminating.
unit – Time unit for keepAliveTime.
workQueue – The task queue which’ll only hold Runnable tasks. It’ll have to be a .BlockingQueue.

Now Let’s see how the implementation goes.

Here I am downloading images from two URLs.
Just to make it simple I am repeating two URLs.

Here I have a Runnable Class that downloads the Image.
I am just using “InputStream” to download the image, You can modify it the way you want.

LongThread.java

package com.coderzheaven.multiplethread;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.io.InputStream;

public class LongThread implements Runnable {

    int threadNo;
    Handler handler;
    String imageUrl;
    public static final String TAG = "LongThread";

    public LongThread() {
    }

    public LongThread(int threadNo, String imageUrl, Handler handler) {
        this.threadNo = threadNo;
        this.handler = handler;
        this.imageUrl = imageUrl;
    }

    @Override
    public void run() {
        Log.i(TAG, "Starting Thread : " + threadNo);
        getBitmap(imageUrl);
        sendMessage(threadNo, "Thread Completed");
        Log.i(TAG, "Thread Completed " + threadNo);
    }


    public void sendMessage(int what, String msg) {
        Message message = handler.obtainMessage(what, msg);
        message.sendToTarget();
    }

    private Bitmap getBitmap(String url) {
        Bitmap bitmap = null;
        try {
            // Download Image from URL
            InputStream input = new java.net.URL(url).openStream();
            // Decode Bitmap
            bitmap = BitmapFactory.decodeStream(input);
            // Do extra processing with the bitmap
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

}

Now the MainActivity that implements this class using ThreadPoolExecutor

MainActivity.java

package com.coderzheaven.multiplethread;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity implements Handler.Callback {

    private Handler handler;
    TextView tvStatus;
    int curCount = 0;
    ProgressBar progressBar;
    String url1 = "YOUR_IMAGE_URL1";
    String url2 = "YOUR_IMAGE_URL2";
    float totalCount = 50F;

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

        tvStatus = (TextView) findViewById(R.id.tvDownloadCount);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                NUMBER_OF_CORES * 2,
                NUMBER_OF_CORES * 2,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>()
        );

        for (int i = 0; i < totalCount; i++) {
            String imageUrl = (i % 2 == 0) ? url1 : url2;
            executor.execute(new LongThread(i, imageUrl, new Handler(this)));
        }

        tvStatus.setText("Starting Download...");
    }

    @Override
    public boolean handleMessage(Message msg) {
        curCount++;
        float per = (curCount / totalCount) * 100;
        progressBar.setProgress((int) per);
        if (per < 100)
            tvStatus.setText("Downloaded [" + curCount + "/" + (int)totalCount + "]");
        else
            tvStatus.setText("All images downloaded.");
        return true;
    }
}

My Simple Layout will look like this.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.coderzheaven.multiplethread.MainActivity">

    <TextView
        android:id="@+id/tvDownloadCount"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_margin="20dp"
        android:gravity="center"
        android:textSize="20sp"
        android:textStyle="bold"
        android:textColor="@android:color/holo_green_dark"/>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvDownloadCount"
        android:layout_centerHorizontal="true" />


</RelativeLayout>

You can download the complete Android Studio Source Code from here

Leave a Reply

Your email address will not be published. Required fields are marked *