Sunday, April 8, 2018

Running methods in work thread for Android application

Android application has few ways to invoke method asynchronously, Thread, AsyncTask, Looper/Handler works in the same process. Service can cross process boundary.

1. thread
The most basic way to call method in a work thread is creating a thread object, the below sample creating a new work thread to do some work, and then calling view.post method to update UI in the main thread.

new Thread(new Runnable() {
    public void run() {
        final String workThreadInfo = "Work thread: " + getThreadSignature();
        txtOutput.post(new Runnable() {
            public void run() {
                String uiThreadInfo = "UI thread post: " + getThreadSignature();
                txtOutput.append("\r\n" + workThreadInfo);
                txtOutput.append("\r\n" + uiThreadInfo);
            }
        });
    }
}).start();

2. AsyncTask
AsyncTask standardizes the logic to create a work thread and then update the ui with the result, the above thread sample can be implemented using AsyncTask as below
new AsyncTask<Void, Void, Void>() {
    String workThreadInfo;

    @Override
    protected Void doInBackground(Void... voids) {
        workThreadInfo = "Work thread: " + getThreadSignature();
        return null;
    }

    @Override
    protected void onPostExecute(final Void result) {
        String uiThreadInfo = "UI thread post: " + getThreadSignature();
        txtOutput.append("\r\n" + workThreadInfo);
        txtOutput.append("\r\n" + uiThreadInfo);
    }

}.execute();

3. Handler/Looper
Android uses MessageQueue, Looper and Handler to handle messages. By default, new work thread just created does not have a Looper associate with it, the thread can call Looper.prepare to start the looper.

A thread can have only only one Looper which manages a single message queue by Handler to consume the messages. Multiple Handler can associate with a Looper to consume messages, Handlers are automatically added into the current thread's looper if looper is not specified when it is constructed. Messages are sent or post to message queue by calling handler's method and that message will be processed by that particular handler .

Message can be sent/post to message queues from multiple threads by calling Handler's method, and the only single Looper thread dispatches and consumes the message. So part of Handler's logic (send/post) is running in producer threads, and part of Handler's logic (process) is running in consumer's Loop thread.

Handler.handleMessage does not handle runnable, as dispatchMessage will direct run the runnable by itself. In addition, unlike runnable, the same message cannot be post/sent to different handler.

LooperThread mLooperThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Log.e(TAG, "onCreate: " + getThreadSignature() );
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
     mLooperThread = new LooperThread();
    mLooperThread.start();
}

class LooperThread extends Thread {

    public Handler mHandler;
    public Handler mHandler2;

    public void run() {
        Log.e(TAG, "Looper thread run: " + getThreadSignature() );

        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) { //only msg will get here
                Log.e(TAG, "handleMessage: " + msg.toString() );
                doLongRunningOperation(msg);
            }

            @Override
            public void dispatchMessage(Message msg) {  //both runnable and msg will get here
                Log.e(TAG, "dispatchMessage: " + msg.toString() );
                super.dispatchMessage(msg);
            }
        };

        mHandler2 = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.e(TAG, "handleMessage: " + msg.toString() );
                    doLongRunningOperation(msg);
            }

            @Override
            public void dispatchMessage(Message msg) {
                Log.e(TAG, "dispatchMessage: " + msg.toString() );
                super.dispatchMessage(msg);

            }
        };

        Looper.loop();
    }


    private void doLongRunningOperation(Message msg) {
        Log.e(TAG, "doLongRunningOperation in handler: what " + Integer.toString(msg.what) );
        Log.e(TAG, "doLongRunningOperation in handler: " + getThreadSignature() );
    }
}

public void onLooperHandlerClicked(View v) {
    Log.e(TAG, "onLooperHandlerClicked: " + getThreadSignature() );
   // Message msg= mLooperThread.mHandler.obtainMessage();
    Message msg = Message.obtain();
    msg.what = 100;
    mLooperThread.mHandler.sendMessage(msg);

    Runnable task = new Runnable() {
        @Override
        public void run() {
            Log.e(TAG, "runnable called at: "+  getThreadSignature());
        }
    };

    Message msg2 = mLooperThread.mHandler.obtainMessage();
    msg2.what = 200;
    mLooperThread.mHandler2.sendMessage(msg);
    mLooperThread.mHandler2.post(task);
    
}

HandlerThread implements the above logic, and can be used by only implementing the handler logic.

4. Service
Android Service is another option to handle async method, the caller calls a service provider to perform a task.
IntentService is a derived class of generic Service, which performs a service request called with startService, and then stops itself automatically.
Generic service will keep the service live when startService is called by caller and the service will stay there until stopService is called by caller.
Service can also implement bind, so it can expose a complex interface for caller to invoke

No comments:

Post a Comment