ZOFTINO.COM android and web dev tutorials

Android ThreadPoolExecutor Example

As the components of android apps run on a single thread called main thread, to improve the performance of android apps, it is important to execute long running task on worker threads. If application is complex and needs to execute multiple tasks at the same time, creating a thread for each task is expensive. Using thread pools improve the performance due to reduced overhead associated with each task execution and help in managing resources including threads when collection of tasks are executed.

You can use java concurrency classes in android to create thread and thread pools.

Executor

Executor is an interface used to decouple task submission from task execution.

class MyTaskExecutor implements Executor {
   public void execute(Runnable r) {
     new Thread(r).start();
   }
 }
 MyTaskExecutor taskExecutor = new MyTaskExecutor();
taskExecutor.execute(new Runnable() {
            @Override
            public void run() {
 		//do work
            }
        }); 

ExecutorService

ExecutorService is an interface which implements Executor interface and provides additional methods which allow you to shutdown the service and track progress and cancel submitted tasks.

Some of the methods are shutdown(), invokeAll() which is used to execute collection of tasks, and submit() which returns Future object using that you can cancel the task or track progress of it.

The default implementation of ExecutorService interface provided is AbstractExecutorService class.

ThreadPoolExecutor

ThreadPoolExecutor is a sub class of AbstractExecutorService class. In addition to all the methods it inherits from AbstractExecutorService class, it provides methods to maintais task queue and thread pool. It picks tasks from queue and executes it using a free thread from the thread pools it maintains. Task producer submits tasks to task queue.

ThreadPoolExecutor constructor arguments are core and maximum thread pool sizes, keep alive time which specifies the time after which excess free threads will be terminated to main the thread pool size to core size, keep alive time unit and task queue.

Java concurrency framework provides various queue implementations which can be used as task queue, ArrayBlockingQueue, BlockingDeque, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue, and TransferQueue.

You can also define your own thread factory which is used to creates threads in the thread pool and add it to ThreadPoolExecutor.

 downaloadWorkQueue = new LinkedBlockingQueue<Runnable>();

downloadThreadPool = new ThreadPoolExecutor(5, 5,
        50, TimeUnit.MILLISECONDS, downaloadWorkQueue); 

To add a task to queue, you need to just pass runnable object to execute method of ThreadPoolExecutor object.

 downloadThreadPool.execute(task);

Android ThreadPoolExecutor Example

I’ll show how to use ThreadPoolExecutor in android with an example. The example displays a list of files in recycler view allowing the users to click items. Clicking an item triggers the download of file associated with the item in the recycler view. Each click creates a runnable task which is added to task queue of ThreadPoolExecutor. Once task is complete, the status will be updated in UI for each item.

DownloadManager

DownloadManager is a singleton class. It creates instance of task queue, ThreadPoolExecutor and main thread executor which is used to update UI with results.

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

public class DownloadManager {
    private final ThreadPoolExecutor downloadThreadPool;
    private final BlockingQueue<Runnable> downaloadWorkQueue;

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 5;
    private static final int KEEP_ALIVE_TIME = 50;

    private static DownloadManager downloadManager = null;
    private static MainThreadExecutor handler;

    static {
        downloadManager = new DownloadManager();
        handler = new MainThreadExecutor();
    }

    private DownloadManager(){
        downaloadWorkQueue = new LinkedBlockingQueue<Runnable>();

        downloadThreadPool = new ThreadPoolExecutor(5, 5,
                50, TimeUnit.MILLISECONDS, downaloadWorkQueue);
    }

    public static DownloadManager getDownloadManager(){
        return downloadManager;
    }

    public void runDownloadFile(Runnable task){
        downloadThreadPool.execute(task);
    }

    //to runs task on main thread from background thread
    public MainThreadExecutor getMainThreadExecutor(){
        return handler;
    }
} 

MainThreadExecutor

MainThreadExecutor uses handler of the main thread to run tasks on the main thread.

 import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;

import java.util.concurrent.Executor;

public class MainThreadExecutor implements Executor{

    private Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void execute(@NonNull Runnable runnable) {
        handler.post(runnable);
    }
} 

DownloadResultUpdateTask

This runnable updates the UI with results from background task submitted to ThreadPoolExecutor. This task is run on the main thread using MainThreadExecutor class.

 import android.widget.TextView;

public class DownloadResultUpdateTask implements Runnable{
    private TextView message;
    private String backgroundMsg;

    public DownloadResultUpdateTask(TextView msg){
        message = msg;
    }
    public void setBackgroundMsg(String bmsg){
        backgroundMsg = bmsg;
    }
    @Override
    public void run() {
        message.setText(backgroundMsg);
    }
} 

DownloadTask

This is actual download task which will be created and added to ThreadPoolExecutor every time an item is clicked in recyclerview to download a file.

import java.io.FileOutputStream;
import java.io.InputStream;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class DownloadTask implements Runnable{
    String url;
    String localFile;
    DownloadResultUpdateTask resultUpdateTask;

    public DownloadTask(String urlIn, String localFileIn,
                        DownloadResultUpdateTask drUpdateTask){
        url = urlIn;
        localFile = localFileIn;
        resultUpdateTask = drUpdateTask;
    }

    @Override
    public void run() {
        String msg;
        if(downloadFile()){
            msg = "file downloaded successful "+url;
        }else{
            msg = "failed to download the file "+url;
        }
        //update results download status on the main thread
        resultUpdateTask.setBackgroundMsg(msg);
        DownloadManager.getDownloadManager().getMainThreadExecutor()
                .execute(resultUpdateTask);
    }

    public boolean downloadFile(){
        try{
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();

            Response response = client.newCall(request).execute();

            InputStream in = response.body().byteStream();
            FileOutputStream fileOutput =
                    new FileOutputStream(localFile);

            byte[] buffer = new byte[1024];
            int bufferLength = 0;
            while ((bufferLength = in.read(buffer)) > 0) {
                fileOutput.write(buffer, 0, bufferLength);
            }
            fileOutput.close();
            response.body().close();
        }catch (Exception e){e.printStackTrace(); return false;}
        return true;
    }
}

Activity

 import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class DownloadFilesActivity extends AppCompatActivity {
    private static final String TAG = "DownloadFilesActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download_files);

        RecyclerView fileRv = findViewById(R.id.files_rv);

        LinearLayoutManager recyclerLayoutManager = new LinearLayoutManager(this);
        fileRv.setLayoutManager(recyclerLayoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(
                fileRv.getContext(),recyclerLayoutManager.getOrientation());
        fileRv.addItemDecoration(dividerItemDecoration);

        FilesRecyclerViewAdapter recyclerViewAdapter = new FilesRecyclerViewAdapter(
                                                    getFilesList(), this);
        fileRv.setAdapter(recyclerViewAdapter);

        //storage write permission needed to save downloaded file on device
        writeStoragePermission();
    }
    private void writeStoragePermission(){
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.READ_EXTERNAL_STORAGE},
                    1);

        }
    }
    private List<String> getFilesList(){
        List<String> files = new ArrayList<String>();
        files.add("http://dummyyyy.com/coupons");
        files.add("http://dummyyyy.com/storeOffers");
        files.add("http://dummyyyy.com/topCoupon");
        return files;
    }
} 

Activity layout

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/files_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"/>
</LinearLayout> 

RecyclerView adapter

import android.content.Context;
import android.os.Environment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class FilesRecyclerViewAdapter extends
        RecyclerView.Adapter<FilesRecyclerViewAdapter.ViewHolder> {

    private List<String> fileList;
    private Context context;

    private String DOWNLOAD_DIR = Environment.getExternalStoragePublicDirectory
            (Environment.DIRECTORY_DOWNLOADS).getPath();

    public FilesRecyclerViewAdapter(List<String> list, Context ctx) {
        fileList = list;
        context = ctx;
    }
    @Override
    public int getItemCount() {
        return fileList.size();
    }

    @Override
    public FilesRecyclerViewAdapter.ViewHolder
                            onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.file_item, parent, false);

        FilesRecyclerViewAdapter.ViewHolder viewHolder =
                new FilesRecyclerViewAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(FilesRecyclerViewAdapter.ViewHolder holder, int position) {
        final int itemPos = position;
        final String fileName = fileList.get(position);
        holder.fileName.setText(fileName);

        final TextView downloadStatus = holder.downloadStatus;

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                downloadFile(fileName, downloadStatus);
            }
        });
    }
    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView fileName;
        public TextView downloadStatus;

        public ViewHolder(View view) {
            super(view);
            fileName = view.findViewById(R.id.file_name_i);
            downloadStatus = view.findViewById(R.id.download_file_status);
        }

    }
    private String getFileName(String url){
        return url.substring(url.lastIndexOf("/")+1);
    }
    private void downloadFile(String url, TextView downloadStatus){
        String localFile = DOWNLOAD_DIR+"/"+getFileName(url);
        DownloadResultUpdateTask drUpdateTask = new DownloadResultUpdateTask(downloadStatus);

        DownloadTask downloadTask = new DownloadTask(url, localFile, drUpdateTask);
        DownloadManager.getDownloadManager().runDownloadFile(downloadTask);

    }
}

RecyclerView item layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp">
    <TextView
        android:id="@+id/file_name_i"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
    <TextView
        android:id="@+id/download_file_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"/>
</LinearLayout>