2

I am working through a school book example as on how to write data into a SQlite database using Room in Android. Apparently, we cannot write data into the database from the main-thread, but we need a background-thread for this. In my book, what we do is, we create an extra class AppExecutors with the following content:

public class AppExecutors {
    private final Executor _diskIO;
    private final Executor _networkIO;
    private final Executor _mainThread;

    private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
        _diskIO = diskIO;
        _networkIO = networkIO;
        _mainThread = mainThread;
    }

    public AppExecutors() {
        this(Executors.newSingleThreadExecutor(),
                Executors.newFixedThreadPool(3),
                new MainThreadExecutor());
    }

    public Executor diskIO() {
        return _diskIO;
    }


public Executor networkIO() {
        return _networkIO;
    }

    public Executor mainThread() {
        return _mainThread;
    }

    private static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}

Then, inside the MainActivity, which contains the UI as well, i.e. the button, which triggers an action when clicked, we call

getApp().getExecutors().diskIO().execute(() -> {
    ...
}

where getApp().getExecutors() returns a new instance of AppExecutors.

Here is my question:

I do understand that getApp().getExecutors().mainThread().execute(() -> {...} is passed a new Runnable and this Runnable is given to the messagequeue of the main-thread. The looper takes each thread out of the messagequeue and executes it.

However, things are different for getApp().getExecutors().diskIO().execute(() -> {...}. The Runnable that is passed to it is apparently executed in a background thread. Executors.newSingleThreadExecutor() seems to be opening up a new thread. But there is no handler or messagequeue associated with this thread. So, I was wondering, won't this thread be closed after execution? Once the thread is closed, we cannot open the same thread again. And that's a bit confusing for me, shouldn't the thread stay open? I have read up on the ExecutorService now and apparently this service just lets the thread stay open. But then again, isn't this the same thing that the messageque and the handler are doing? So how is this different?

Luk
  • 1,009
  • 2
  • 15
  • 33

2 Answers2

1

I think this answer will answer your question partially. Additionally for your question:

So, I was wondering, won't this thread be closed after execution? Once the thread is closed, we cannot open the same thread again. And that's a bit confusing for me, shouldn't the thread stay open?

No, it won't. The thread of that executor service will be Idle or blocked state until you queue a new runnable since it uses a BlockingQueue. The thread will be blocked if the queue is empty, and will be activated if a Runnale object arrives to its queue. As you can see the ExecutorService does not have a Looper. It will be destroyed only if you call either shutdown() or shutdownNow() methods of the ExecutorService.

But then again, isn't this the same thing that the messageque and the handler are doing? So how is this different?

In contrast a Handler needs to be bound to a Looper in order to send messages and post Runnables, a Looper lives inside a HandlerThread. A HandlerThread corresponds to a single threaded executor service, and its Handler corresponds to a Executor. In AppExecutors class a Handler is tied to the Looper of the main thread, because the UI objects cannot be touched from a thread other than the main thread which created it.

Let's see the difference in an example Java code. An example for a background thread using ExecutorService.

/*
To create a background thread making use of the Java concurrent API first we
need to create an ExecutorService or only Executor if you don't want to manage
the service. But generally it must be shutdown when we are done with it.
That's why we need a reference to the ExecutorService in orderto shutdown it.
*/
ExecutorService executorService = Executors.newSingleThreadExecutor();
Executor executor = executorService;

// To send a command:
executor.execute(someRunnable);
// As you can see an executor has only the execute method.

// After we are done with executor service we must shutdown it.
executorService.shutdown();
// or
executorService.shutdownNow();
// This returns a list of runnables that were in the queue.

An example for a background thread using HandlerThread:

/*
To create a background thread which has a message queue we have to make
use of the Android's HandlerThread and Handler API. A HandlerThread object
manages a Looper and a Thread in it while A Handler object is used to send
commands or messages to a living HandlerThread object.
*/
// A HandlerThread can also be instantiated with a name and a priority.
HandlerThread handlerThread = new HandlerThread();
handlerThread.start(); // Must call start in contras to an ExecutorService

// Tie this handler to the handlerThread's Looper so that we can send
// commands or messages to its queue.
Handler handler = new Handler(handlerThread.getLooper());

// To send a command
handler.post(someRunnable); // or
handler.postDelayed(someRunnable, delayInMillis); // or
handler.postAtFrontOfQueue(someRunnable); // or
handler.postAtTime(someRunnable, upTimeInMillis);

Handler API is only Android specific API and has many utilities to send and process Message objects and Runnable commands. See the Handler API for further info.

Kozmotronik
  • 2,080
  • 3
  • 10
  • 25
  • ah, I see, thx a lot Kozmotronik! ... so, in the AppExecutors-class, in the private constructor, instead of writing `Executors.newSingleThreadExecutor()`, I could also do the same as was done for the main-thread: Create a class that implements Executor, in which I create a new HandlerThread and a handler and proceed as in your example? – Luk Jul 25 '21 at 21:31
  • Since you will use a Handler object along with a HandlerThread, implementing an Executor is useless. Just to clear your understanding, instead of implementing that Executor a Handler can be used directly to post commands to the main thread. However they have choosen to implement it in a wrapper manner to provide an API all through executors. If you still don't get it I will show an example tomorrow using the AppExecutors class. – Kozmotronik Jul 25 '21 at 22:22
  • thx again! I think I do understand what you mean. If you find the time to code that example though, that would be great ofc. Either way, thx a lot for your help! – Luk Jul 26 '21 at 07:23
  • 1
    Sure, I'll do it tonight, just keep watching. – Kozmotronik Jul 26 '21 at 07:52
1

Here is an example with a modified AppExecutors class. You can see that instead of using a single thread executor we have used a background handler thread and we create a Handler object and then tied it to its HandlerThread object. Also, for the main thread execution we removed the wrapper class and create another Handler object and then we tied it to the main thread's looper. I hope everything is clearer for you

public class AppExecutors {
    private final Handler _diskIO;
    private final Executor _networkIO;
    private final Handler _mainThread;

    private AppExecutors(Handler diskIO, Executor networkIO, Handler mainThread) {
        _diskIO = diskIO;
        _networkIO = networkIO;
        _mainThread = mainThread;
    }

    public AppExecutors() {

        HandlerThread htDiskIO = new HandlerThread(
            "com.application.htDiskIO", // Give it a name (optional)
            Process.THREAD_PRIORITY_BACKGROUND
        );

        this( new Handler(htDiskIO.getLooper()),
              Executors.newFixedThreadPool(3),
              new Handler(Looper.getMainLooper()));
    }

    public Handler diskIO() {
        return _diskIO;
    }


    public Executor networkIO() {
        return _networkIO;
    }

    public Handler mainThread() {
        return _mainThread;
    }
}

Here is some example usage snippets for the new AppExecutors class:

AppExecutors executors = new AppExecutors();

// Now for diskIO operations you must call like:
executors.diskIO().post(someRunnable);
executors.diskIO().postDelayed(someRunnable, someDelay);

// The network operation is same
executors.networkIO().execute(someRunnable);

// And last but not least; main thread operations
execute.mainThread().post(someRunnable);
execute.mainThread().postDelayed(someRunnable, someDelay);

One more thing to note is, HandlerThread and Handler API belongs to the Google's android.os package. So you will not be able to use them in an application that is not written for Android OS. That's all for this example, I wish you success in your studies.

Kozmotronik
  • 2,080
  • 3
  • 10
  • 25