Skip to content

Instantly share code, notes, and snippets.

@jjhesk
Last active August 1, 2016 07:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjhesk/f1d1263852d48f06ed3e072a32247bb6 to your computer and use it in GitHub Desktop.
Save jjhesk/f1d1263852d48f06ed3e072a32247bb6 to your computer and use it in GitHub Desktop.
memory management for the leak fix for on LILIPOP before.
public class NestingFragment extends Fragment{
//As we setRetainInstanceState(true), this field will hold the reference of //the old ChildFragmentManager
private FragmentManager mRetainedChildFragmentManager;
private FragmentManager getMyChildFragmentManager() {
if(mRetainedChildFragmentManager == null) {
//Hold the reference of child fragment manager created by //Fragment internally and hold it for the future recreated //fragment since Fragment#getChildFragmentManager() always //creates new fragment manager after rev 20.
mRetainedChildFragmentManager = getChildFragmentManager();
}
return mRetainedChildFragmentManager;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(mChildFragmentManager != null) {
//restore the last retained child fragment manager to the new
//created fragment
try {
Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
childFMField.setAccessible(true);
childFMField.set(this, mChildFragmentManager);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// Other code ...
}
public class NestingFragment extends Fragment {
//...other codes
private FragmentManager retainedChildFragmentManager;
private FragmentHostCallback currentHost;
private Class fragmentImplClass;
private Field mHostField;
{
//Prepare the reflections to manage hiden fileds
try {
fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl");
mHostField = fragmentImplClass.getDeclaredField("mHost");
mHostField.setAccessible(true);
} catch (ClassNotFoundException e) {
throw new RuntimeException("FragmentManagerImpl is renamed due to the " +
"change of Android SDK, this workaround doesn't work any more. " +
"See the issue at " +
"https://code.google.com/p/android/issues/detail?id=74222", e);
} catch (NoSuchFieldException e) {
throw new RuntimeException("FragmentManagerImpl.mHost is found due to the " +
"change of Android SDK, this workaround doesn't work any more. " +
"See the issue at " +
"https://code.google.com/p/android/issues/detail?id=74222", e);
}
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
protected FragmentManager childFragmentManager() {
if (retainedChildFragmentManager == null) {
retainedChildFragmentManager = getChildFragmentManager();
}
return retainedChildFragmentManager;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (retainedChildFragmentManager != null) {
//restore the last retained child fragment manager to the new
//created fragment
try {
//Copy the mHost(Activity) to retainedChildFragmentManager
currentHost = (FragmentHostCallback) mHostField.get(getFragmentManager());
Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
childFMField.setAccessible(true);
childFMField.set(this, retainedChildFragmentManager);
refreshHosts(getFragmentManager());
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
//Refresh children fragment's hosts
} else {
//If the child fragment manager has not been retained yet, let it hold the internal
//child fragment manager as early as possible. This can prevent child fragment
//manager from missing to be set and then retained, which could happen when
//OS kills activity and restarts it. In this case, the delegate fragment restored
//but childFragmentManager() may not be called so mRetainedChildFragmentManager is
//yet set. If the fragment is rotated, the state of child fragment manager will be
//lost since mRetainedChildFragmentManager hasn't set to be retained by the OS.
retainedChildFragmentManager = getChildFragmentManager();
}
}
private void refreshHosts(FragmentManager fragmentManager) throws IllegalAccessException {
if (fragmentManager != null) {
replaceFragmentManagerHost(fragmentManager);
}
//replace host(activity) of fragments already added
List<fragment> frags = fragmentManager.getFragments();
if (frags != null) {
for (Fragment f : frags) {
if (f != null) {
try {
//Copy the mHost(Activity) to retainedChildFragmentManager
Field mHostField = Fragment.class.getDeclaredField("mHost");
mHostField.setAccessible(true);
mHostField.set(f, currentHost);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
if (f.getChildFragmentManager() != null) {
refreshHosts(f.getChildFragmentManager());
}
}
}
}
}
//replace host(activity) of the fragment manager so that new fragments it creates will be attached
//with correct activity
private void replaceFragmentManagerHost(FragmentManager fragmentManager) throws IllegalAccessException {
if (currentHost != null) {
mHostField.set(fragmentManager, currentHost);
}
}
//...other codes
}
@jjhesk
Copy link
Author

jjhesk commented May 12, 2016

This is another version

public class NestingFragment extends Fragment {
    //...other codes

    private FragmentManager retainedChildFragmentManager;
    private FragmentHostCallback currentHost;
    private Class fragmentImplClass;
    private Field mHostField;

    {
        //Prepare the reflections to manage hiden fileds
        try {
            fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl");
            mHostField = fragmentImplClass.getDeclaredField("mHost");
            mHostField.setAccessible(true);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("FragmentManagerImpl is renamed due to the " +
                    "change of Android SDK, this workaround doesn't work any more. " +
                    "See the issue at " +
                    "https://code.google.com/p/android/issues/detail?id=74222", e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("FragmentManagerImpl.mHost is found due to the " +
                    "change of Android SDK, this workaround doesn't work any more. " +
                    "See the issue at " +
                    "https://code.google.com/p/android/issues/detail?id=74222", e);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    protected FragmentManager childFragmentManager() {
        if (retainedChildFragmentManager == null) {
            retainedChildFragmentManager = getChildFragmentManager();
        }
        return retainedChildFragmentManager;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (retainedChildFragmentManager != null) {
            //restore the last retained child fragment manager to the new
            //created fragment
            try {
                //Copy the mHost(Activity) to retainedChildFragmentManager
                currentHost = (FragmentHostCallback) mHostField.get(getFragmentManager());

                Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
                childFMField.setAccessible(true);
                childFMField.set(this, retainedChildFragmentManager);

                refreshHosts(getFragmentManager());
            } catch (Exception e) {
                logger.warn(e.getMessage(), e);
            }
            //Refresh children fragment's hosts
        } else {
            //If the child fragment manager has not been retained yet, let it hold the internal
            //child fragment manager as early as possible. This can prevent child fragment
            //manager from missing to be set and then retained, which could happen when
            //OS kills activity and restarts it. In this case, the delegate fragment restored
            //but childFragmentManager() may not be called so mRetainedChildFragmentManager is
            //yet set. If the fragment is rotated, the state of child fragment manager will be
            //lost since mRetainedChildFragmentManager hasn't set to be retained by the OS.
            retainedChildFragmentManager = getChildFragmentManager();
        }
    }

    private void refreshHosts(FragmentManager fragmentManager) throws IllegalAccessException {
        if (fragmentManager != null) {
            replaceFragmentManagerHost(fragmentManager);
        }

        //replace host(activity) of fragments already added
        List<Fragment> frags = fragmentManager.getFragments();
        if (frags != null) {
            for (Fragment f : frags) {
                if (f != null) {
                    try {
                        //Copy the mHost(Activity) to retainedChildFragmentManager
                        Field mHostField = Fragment.class.getDeclaredField("mHost");
                        mHostField.setAccessible(true);
                        mHostField.set(f, currentHost);
                    } catch (Exception e) {
                        logger.warn(e.getMessage(), e);
                    }
                    if (f.getChildFragmentManager() != null) {
                        refreshHosts(f.getChildFragmentManager());
                    }
                }
            }
        }
    }

    //replace host(activity) of the fragment manager so that new fragments it creates will be attached
    //with correct activity
    private void replaceFragmentManagerHost(FragmentManager fragmentManager) throws IllegalAccessException {
        if (currentHost != null) {
            mHostField.set(fragmentManager, currentHost);
        }
    }

    //...other codes
}

@jackcsk
Copy link

jackcsk commented May 12, 2016

Suggested revision:

try {
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 && this.getChildFragmentManager() != null) {
        Field childFMField = Fragment.class.getDeclaredField("mChildFragmentManager");
        childFMField.setAccessible(true);
        childFMField.set(this, null);
    }
} catch (NoSuchFieldException ex) {
    Log.e(TAG, ex.getMessage(), ex);
} catch (IllegalAccessException ex) {
    Log.e(TAG, ex.getMessage(), ex);
}

@nksaroj
Copy link

nksaroj commented Aug 1, 2016

This doesn't work any more on API 24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment