Android – Multithreading in a UI environment

来自:http://www.aviyehuda.com/blog/2010/12/20/android-multithreading-in-a-ui-environment/

Why do we need multithreading in Android applications?

Let’s say you want to do a very long operation when the user pushes a button.
If you are not using another thread, it will look something like this:

1 ((Button)findViewById(R.id.Button01)).setOnClickListener(          
2              new OnClickListener() {
3        
4          @Override
5          public void onClick(View v) {
6             int result = doLongOperation();
7             updateUI(result);
8          }
9       });

What will happen?
The UI freezes. This is a really bad UI experience. The program may even crash.



The problem in using threads in a UI environment
So what will happen if we use a Thread for a long running operation.
Let’s try a simple example:

01 ((Button)findViewById(R.id.Button01)).setOnClickListener(
02 new OnClickListener() {
03           
04          @Override
05          public void onClick(View v) {
06              
07             (new Thread(new Runnable() {
08                 
09                @Override
10                public void run() {
11                     int result = doLongOperation();
12                     updateUI(result);
13                }
14             })).start();
15              
16          }

The result in this case is that the application crashes.
12-07 16:24:29.089: ERROR/AndroidRuntime(315): FATAL EXCEPTION: Thread-8
12-07 16:24:29.089: ERROR/AndroidRuntime(315): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
12-07 16:24:29.089: ERROR/AndroidRuntime(315): at ...

Clearly the Android OS wont let threads other than the main thread change UI elements.

But why?
Android UI toolkit, like many other UI environments, is not thread-safe.



The solution

  • A queue of messages. Each message is a job to be handled.
  • Threads can add messages.
  • Only a single thread pulls messages one by one from the queue.

The same solution was implemented in swing (Event dispatching thread
and SwingUtilities.invokeLater() )



Handler
The Handler is the middleman between a new thread and the message queue.


Option 1 – Run the new thread and use the handler to send messages for ui changes

01 final Handler myHandler = new Handler(){
02    @Override
03 public void handleMessage(Message msg) {
04       updateUI((String)msg.obj);
05 }
06     
07 };
08  
09 (new Thread(new Runnable() {
10     
11    @Override
12    public void run() {
13       Message msg = myHandler.obtainMessage();
14        
15       msg.obj = doLongOperation();
16        
17       myHandler.sendMessage(msg);
18    }
19 })).start();

* keep in mind that updating the UI should still be a short operation, since the UI freezes during the updating process.

Other possibilities:
handler.obtainMessage with parameters
handler.sendMessageAtFrontOfQueue()
handler.sendMessageAtTime()
handler.sendMessageDelayed()
handler.sendEmptyMessage()


Option 2 – run the new thread and use the handler to post a runnable which updates the GUI.

01 final Handler myHandler = new Handler();
02              
03             (new Thread(new Runnable() {
04                 
05                @Override
06                public void run() {
07                   final String res = doLongOperation();
08                   myHandler.post(new Runnable() {
09                       
10                      @Override
11                      public void run() {
12                         updateUI(res);
13                      }
14                   });
15                }
16             })).start();
17              
18          }








Looper
If we want to dive a bit deeper into the android mechanism we have to understand what is a Looper.
We have talked about the message queue that the main thread pulls messages and runnables from it and executes them.
We also said that each handler you create has a reference to this queue.
What we haven’t said yet is that the main thread has a reference to an object named Looper.
The Looper gives the Thread the access to the message queue.
Only the main thread has executes to the Looper by default.

Lets say you would like to create a new thread and you also want to take advantage of the message queue functionality in that thread.

01 (new Thread(new Runnable() {
02  
03                   @Override
04                   public void run() {
05  
06                      innerHandler = new Handler();
07                       
08                      Message message = innerHandler.obtainMessage();
09                      innerHandler.dispatchMessage(message);
10                   }
11                })).start();

Here we created a new thread which uses the handler to put a message in the messages queue.

This will be the result:
12-10 20:41:51.807: ERROR/AndroidRuntime(254): Uncaught handler: thread Thread-8 exiting due to uncaught exception
12-10 20:41:51.817: ERROR/AndroidRuntime(254): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
12-10 20:41:51.817: ERROR/AndroidRuntime(254): at android.os.Handler.(Handler.java:121)
12-10 20:41:51.817: ERROR/AndroidRuntime(254): at ...



The new created thread does not have a Looper with a queue attached to it. Only the UI thread has a Looper.
We can however create a Looper for the new thread.
In order to do that we need to use 2 functions: Looper.prepare() and Looper.loop().

01 (new Thread(new Runnable() {
02  
03                @Override
04                public void run() {
05  
06                   Looper.prepare();
07                   innerHandler = new Handler();
08                          
09                   Message message = innerHandler.obtainMessage();
10                   innerHandler.dispatchMessage(message);
11                   Looper.loop();
12                }
13             })).start();


If you use this option, don’t forget to use also the quit() function so the Looper will not loop for ever.

1 @Override
2    protected void onDestroy() {
3       innerHandler.getLooper().quit();
4       super.onDestroy();
5    }




AsyncTask
I have explained to you that a Handler is the new thread’s way to communicate with the UI thread.
If while reading this you were thinking to yourself, isn’t there an easier way to do all of that… well, you know what?! There is.

Android team has created a class called AsyncTask which is in short a thread that can handle UI.

Just like in java you extend the class Thread and a SwingWorker in Swing, in Android you extend the class AsyncTask.
There is no interface here like Runnable to implement I’m afraid.

01 class MyAsyncTask extends AsyncTask<Integer, String, Long> {
02        
03       @Override
04       protected Long doInBackground(Integer... params) {
05           
06          long start = System.currentTimeMillis();
07          for (Integer integer : params) {
08             publishProgress("start processing "+integer);
09             doLongOperation();
10             publishProgress("done processing "+integer);
11          }
12           
13          return start - System.currentTimeMillis();
14       }
15  
16        
17        
18       @Override
19       protected void onProgressUpdate(String... values) {
20          updateUI(values[0]);
21       

你可能感兴趣的:(thread,exception,android,UI,Integer,multithreading)