0

I'm getting a memory leak on Android reported by LeakCanary but I can't find out how to trace it.

I have an activity called Splash which calls one Service to fetch configuration data, then via a handler pings back to the activity.

//Started like this in the Splash activity
ConfigDumpService.start(this, this, BaseService.DATA_CALLBACK_ACTION_SIMPLE);

where the start method is:

public static void start(final Context context, final Handler.Callback handlerCallback, final int callbackAction) {
    final Messenger messenger = new Messenger(new Handler(handlerCallback));
    final Intent intent = new Intent(context, ConfigDumpService.class);
    intent.putExtra(BaseService.PARAM_MESSENGER, messenger);
    intent.putExtra(BaseService.PARAM_CALLBACK_ACTION, callbackAction);
    context.startService(intent);
}

The Splash activity implements Handler.Callback

@Override
public boolean handleMessage(final Message msg) {
    L.p("In Splash handleMessage(), thread: " + Thread.currentThread().getName());

    if (BaseService.DATA_RETRIEVE_SUCCESS == msg.arg1) {
        L.p("Message from ConfigService service is SUCCESS!");
        startApp();
    } else {
        L.p("Message from ConfigService service is FAIL!");
        showCannotContinueDialog();
    }
    return true;
}

The ConfigDumpService

// Previously fetched some data...

final Message message = Message.obtain();
message.setData(bundle);

if (successful) {
    message.arg1 = BaseService.DATA_RETRIEVE_SUCCESS;
} else {
    message.arg1 = BaseService.DATA_RETRIEVE_FAIL;
}

try {
    final Messenger messenger = startIntent.getParcelableExtra(BaseService.PARAM_MESSENGER);
    messenger.send(message);
} catch (RemoteException e) {
    L.p("In onHandleIntent RemoteException");
    e.printStackTrace();
}

stopSelf();

Another place where a handler is created in the Splash activity is to start the main activity after a small delay:

final Handler handler = new Handler();
final Runnable mRunnable = new Runnable() {
    public void run() {

        DialogManager.removeAllDialogs();

        // Let the base activity know we're just starting the app
        BaseActivity.startWithHome(Splash.this, homeId, false, true);
        Splash.this.finish();
    }
};
handler.postDelayed(mRunnable, splashImageLoadWasSuccessful ? mSplashScreenWaitMilliseconds : 0);

The call stack isn't super helpful. I'd appreciate any tips.

Thanks

enter image description here

zundi
  • 2,361
  • 1
  • 28
  • 45
  • WIthout code there's no way we can answer this. How does your service get (and save) a way to call the activity? WHere is that reference cleaned up? – Gabe Sechan Dec 15 '17 at 20:12
  • Agreed, a [mcve] would help, particularly showing this `Splash` activity and this `Handler`. – CommonsWare Dec 15 '17 at 20:34
  • @CommonsWare Added code. Thanks. – zundi Dec 15 '17 at 20:57
  • `Splash.this.finish();` might be better off as `finish();` – petey Dec 15 '17 at 21:04
  • You should be getting Lint warnings from that use of `Handler`, per Peppermint's answer. FWIW, I'm not sure why you are using a `Service` here. – CommonsWare Dec 15 '17 at 21:58
  • In the Splash activity I don't continue until the config fetch is complete, however in other parts of the app I want to do a config fetch independent of the activity that started it. That's why it's a `Service` and not, say, an `AsyncTask`. Is that reasoning correct? – zundi Dec 16 '17 at 23:29

1 Answers1

1

I think it's related to how you instantiate your Handler. Since you are implementing the Handler.Callback in your SplashActivity your are keeping a hard reference in your message queue. Instead You should implement your own Handler class and if it is an inner class it should be static. And within this custom Handler you should have a WeakReference to the Activity you pass in. This post explains the problem in more detail and shows you how to solve this issue. Hope this helps ;)

Peppermint Paddy
  • 1,269
  • 9
  • 14
  • Just out of curiosity, once the onHandle() method finishes, the SplashActivity would lose this reference and would be available to be GC'ed, right? Or would the activity's reference be held "forever" in the message queue? – zundi Dec 20 '17 at 16:15