Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Android – Multithreading in a UI environment

For the past few days, I've been getting up to speed with concurrent thread execution in Java, namely for Android.

I only just stumbled on to this excellent blog post on the topic. I will therefore organise the rest of this post in reference to the link - do read it!

Threading as per Android Docs.

The developer documentation states,

public class MyActivity extends Activity {

    [ . . . ]
    // Need handler for callbacks to the UI thread
    final Handler mHandler = new Handler();

    // Create runnable for posting
    final Runnable mUpdateResults = new Runnable() {
        public void run() {
            updateResultsInUi();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        [ . . . ]
    }

    protected void startLongRunningOperation() {

        // Fire off a thread to do some work that we shouldn't do directly in the UI thread
        Thread t = new Thread() {
            public void run() {
                mResults = doSomethingExpensive();
                mHandler.post(mUpdateResults);
            }
        };
        t.start();
    }

    private void updateResultsInUi() {

        // Back in the UI thread -- update our UI elements based on the data in mResults
        [ . . . ]
    }
}

You'll find a working demo of this with useful debug info posted to LogCat; the raw source will implement as Runnable, which is typically preferred.

FORK IT!: Source @Github

Handler: run the new thread and use the handler to post a runnable which updates the GUI.

The important bits are shown below

    final Handler mHandler = new Handler(){
        public void handleMessage(Message msg) {
            if (msg.what==0){
                Log.d(this.getLooper().getThread().getName(), "mHandler");
                Toast.makeText(getApplicationContext(), "Whooo: " + msg.what, Toast.LENGTH_LONG).show();
            }
        }       
    };

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textOutput = (TextView)findViewById(R.id.textOutput);
        buttonPush = (Button)findViewById(R.id.buttonPush); // Get button from xml
        buttonPush.setOnClickListener(this); 
    }

    public void onClick(View v) {
        switch(v.getId()){
        case R.id.buttonPush:
            startLongRunningOperation();
        default:
            break;
        }
    }

    protected void startLongRunningOperation() {

        // DISPLAYING UR PROGRESS DIALOG
        final ProgressDialog backgroundTask = ProgressDialog.show(this, "", "Loading", true);

        // Fire off a thread to do some work that we shouldn't do directly in the UI thread
        Thread t = new Thread() {
            public void run() {
                try {
                    Log.d(this.getName(),"bound to "+mHandler.getLooper().getThread().getName());
                    mResults = doSomethingExpensive();

                    // Create runnable for posting
                    mHandler.post(new Runnable() {

                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            updateResultsInUi(mResults);
                        }
                    });

                    backgroundTask.dismiss();
                }catch (Exception e){
                    //TODO
                }
                Log.d(this.getName(),"sending message to "+mHandler.getLooper().getThread().getName());
                mHandler.sendEmptyMessage(0);
            }
        };
        t.start();
    }  

    private void updateResultsInUi(int mResults) {
        // Back in the UI thread -- update our UI elements based on the data in mResults
        textOutput.setText("Received: " + mResults);
    }    

    private int doSomethingExpensive(){
        try {
            Log.d(Thread.currentThread().getName(), "background operation starting");
            Thread.sleep(1000);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return(RESULT_OK);
    }

}

FORK IT!: Threading 3 @ Github

Looper

Just completed! I should be able to setup the pipeline thread pattern as long as I can hook into the same thread, rather than spawning new threads on each onClick() event.

FORK IT!: Threading 4 @ Github

Pipeline Thread Execution

Pipelining: My Way

This is completely my own work, and was based on Threading 4 above. The idea behind this approach was to instantiate a single thread, and then assign tasks to that thread. On each click, the same thread is used, much rather than spawning new threads. Do notice the much larger code overhead to achieve this pipeline concurrency pattern.

FORK IT!: Threading 5 @ Github

Ivan's Approach: Great for learning!

This example is largely based on Ivan's code; I cut it down into a far more generic model that's easier to digest. It sets up a single pipeline thread.

Ivan's code is setup to span four .java files; I was able to shrink this down to two so far. However, at circa 250 lines of code it still does seem like much to achieve pipelining.

FORK IT!: Threading 2 @ Github

comments powered by Disqus