Saturday, December 7, 2013

Communication between two threads in Android

The source code for this explanation can be found here. That is a bigger example, so, I focus here in the intercommunication portion between two threads. Notice that one of the threads could be the UI but it does not have to (in our case it's not). The direction does not matter either. This is basically general enough.

In the example we have a thread pulling data from the microphone and storing it in a buffer. Then wants to let know another thread in charge of processing the data, that the data is available. That way the processing thread does not have to be polling/checking all the time.

So, to the bottom line... to do this:

1. We declare a handle. All what this is, is something that listens for the message to come. So, the handle has to be created in the receiving thread, duh! We do that in the line within ProcessorThread.java:
  public Handler mHandler;               // Processor handler  
2. We send messages to that handle. As we are going to do that likely from a different thread than the receiving thread, we need to know what is the handle. Like any time that you want to access an object in a different object, you have several ways. One is to pass the reference to that object, for instance, in the constructor, somehow. The other (which we use here) is to simply declare that object "public", so, visible to other objects outside the containing object. See that in the line of code above. So, to send a message to it, all what we need to do in the Recording Thread is to detail the path to the handler:
Message.obtain(scope_screen_view.ProcessorThread.mHandler,   
                          scope_screen_view.ProcessorThread.DO_PROCESSING, "").sendToTarget();
The first parameter is the reference, while the second is what message to send (which is also defined in this case in the receiving thread). It is just a constant, so, it could be defined anywhere, but makes sense from an overall object definition to be inside the receiving one.

3. Finally we need to create the handler itself, i.e., what tells what to do with the received messages. That, again, is done within the ProcessorThread:
 Looper.prepare();  
 mHandler = new Handler()  
 {  
     public void handleMessage(Message msg)  
     {  
         switch (msg.what)  
         {  
             case DO_PROCESSING:  
                 // GOT NEW DATA. ANALYZE  
                 int[] intermediate_buffer;  
                 int[] intermediate_buffer_2;  
                 synchronized(source_buffer){  
                     intermediate_buffer=source_buffer.get();  
                 }  
                 if (Scope.JNI==false) process(intermediate_buffer);  
                 else {  
                        intermediate_buffer_2=processjni(intermediate_buffer);  
                        synchronized(destiny_buffer){  
                              destiny_buffer.put(intermediate_buffer_2);  
                         }  
                 }   
                 break;  
         }  
     }   
};  
Looper.loop();
The key things here are:
a/ The handleMessage method which gets the message and selects what to do with it. Within the code you can see what is done with a switch-case approach. In this case, where we had only one message, we could have used "if" too... So, then, based on the case, we just go execute some piece of code (in this case, to process the new audio samples that were placed in the Audio Buffer).
b/ The Looper is the class used to run a message loop for a thread. Threads, unlike the main UI, do not have by default a message loop associated with them. So, that is what we do here. We place the handler between the Looper.prepare and the Looper.loop.
Note: so, if you are doing a handler in the main UI, you just need to declare/create it, but no looper is needed. For instance, to launch different AsyncTasks from the main UI, depending on the incoming messages, this is all what you would do in the UI:
 private Handler threadHandler = new Handler() {  
           public void handleMessage(android.os.Message msg) {            
           switch(msg.what){  
                case ACK_PLAYED:  
                     new async_recorder(getApplicationContext(),threadHandler,audio_buffer).execute();  
                     break;  
                case ACK_RECORDED:  
                     new async_processor(getApplicationContext(),threadHandler,audio_buffer).execute();
                     break;       
                case ACK_LOADED:  
                     new async_processor(getApplicationContext(),threadHandler,audio_buffer).execute();  
                     break;  
                case ACK_PROCESSED:  
                     mResult.setText((String)msg.obj);  
                     break;  
                case ACK_SAVED:  
                     mResult.setText("SAVED");  
                     break;  
           }            
           }  
      };  

4/ This is it. Nevertheless, there is one small tricky thing related to threads.When you got a thread (without handler) running and want to stop it (for instance, when you move your app to background), the traditional way is by using a variable, within the thread, that gates the execution and that you can manipulate from outside. For instance, one would put the thread code whitin a "while (running) {//code}" and then, using a thread user defined method turn running true (to run) or false (to exit). The code of the example does that. Within the thread to be stopped we have:
 public void run() {  
           Looper.prepare();  
           mHandler = new Handler()  
           {  
                public void handleMessage(Message msg)  
                {  
                     switch (msg.what)  
                     {  
                     case DO_PROCESSING:  
                          // Do whatever... 
                          break; 
                     case "other one":  
                          // Do whatever else... 
                          break;   
                     }  
                }   
           };  
           Looper.loop();   
           while(running){ // <<< This is what we are talking about
           }  
   }  
Although the "running" variable is private, we can externally change it through it with a public method that the class exposes:
   public void setRunning(boolean b) {  
        Log.d("MyActivity", "RUNNING "+b);  
     running = b;  
   } 
So, that when we want to stop it, for instance, when the surface is destroyed, we just do:
public void surfaceDestroyed(SurfaceHolder holder) {  
           boolean retry = true;  
           ProcessorThread.setRunning(false);  
           while (retry) {  
                     ProcessorThread.join();  
                     retry = false;  
                }  
           }  
Nevertheless, in our case we have the Looper waiting for messages to come. So, while the looper is running, the thread will just not stop. To stop it you just got to stop the looper. That is done in the call when surfaceDestroyed calls:
ProcessorThread.mHandler.getLooper().quit();
So, I think in this case you basically don't need to use the "running=false" approach... I tested it and it seems to be the case but if anybody knows otherwise, please let me know...
Cheers!

PS.: Please, click here to see an index of other posts on Android.

No comments:

Post a Comment