Search code examples
javaandroidmemory-leaksfirebaseleakcanary

Is this my code or Firebase code causing this leak?


I am getting a lot of memory leaks in an application I created. I created a very simple app to reproduce the problem. This application just makes a reference to the FirebaseDatabase and sets up a ChildEventListener. When the user clicks the button it adds a record to the database, and starts a new activity which does System.gc().

Pressing the button multiple times will cause Leak Canary to generate a dump.

MainActivity.java:

public class MainActivity extends AppCompatActivity {

private FirebaseDatabase firebaseDatabase;
private DatabaseReference dbRef;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    firebaseDatabase = FirebaseDatabase.getInstance();
    dbRef = firebaseDatabase.getReference("leak");
    dbRef.addChildEventListener(new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
        }
    });

    findViewById(R.id.btn_leak).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            dbRef.child(UUID.randomUUID().toString()).setValue("Yes");
            Intent leakIntent = new Intent(getApplicationContext(), LeakActivity.class);
            startActivity(leakIntent);
        }
    });

}

}

LeakActivity.java:

public class LeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.leak);

        System.gc();
    }
}

Due to the post limit, the leak canary log is here.

Am I doing something wrong in my code, or is this related to Firebase?

EDIT: @qbix's answer seemed to work. For others, here is the working version of MainActivity.java:

public class MainActivity extends AppCompatActivity {

private FirebaseDatabase firebaseDatabase;
private DatabaseReference dbRef;
private ChildEventListener dbListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    firebaseDatabase = FirebaseDatabase.getInstance();
    dbRef = firebaseDatabase.getReference("leak");
    dbListener = getDbListener();
    dbRef.addChildEventListener(dbListener);

    findViewById(R.id.btn_leak).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            dbRef.child(UUID.randomUUID().toString()).setValue("Yes");
            Intent leakIntent = new Intent(getApplicationContext(), LeakActivity.class);
            startActivity(leakIntent);
        }
    });
}

@Override
protected void onStop() {
    dbRef.removeEventListener(dbListener);
    super.onStop();
}

private ChildEventListener getDbListener(){
    return new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };
}

}


Solution

  • I haven't used LeakCanary, so this is just an educated guess.

    ChildEventListeners need to be unregistered when they are no longer needed. Often listeners are added and removed in activity lifecycle methods, such as onCreate() and onDestroy(). Instead of creating an anonymous listener, create an object of that type and remove it using Query.removeEventListener() when no longer needed to see if that eliminates the leak report.