//
you're reading...

Programming

The Downside of Using TimerTask with Handler in a Multithreaded Application

Good day to you All,

I would like to discuss on the downside of using TimerTask with a Handler, in a multithreaded application for Android, and why using IntentService along with TimerTask is a good idea. Not long ago, there is a discussion on enhancing the current Speech Timer for Android. The app should be able to send a continuous signal to a device. The signal will be compromised of several different tunes, running interchangeably. The continuous signal should be started or stopped by a property value (turned on/off by a button) and whether the audio jack is plugged or unplugged.

Seems like a multithread work here. Since my existing code is already using Timer task, so in order not to make too many changes, I decided to implement the change using TimerTask. I also decide to make a simple app to test the change in isolation. Below is the screen shot.

Screen Shot 2014 09 25 at 11 20 24 am

The Green Light, Yellow Light, Red Light, Buzzer buttons control the tunes. An individual tune will activate a light in the connected device. If the button is turned on, the tune will be sent to the device. The oscillation frequency controls how many times in a second should the app switch between tunes. The Audio Light button along with the audio jack controls whether to send the signal or not. Only when the Audio Light is on and the audio jack is plugged then the signal will be sent. Below is my initial code. 

	/** Other codes in the app
		.....
	*/

	/** The multithread code */

    TimerTask scanTask;
	Timer t;
	private final Handler handler = new Handler();

    private void playIfPlugged() {
    	boolean connectedHeadphones = /** The code to get the status of the head phones */
    	if(connectedHeadphones && audioLightOn){

	    		//Clean any previous run
	    		if(scanTask!=null){
					t.cancel();
		   	     	scanTask=null;
				}
  	    
	    		t = new Timer();
		    	scanTask = new TimerTask() {
		            public void run() {
		                    handler.post(updateTimerThread);
		            }
				};
		        t.schedule(scanTask, 0,1000/progressChanged);
    	}else{
    		if(scanTask!=null){
    			t.cancel();
    	   	    scanTask=null;
			}
			//Release the sounds as well
    	}
    }
    
	Runnable updateTimerThread = new Runnable() {
		
		public void setPlayerQueue(int makeFalse){
			/** Determines which tune to play next */
		}
		
		public void playMe(int soundId){
			//initiate
			if(sounds == null){
 	     	 	   /** Load the sounds */
 	     	}

			//play sound
			sounds.play(soundId, 0.2f, 0.2f, 0, -1, 1f);
			
			/** Wait for a moment */
    		
    		//stop the sound
    		if(sounds!=null){
        		/** Release the sounds */
        	}
		}
		
		public int numberOfSounds(){
			/** Returns the number of tunes for the signal */
		}
		
		public void run() {
			/** Determines which tune to be played by calling playMe() and the other methods in the Runnable */
        }
    };

	/** Other code in the app */

The app seems to work okay with the above logic, but when I unplug the headset or turn the Audio Light off, the sound signal does not immediately stop, and the UI does not respond immediately either. It appears there is a multithreading issue, because TimerTask should have issued an independent thread, but here somehow it is interfering with the main thread. I spotted the problem on the sounds variable, it is shared by the independent thread and the UI thread. But even after assigning the sounds variable just to the independent thread the problem persisted. There must be a way to fix this. But how?

So in order to avoid any possible multithreading issue, I decided to use an IntentService along with the TimerTask. I cannot only use IntentService because there is no way for an external thread to stop the IntentService work. After carefully putting the logic of playMe() method to an IntentService, my code looks like below.

/** Other codes in the app */

private void playIfPlugged() {
    	boolean connectedHeadphones = //Get the status of the headphones.
    	
	   	//Clean any previous run
	    if(scanTask!=null){
					t.cancel();
		   	     	scanTask=null;
		}
	    		
	    t = new Timer();
		scanTask = new TimerTask() {
		      public void run() {
		           if(connectedHeadphones && audioLightOn){
			            	Intent mServiceIntent = new Intent(MainActivity.this, SpeechTimerIntentService.class);
				    		mServiceIntent.putExtra(SpeechTimerConstants.OSILLATION_FREQ, progressChanged);
				    		
				    		/** Also put another extra to mServiceIntent to signify which tune to play */
		           }
						
		      }
		};
		t.schedule(scanTask, 0,1000/progressChanged);
    }

/** Other codes in the app */

/** The handler method in the IntentService */

protected void onHandleIntent(Intent intent) {
		
		int osiFreq = intent.getIntExtra(SpeechTimerConstants.OSILLATION_FREQ, 1);
		boolean greenOn = intent.getBooleanExtra(SpeechTimerConstants.GREEN_ON, false);

		/** Get other extras as well */
		
		int soundId = 0;
		SoundPool sounds= new SoundPool(5, AudioManager.STREAM_MUSIC,0);
		if(greenOn)
				soundId = sounds.load(this, R.raw.timelight_green,1);

		/** Do the same if clause for other tunes */
				
		sounds.play(soundId, 0.2f, 0.2f, 0, -1, 1f);
				
		//wait for a moment code
				
		//stop the sound
		if(sounds!=null){
		    		sounds.release();
		    		sounds = null;
		}
    	
	}

The multithreading issue seems to disappear now, the UI is now responsive, since there is no more variable shared together between the threads. But stopping the signal still has the latency issue. It got me frustated, until I realized there is probably a queue, in which all the IntentService is fired to, and the latency comes from the fact the IntentService jobs are not executed immediately. So I do a little tweak to the onHandleIntent() method. In there I also check whether the headphone is plugged at the moment. The code changed like below. Unfortunately, I am not able to get the value of the Audio Light button of the moment because how the Android framework is designed. But hey, this is already good enough, in real life scenario, the Audio Light value will be a property value, which is seldom changed.

/** The handler method in the IntentService */

protected void onHandleIntent(Intent intent) {
		
		int osiFreq = intent.getIntExtra(SpeechTimerConstants.OSILLATION_FREQ, 1);
		boolean greenOn = intent.getBooleanExtra(SpeechTimerConstants.GREEN_ON, false);

		/** Get other extras as well */
		
		AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
    	boolean connectedHeadphones = am.isWiredHeadsetOn();
    	
    	if(connectedHeadphones){
			int soundId = 0;
			
			SoundPool sounds= new SoundPool(5, AudioManager.STREAM_MUSIC,0);
			if(greenOn)
				soundId = sounds.load(this, R.raw.timelight_green,1);

			/** Do the same if clause for other tunes */
				
			sounds.play(soundId, 0.2f, 0.2f, 0, -1, 1f);
				
			//wait for a moment code
				
			//stop the sound
			if(sounds!=null){
		    		sounds.release();
		    		sounds = null;
			}
		}
    	
	}



Do you enjoy this post? Enter your e-mail address below to receive articles like this one in your mailbox.

Free Updates!

Learn how to grow your indie business while keeping your day job.

Categories

Archives

Keep updated!

Don't miss out on new articles!