2619 2619 - 4 months ago 25
Android Question

Service has leaked IntentReceiver in android

I am making an android app. I am making a service that runs in the background all the time when user checks a

checkbox
and it should stop when user
unckeck
it. So it does work when I check the box and it also shows "Service start" Toast but when I
uncheck
the
checkbox
then it shows the Toast that says "Service stopped" but it does not stop playing the sound which started when the service was started. The only way to stop that sound is to turn off my mobile and then turn it on.

Here is my java code for starting and stopping service

public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();

this.registerReceiver(this.batteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

return START_STICKY;
}
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
}


Here is my xml file

<CheckBoxPreference
android:title="Enable/Disable Alarm"
android:defaultValue="true"
android:key="cbAlarm"
android:summary="Enable or disable alarm" />


and here is my logcat

05-06 09:13:53.230: D/dalvikvm(5351): GC_EXTERNAL_ALLOC freed 39K, 50% free 2725K/5379K, external 0K/0K, paused 124ms
05-06 09:13:53.355: D/dalvikvm(5351): GC_EXTERNAL_ALLOC freed 21K, 49% free 2779K/5379K, external 504K/518K, paused 18ms
05-06 09:13:53.420: D/CLIPBOARD(5351): Hide Clipboard dialog at Starting input: finished by someone else... !
05-06 09:13:57.975: I/MediaPlayer(5351): uri is:content://media/internal/audio/media/44
05-06 09:13:57.975: I/MediaPlayer(5351): inside getAudioFilePath: content://media/internal/audio/media/44
05-06 09:13:57.980: I/MediaPlayer(5351): The actual path is:/system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: I/MediaPlayer(5351): path is: /system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: I/MediaPlayer(5351): file path found for DRM file:path is: /system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: E/MediaPlayer-JNI(5351): setDataSource: outside path in JNI is ?x@
05-06 09:14:20.525: E/ActivityThread(5351): Service com.zafar.test.BatteryService has leaked IntentReceiver com.zafar.test.BatteryService$1@40536548 that was originally registered here. Are you missing a call to unregisterReceiver()?
05-06 09:14:20.525: E/ActivityThread(5351): android.app.IntentReceiverLeaked: Service com.zafar.test.BatteryService has leaked IntentReceiver com.zafar.test.BatteryService$1@40536548 that was originally registered here. Are you missing a call to unregisterReceiver()?
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.LoadedApk$ReceiverDispatcher.<init>(LoadedApk.java:756)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:551)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:866)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ContextImpl.registerReceiver(ContextImpl.java:853)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ContextImpl.registerReceiver(ContextImpl.java:847)
05-06 09:14:20.525: E/ActivityThread(5351): at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:318)
05-06 09:14:20.525: E/ActivityThread(5351): at com.zafar.test.BatteryService.onStartCommand(BatteryService.java:35)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2043)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ActivityThread.access$2800(ActivityThread.java:117)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:998)
05-06 09:14:20.525: E/ActivityThread(5351): at android.os.Handler.dispatchMessage(Handler.java:99)
05-06 09:14:20.525: E/ActivityThread(5351): at android.os.Looper.loop(Looper.java:130)
05-06 09:14:20.525: E/ActivityThread(5351): at android.app.ActivityThread.main(ActivityThread.java:3691)
05-06 09:14:20.525: E/ActivityThread(5351): at java.lang.reflect.Method.invokeNative(Native Method)
05-06 09:14:20.525: E/ActivityThread(5351): at java.lang.reflect.Method.invoke(Method.java:507)
05-06 09:14:20.525: E/ActivityThread(5351): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
05-06 09:14:20.525: E/ActivityThread(5351): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
05-06 09:14:20.525: E/ActivityThread(5351): at dalvik.system.NativeStart.main(Native Method)
05-06 09:14:37.810: D/CLIPBOARD(5351): Hide Clipboard dialog at Starting input: finished by someone else... !


Help please. Where am I making mistake.

Edit

private BroadcastReceiver batteryInfoReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);

if(plugged == 2) {

SharedPreferences getAlarms = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String alarms = getAlarms.getString("ringtone", "default ringtone");
//getting uri from MediaStore via filepath i.e. content://media/internal/audio/media/29
Uri uri = Uri.parse(alarms);

mMediaPlayer = new MediaPlayer();
try {
mMediaPlayer.setDataSource(context, uri);
final AudioManager audioManager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mMediaPlayer.prepare();
mMediaPlayer.start();
}
} catch (IOException e) {
//System.out.println("OOPS");
e.getStackTrace();
}
} else if(plugged == 0) {
mMediaPlayer.stop();
}

}
};

Answer

Make sure you call unregisterReceiverbefore the onDestroy finishes. After onDestroy the Service's context is invalid, so all registered intent receivers will stop operating. Android warns you with the "Leaked intent receiver" exception that your app is misbehaving.

The music does not stop because the receiver is not called any more. You should ensure that mMediaPlayer.stop is called before the return from onDestroy. I suggest you extracting all this functionality into separate methods, thus it will be much easier to compose your application.

private void startMusic(int level);
private void stopMusic();

Also, be careful not to leak the mMusicPlayer if due to error you receive two plugged == 2 intents in a row. It is always better to write your code in a defensive way, rather than relying on the invoker's additional knowledge.

The exception handling is also quite dangerous in your code. If the music starting failed, it may happen that you get a null pointer exception when you want to stop the music.

Hope this helps.