FragmentTransaction.commitNow, framework edition

Offer commitNow and commitNowAllowingStateLoss methods on Fragment for
use by encapsulated components using fragments as implementation
details. This can help prevent unexpected ordering side effects at the
app level when a call to a library method wants to commit and
immediately initialize a fragment as an implementation detail.

Note that this change still does not permit reentrant FragmentManager
operations. It is still an error to add/remove/change fragments in the
same FragmentManager while a fragment transaction is being executed.

Have the commonly used ViewPager adapters use commitNow instead of
executePendingTransactions.

Change-Id: Ia37a871234a287423063f0c2c3e4c93d69116cad
(cherry picked from commit f6b30662f8)
This commit is contained in:
Adam Powell
2016-02-04 15:38:20 -08:00
parent 2af189a0a7
commit 8585ed66b9
6 changed files with 85 additions and 1 deletions

View File

@@ -4604,6 +4604,8 @@ package android.app {
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
method public abstract void commitNow();
method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);

View File

@@ -4737,6 +4737,8 @@ package android.app {
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
method public abstract void commitNow();
method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);

View File

@@ -4604,6 +4604,8 @@ package android.app {
method public abstract android.app.FragmentTransaction attach(android.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
method public abstract void commitNow();
method public abstract void commitNowAllowingStateLoss();
method public abstract android.app.FragmentTransaction detach(android.app.Fragment);
method public abstract android.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.app.FragmentTransaction hide(android.app.Fragment);

View File

@@ -667,6 +667,18 @@ final class BackStackRecord extends FragmentTransaction implements
return commitInternal(true);
}
@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}
@Override
public void commitNowAllowingStateLoss() {
disallowAddToBackStack();
mManager.execSingleAction(this, true);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");

View File

@@ -1503,6 +1503,26 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
}
}
public void execSingleAction(Runnable action, boolean allowStateLoss) {
if (mExecutingActions) {
throw new IllegalStateException("FragmentManager is already executing transactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of fragment host");
}
if (allowStateLoss) {
checkStateLoss();
}
mExecutingActions = true;
action.run();
mExecutingActions = false;
doPendingDeferredStart();
}
/**
* Only call from main thread!
*/
@@ -1543,6 +1563,12 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
didSomething = true;
}
doPendingDeferredStart();
return didSomething;
}
void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
@@ -1556,7 +1582,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
startPendingDeferredFragments();
}
}
return didSomething;
}
void reportBackStackChanged() {

View File

@@ -284,4 +284,45 @@ public abstract class FragmentTransaction {
* to change unexpectedly on the user.
*/
public abstract int commitAllowingStateLoss();
/**
* Commits this transaction synchronously. Any added fragments will be
* initialized and brought completely to the lifecycle state of their host
* and any removed fragments will be torn down accordingly before this
* call returns. Committing a transaction in this way allows fragments
* to be added as dedicated, encapsulated components that monitor the
* lifecycle state of their host while providing firmer ordering guarantees
* around when those fragments are fully initialized and ready. Fragments
* that manage views will have those views created and attached.
*
* <p>Calling <code>commitNow</code> is preferable to calling
* {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
* as the latter will have the side effect of attempting to commit <em>all</em>
* currently pending transactions whether that is the desired behavior
* or not.</p>
*
* <p>Transactions committed in this way may not be added to the
* FragmentManager's back stack, as doing so would break other expected
* ordering guarantees for other asynchronously committed transactions.
* This method will throw {@link IllegalStateException} if the transaction
* previously requested to be added to the back stack with
* {@link #addToBackStack(String)}.</p>
*
* <p class="note">A transaction can only be committed with this method
* prior to its containing activity saving its state. If the commit is
* attempted after that point, an exception will be thrown. This is
* because the state after the commit can be lost if the activity needs to
* be restored from its state. See {@link #commitAllowingStateLoss()} for
* situations where it may be okay to lose the commit.</p>
*/
public abstract void commitNow();
/**
* Like {@link #commitNow} but allows the commit to be executed after an
* activity's state is saved. This is dangerous because the commit can
* be lost if the activity needs to later be restored from its state, so
* this should only be used for cases where it is okay for the UI state
* to change unexpectedly on the user.
*/
public abstract void commitNowAllowingStateLoss();
}