android - Mutation of a Bundle object -
i'm working legacy code , found inconsistent behavior in function:
@override public void openfragment(final class<? extends basefragment> fragmentclass, final boolean addtobackstack, final bundle args) { long delay = 0; if (mdrawerlayout.isdraweropen(gravitycompat.start)) { delay = getresources().getinteger(android.r.integer.config_shortanimtime) * 2; } // fixme: quick fix, not cases final bundle args666 = args != null ? (bundle) args.clone() : null; new handler().postdelayed(new runnable() { @override public void run() { doopenfragment(fragmentclass, addtobackstack, args666); } }, delay); closedrawer(); } protected void doopenfragment(final class<? extends basefragment> fragmentclass, final boolean addtobackstack, final bundle args) { try { if (getsupportfragmentmanager().getbackstackentrycount() >= 1) { shownavigationicon(); } hidekeyboard(); basefragment fragment = createfragment(fragmentclass, args); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); fragment.inittransactionanimation(transaction); string tag = gettag(fragment); transaction.add(r.id.container, fragment, tag); if (addtobackstack) { transaction.addtobackstack(tag); } transaction.commitallowingstateloss(); hidelastfragment(0); } catch (exception e) { sentry.captureexception(e, "error opening fragment"); } }
openfragment
gets non-empty bundle args, doopenfragment
empty bundle. fragments committed calling commitallowingstateloss()
a quick fix can use bundle.clone():
final bundle args666 = (bundle) args.clone(); new handler().postdelayed(new runnable() { @override public void run() { doopenfragment(fragmentclass, addtobackstack, args666); } }, delay);
it not handle cases , deepcopy
available in api26.
- why happen?
- how fix it?
[update]
i played @pavel's solution , things weirder
final bundle args666 = args != null ? clonethroughserialization(args) : args; final bundle args777 = args != null ? (bundle) args.clone() : args;
[update2]
actually, problem isn't postdelayed
call. let's see call stack:
in gorighttothecollectionscreen
bundle created , packed (nothing suspicious, no mutation afterward).
i guess, source of problem in 2 calls inside openfragmentschain
:
public void openrootfragmentschain(class<? extends basefragment> fragmentclass, list<class<? extends basefragment>> fragmentclasses, boolean addtobackstack, bundle args) { openfragmentschain(fragmentclasses, addtobackstack, args); openfragment(fragmentclass, true, args); } public void openfragmentschain(list<class<? extends basefragment>> fragmentclasses, boolean addtobackstack, bundle args) { try { (int = 0; < fragmentclasses.size(); i++) { fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); basefragment fragment = createfragment(fragmentclasses.get(i), args); string tag = gettag(fragment); transaction.add(r.id.container, fragment, tag); if (addtobackstack) { transaction.addtobackstack(tag); } if (i != fragmentclasses.size() - 1) { transaction.hide(fragment); } transaction.commitallowingstateloss(); } if (fragmentclasses.size() >= 1) { updatedrawer(); } } catch (exception e) { sentry.captureexception(e, "error opening fragment chain"); } } protected void doopenfragment(final class<? extends basefragment> fragmentclass, final boolean addtobackstack, final bundle args) { try { if (getsupportfragmentmanager().getbackstackentrycount() >= 1) { shownavigationicon(); } hidekeyboard(); basefragment fragment = createfragment(fragmentclass, args); fragmenttransaction transaction = getsupportfragmentmanager().begintransaction(); fragment.inittransactionanimation(transaction); string tag = gettag(fragment); transaction.add(r.id.container, fragment, tag); if (addtobackstack) { transaction.addtobackstack(tag); } transaction.commitallowingstateloss(); hidelastfragment(0); } catch (exception e) { sentry.captureexception(e, "error opening fragment"); } } protected basefragment createfragment(class<? extends basefragment> fragmentclass, bundle args) throws exception { basefragment fragment = fragmentclass.newinstance(); fragment.sethasoptionsmenu(true); fragment.setarguments(args); fragment.setnavigationhandler(basefragmentnavigatoractivity.this); fragment.settoolbar(mtoolbar); fragment.setmenuloadservice(mmenuloaderservice); return fragment; }
- some other code modifies same bundle before run() called. problem in code.
you can deep clone through serialization.
public static bundle clonethroughserialization(@nonnull bundle bundle) { parcel parcel = parcel.obtain(); bundle.writetoparcel(parcel, 0); bundle clonedbundle = new bundle(); clonedbundle.readfromparcel(parcel); parcel.recycle(); return clonedbundle; }
Comments
Post a Comment