3

I have a foreground service which plays an alarm for one minute and then closes by itself. I am using Handler for closing the service after one minute by posting a delayed message.
The Handler class uses singleton approach (for a reason which is not relevant to this post). Although the code works fine, the problem is that the service doesn't get garbage collected even after its onDestroy() method is called.

Following is the code:-

AlarmService.java:-

public class AlarmService extends Service {
private MediaPlayer alarm;
private AlarmHandler alarmHandler;
private static final int DELAY = 60 * 1000;

//onBind method

@Override
public void onCreate() {
    super.onCreate();
    alarmHandler = AlarmHandler.getInstance();
    alarmHandler.attachService(this);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    clearPreviousAlarms();          //if any
    alarm = MediaPlayer.create(this, R.raw.kalimba);
    startForeground(1, NotificationUtil.buildNotification(this));
    alarm.start();
    alarmHandler.sendEmptyMessageDelayed(1, DELAY);

    return START_NOT_STICKY;
}

private void clearPreviousAlarms() {
    if (alarm != null) {
        alarm.release();
    }
    if (alarmHandler.hasMessages(1)) {
        alarmHandler.removeMessages(1);
    }

}

@Override
public void onDestroy() {
    super.onDestroy();
    MyApp.refWatcher.watch(this);    //using leakCanary for detecting the memory leak
    alarm.release();
    if (alarmHandler.hasMessages(1))
        alarmHandler.removeMessages(1);
}

}

AlarmHandler.java:-

class AlarmHandler extends Handler {
private static AlarmHandler alarmHandler;
private WeakReference<AlarmService> alarmService;

private AlarmHandler() {
}

public void attachService(AlarmService alarmService) {
    this.alarmService = new WeakReference<AlarmService>(alarmService);
}

public static AlarmHandler getInstance() {
    if (alarmHandler == null) {
        alarmHandler = new AlarmHandler();
    }
    return alarmHandler;
}

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case 1: {
            alarmService.get().stopSelf();
        }
        break;
    }
}

}

NOTE:-

  • As you can see above, memory leak is still being caused despite of WeakReference being used.

  • I am using LeakCanary for memory leak detection.

syed_noorullah
  • 155
  • 2
  • 13
  • 1
    I don't know about Leak Canary, but if I run your code as part of a sample app and use the [Android Studio Profiler](https://developer.android.com/studio/profile/memory-profiler), I can see that there is one instance of AlarmService created. If I force the garbage collector to run (click on trash can icon in memory profiler), the Alarm Service instance vanishes. So I think maybe there is nothing wrong with your setup but that the garbage collector does not run as often as one might think. – Bö macht Blau Oct 21 '19 at 19:38
  • @0X0nosugar so.... there's nothing to worry about ?? – syed_noorullah Oct 21 '19 at 19:46
  • 1
    So it seems to me. I've heard from others that the garbage collector sometimes takes its time - I guess it's optimized to save energy and only will start running often as soon the device gets low on memory – Bö macht Blau Oct 21 '19 at 19:55
  • @0X0nosugar well that's a new one. I also am trying to use Android Profiler like you did but it shows "No supported devices" although i am using samsung s5 (android 6) and an emulator device running android 8. – syed_noorullah Oct 21 '19 at 20:19
  • Sorry that you're having an issue there - this never happened to me so far, so I don't know how to fix it :( – Bö macht Blau Oct 22 '19 at 16:11
  • 1
    @0X0nosugar no problem mate... i think i figured it out. When i ran the above code on an emulator (Google 6P android 6) leakCanary detected nothing and everything was perfect. Another thing that proved that the AlarmService object was GC'ed was the NullPointer exception which came due to the code changes i made **intentionally** at some points to check if AlarmService object was GC'ed or not. If it wouldn't have been GC'ed there would've been no any NullPointer exception :) – syed_noorullah Oct 22 '19 at 16:27
  • 1
    @0X0nosugar So i think this somehow proves the point that you made in your comment above i.e:- "So it seems to me. I've heard from others that the garbage collector sometimes takes its time - I guess it's optimized to save energy and only will start running often as soon the device gets low on memory" So, Garbage collecter of the emulator device responded "instantly" while the one of the physical device (Samsung S5) didn't. – syed_noorullah Oct 22 '19 at 16:31
  • 1
    Another thing to be considered with the Leak Canary method in `onDestroy()` is that [Service.onDestroy() is not guaranteed to be called](https://stackoverflow.com/questions/14147846/situations-when-a-services-ondestroy-method-doesnt-get-called) But good to know that we both think your code is ok now. Happy coding :) – Bö macht Blau Oct 22 '19 at 16:37

0 Answers0