Tolgay Toklar
Tolgay Toklar

Reputation: 4343

Changing activity from live data is causing memory leak

My code:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_navigation);
    bottomNavigationView = findViewById(R.id.bottom_navigation);
    subscribeObservers();
}

private void subscribeObservers() {
    if (!sessionManager.getAuthedUser().hasActiveObservers()) {
        sessionManager.getAuthedUser().observe(this, new Observer<AuthResource<LoggedUser>>() {
            @Override
            public void onChanged(@Nullable AuthResource<LoggedUser> loggedUserAuthResource) {
                if (loggedUserAuthResource != null) {
                    switch (loggedUserAuthResource.status) {
                        case AUTHENTICATED:
                            Log.d(TAG, "onChanged: Auth success");
                            break;
                        case LOADING:
                            Log.d(TAG, "onChanged: Auth in progress");
                            break;
                        case NOT_AUTHENTICATED:
                            goToWelcomeScreen();
                            Log.d(TAG, "onChanged: Auth failed");
                            break;
                        case ERROR:
                            Log.d(TAG, "onChanged: Auth error");
                            break;
                    }
                }
            }
        });
    }
}

protected void goToWelcomeScreen() {
    Intent intent = new Intent(this, WelcomeActivity.class);
    finish();
    startActivity(intent);
}

This is my session manager method:

    private void initAuthedUser() {
        authedUser.setValue(AuthResource.loading((LoggedUser) null));
        final LiveData<LoggedUser> source = ribonyRepository.getAuthedUser();
        authedUser.setValue(AuthResource.notAuthenticated(null));
    }

    public LiveData<AuthResource<LoggedUser>> getAuthedUser() {
        return authedUser;
    }

As you can see if i run goToWelcomeScreen method in observer my activity is leaking. Here leak logs:

LibraryLeak(className=com.impact.ribony.activities.MainNavigationActivity, leakTrace=
┬
├─ android.app.ActivityThread
│    Leaking: NO (a class is never leaking)
│    GC Root: System class
│    ↓ static ActivityThread.sCurrentActivityThread
│                            ~~~~~~~~~~~~~~~~~~~~~~
├─ android.app.ActivityThread
│    Leaking: UNKNOWN
│    ↓ ActivityThread.mNewActivities
│                     ~~~~~~~~~~~~~~
├─ android.app.ActivityThread$ActivityClientRecord
│    Leaking: UNKNOWN
│    ↓ ActivityThread$ActivityClientRecord.nextIdle
│                                          ~~~~~~~~
├─ android.app.ActivityThread$ActivityClientRecord
│    Leaking: UNKNOWN
│    ↓ ActivityThread$ActivityClientRecord.activity
│                                          ~~~~~~~~
╰→ com.impact.ribony.activities.MainNavigationActivity
​     Leaking: YES (Activity#mDestroyed is true and ObjectWatcher was watching this)
​     key = 4f1783be-bb8f-45df-96bb-e961b3277a1a
​     watchDurationMillis = 5196
​     retainedDurationMillis = 190
, retainedHeapByteSize=1213860, pattern=instance field android.app.ActivityThread$ActivityClientRecord#nextIdle, description=Android AOSP sometimes keeps a reference to a destroyed activity as a nextIdle client record in the android.app.ActivityThread.mActivities map. Not sure what's going on there, input welcome.)

An interesting thing is if i change my onCreate to below then my activity is not leaking. (I removed goToWelcomeScreen call from subscribeObservers method.)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main_navigation);
    bottomNavigationView = findViewById(R.id.bottom_navigation);
    subscribeObservers();
    goToWelcomeScreen();
}

What can cause to this problem? How can I resolve it?

Thanks

Upvotes: 0

Views: 1174

Answers (2)

Pierre-Yves Ricau
Pierre-Yves Ricau

Reputation: 8349

This is identified by LeakCanary as a "library leak", which means it looks like a known leak in the Android Framework. See the description in the trace you pasted:

Android AOSP sometimes keeps a reference to a destroyed activity as a nextIdle client record in the android.app.ActivityThread.mActivities map. Not sure what's going on there, input welcome.

It sounds like you found a way to make the problem go away, which is a great first step. Looks like if you're switching activities during onCreate() you don't have the issue, but the live data is introducing a delay that's causing this to happen.

Upvotes: 1

bidhu
bidhu

Reputation: 16

First make changes to your goToWelcomeScreen method as suggested below.

protected void goToWelcomeScreen() {
    Intent intent = new Intent(this, WelcomeActivity.class);
    startActivity(intent);        
    finish();
}

Call finish after starting next activity. The reason your activity is leaking is probably because the activity seems to be finished but the live data observers are still active. You've to use ViewModels with live data and attach your activity to the lifecycle of viewmodel.

Doing that ensures that all your active activity observers are destroyed along with the activity.

Case 2: In the second case the activity is not leaking because by the time your sessionManager.getAuthedUser() gets executed your activity is already finished before attaching the observer(since goToWelcomeActivity gets executed by then).

Hope it helped clear your doubts, Cheers!

Upvotes: 0

Related Questions