Android Fragment Memory Leaks

13,112

You keep creating (and attaching) a new instance of your fragment.

Change this:

 MainMenuFragment frag = new MainMenuFragment();

 getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit();

To something like:

Fragment fragment = getSupportFragmentManager().
                   findFragmentById(R.id.fragment_container);
    if ( fragment == null ) {
        fragment = new MainMenuFragment();
            getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.fragment_container, fragment, "SOME_TAG_IF_YOU_WANT_TO_REFERENCE_YOUR_FRAGMENT_LATER")
            .commit();
    } 

The FragmentManager keeps references of the Fragments and handles that by itself. You can remove them if you want (before calling the call to onSaveInstance()) by using .detach() method in the FragmentManager.

You could do:

Fragment f = getSupportFragmentManager().findFragmentByTag("SOME_TAG_IF_YOU_WANT_TO_REFERENCE_YOUR_FRAGMENT_LATER");

if ( f != null ) {
   getSupportFragmentManager().detach(f);
}  

Note: This is all pseudo-code and I didn't test it, there might be typos, but you get the idea

Share:
13,112

Related videos on Youtube

Jake Alsemgeest
Author by

Jake Alsemgeest

Updated on September 14, 2022

Comments

  • Jake Alsemgeest
    Jake Alsemgeest over 1 year

    I'm making a small app in my sparetime and I'm using many fragments. I am fairly new to fragments however I have used many resources to use the appropriately... or so I thought. I am getting memory leaks as my heap continues to grow as I change from fragment to fragment. I had my app implemented with Activities previously and I had reduced the memory leaks significantly. However, now that I am joining the fragment world I feel as though I'm starting at square one when it comes to memory.

    MainActivity.java

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
        // Setting the content view of the Activity
        setContentView(R.layout.main);
    
        // Setting up the actionbar.
        ActionBar actionbar = getActionBar();
        actionbar.setSubtitle("");
        actionbar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
                | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
        actionbar.setTitle("");
        actionbar = null;
    
        MainMenuFragment frag = new MainMenuFragment();
    
        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit();
    }
    

    This MainActivity also sets up the ActionBar.

    MainMenuFragment.java

    public class MainMenuFragment extends Fragment implements OnClickListener, LoaderCallbacks<Cursor> {
    
    private static final String TAG = "MainMenuFragment";
    
    SharedPreferences.Editor prefsEditor;
    SharedPreferences prefs;
    
    Bitmap[] picss = null;
    
    
    static int width;
    
    private static int FRIENDS = 0;
    private static int PEOPLE = 1;
    private static int MAP = 2;
    private static int CAMERA = 3;
    private static int TOP_TILE = 4;
    
    LinearLayout topTile = null;
    LinearLayout mainLayout;
    
    private int NUM_OF_BUTTONS = 4;
    
    ImageButton[] buttonList = null;
    
    Bitmap background;
    
    protected Activity activity;
    protected String placeId = null;
    protected ViewGroup container;
    protected View view;
    
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState); 
        activity = getActivity();
    
        view = getView();
    
        setupObjects(view);
        setupLayoutsAndImages(view);
    
        // Populate the UI by initiating the loader to retrieve the 
        // details of the venue from the underlying Place Content Provider.
        if (placeId != null)
          getLoaderManager().initLoader(0, null, this);
      }
    
    
    
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.main_screen , container, false);
        this.container = container;
    
        return view;
    }
    
    public void onDestroyView() {
        super.onDestroyView();
    
        Log.d(TAG, "onDestroyView()");
    
        buttonList[FRIENDS].setImageBitmap(null);
        buttonList[PEOPLE].setImageBitmap(null);
        buttonList[MAP].setImageBitmap(null);
        buttonList[CAMERA].setImageBitmap(null);
        topTile.setBackground(null);
    
        // Removes all the onClickListeners
        buttonList[FRIENDS].setOnClickListener(null);
        buttonList[PEOPLE].setOnClickListener(null);
        buttonList[MAP].setOnClickListener(null);
        buttonList[CAMERA].setOnClickListener(null);
        topTile.setOnClickListener(null);
    
        // Sets buttonList and the topTile to null
         buttonList = null;
         topTile = null;
    
        // Recycles the bitmaps and sets picss to null
        if (picss != null) {
            picss[FRIENDS].recycle();
            picss[PEOPLE].recycle();
            picss[MAP].recycle();
            picss[CAMERA].recycle();
            picss = null;
        }
    
        mainLayout.setBackground(null);
        background.recycle();
    
        unbindDrawables(view);
        view = null;
        activity = null;
        System.gc();
    }
    
    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
            view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup && !(view instanceof AdapterView)) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
            ((ViewGroup) view).removeAllViews();
        }
    }
    
    
    public void onClick(View v) {
        Log.d(TAG, "Got within here.");
        switch (v.getId()) {
        case R.id.b_friendButton:
    
            FragmentTransaction trans = getActivity().getSupportFragmentManager().beginTransaction();
            trans.replace(R.id.fragment_container, new FriendsListFrag());
            trans.addToBackStack(null);
            trans.commit();
            break;
        case R.id.b_peopleButton:
            break;
        case R.id.b_mapButton:
            break;
        case R.id.b_CameraButton:
            break;
        case R.id.ll_Dynamic_Tile:
            break;
        }
    
    }
    
    private void setupObjects(View view) {
    
        Log.d(TAG, "Attempting to setupObjects.");
    
        mainLayout = (LinearLayout) view.findViewById(R.id.MainActivity_Main_Layout);
        topTile = (LinearLayout) view.findViewById(R.id.ll_Dynamic_Tile);
    
        buttonList = new ImageButton[NUM_OF_BUTTONS];
        buttonList[FRIENDS] = (ImageButton) view.findViewById(R.id.b_friendButton);
        buttonList[PEOPLE] = (ImageButton) view.findViewById(R.id.b_peopleButton);
        buttonList[MAP] = (ImageButton) view.findViewById(R.id.b_mapButton);
        buttonList[CAMERA] = (ImageButton) view.findViewById(R.id.b_CameraButton);
    
        picss = new Bitmap[5];
    
        buttonList[FRIENDS].setOnClickListener(this);
        buttonList[PEOPLE].setOnClickListener(this);
        buttonList[MAP].setOnClickListener(this);
        buttonList[CAMERA].setOnClickListener(this);
    
        int width = getActivity().getWindowManager().getDefaultDisplay().getWidth();
        int height = getActivity().getWindowManager().getDefaultDisplay().getHeight();
    
        background = GetImage.decodeSampledBitmapFromResource(getResources(),
                R.drawable.background2, width, height);
    
        System.out.println(mainLayout == null);
    
        mainLayout.setBackground(new BitmapDrawable(getResources(), background));
    
    }
    
    private void setupLayoutsAndImages(View view) {
    
        Log.d(TAG, "Attempting to setupLayoutsAndImages.");
    
        System.out.println("Width: " + width);
        final ImageButton myButton = (ImageButton) getView().findViewById(R.id.b_friendButton);
        getView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                System.out.println("Fragwidth: " + getView().getWidth());
                System.out.println("FragHeight: " + getView().getHeight());
                System.out.println("Button: " + myButton.getWidth());
                width = myButton.getWidth();
    
                System.out.println("Width: " + width);
    
                getView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
    
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                        width, width, 1.0f);
                int marg = (int) getResources().getDimension(
                        R.dimen.main_buttonpadding);
                params.setMargins(marg, marg, marg, marg);
    
                // Setting the params for the buttons.
                buttonList[FRIENDS].setLayoutParams(params);
                buttonList[PEOPLE].setLayoutParams(params);
                buttonList[MAP].setLayoutParams(params);
                buttonList[CAMERA].setLayoutParams(params);
    
                picss[FRIENDS] = GetImage.decodeSampledBitmapFromResource(
                        getResources(), R.drawable.tilefriendsmall, width, width);
                picss[PEOPLE] = GetImage.decodeSampledBitmapFromResource(
                        getResources(), R.drawable.tilepeoplesmall, width, width);
                picss[MAP] = GetImage.decodeSampledBitmapFromResource(
                        getResources(), R.drawable.tilemapsmall, width, width);
                picss[CAMERA] = GetImage.decodeSampledBitmapFromResource(
                        getResources(), R.drawable.tilecamerasmall, width, width);
                picss[TOP_TILE] = GetImage.decodeSampledBitmapFromResource(
                        getResources(), R.drawable.tilefriendlong, width, width);
    
                // Setting up the background for the buttons.
                buttonList[FRIENDS].setImageBitmap(picss[FRIENDS]);
                buttonList[FRIENDS].setAlpha(0.6f);
                buttonList[PEOPLE].setImageBitmap(picss[PEOPLE]);
                buttonList[PEOPLE].setAlpha(0.6f);
                buttonList[MAP].setImageBitmap(picss[MAP]);
                buttonList[MAP].setAlpha(0.6f);
                buttonList[CAMERA].setImageBitmap(picss[CAMERA]);
                buttonList[CAMERA].setAlpha(0.6f);
    
                LinearLayout.LayoutParams topParams = new LinearLayout.LayoutParams(
                        topTile.getWidth(), width);
                topParams.setMargins(marg, marg, marg, marg);
                topTile.setLayoutParams(topParams);
                topTile.setBackground(new BitmapDrawable(getResources(),
                        picss[TOP_TILE]));
                topTile.setAlpha(0.6f);
            }
        });
    
    }
    

    Given the code above, when I switch to the FriendsListFrag() I retain the previous memory that was allocated for the MainMenuFragment. I have tried to use the Memory Analyzer but have had little luck with it.

  • Jake Alsemgeest
    Jake Alsemgeest over 10 years
    Oh okay, so you're saying that every time I open a new fragment it's just keeping the old one? I still need to de-reference any references to the activity on the fragment onDestryoView(), right?
  • Jake Alsemgeest
    Jake Alsemgeest over 10 years
    I tried your suggestion and it seems that I'm still using the same amount of memory. It's starting at 10023 and jumps up to 18123 when switching fragments... which doesn't make all that much sense since the second one doesn't have nearly as much in terms of objects and images.
  • Martin Marconcini
    Martin Marconcini over 10 years
    Well now you have the Fragments correctly handled. The next step might be to optimize the bitmap loading. You might want to try Picasso (library) for that, since it will handle the bitmap lifecycle and will make sure you don't leak memory.
  • Jake Alsemgeest
    Jake Alsemgeest over 10 years
    Okay, sounds good. I looked at the Picasso library. Does it work properly within fragments?
  • Martin Marconcini
    Martin Marconcini over 10 years
    Yes it does. It's written and maintained by the same person who wrote SherlockLibrary among other known Android libs. (Or at least he works on it).
  • ban-geoengineering
    ban-geoengineering over 8 years
    I agree that this could be the problem. See stackoverflow.com/questions/28264139/…
  • Admin
    Admin about 8 years
    can you use "replace" instead of "add" ?
  • Martin Marconcini
    Martin Marconcini about 8 years
    @delive yes, I'd say so.