OSDN Git Service

Be bug-compatible with Fragment#setUserVisibleHint < N
authorAdam Powell <adamp@google.com>
Mon, 18 Apr 2016 18:01:55 +0000 (11:01 -0700)
committerAdam Powell <adamp@google.com>
Mon, 18 Apr 2016 23:39:22 +0000 (16:39 -0700)
Prior to Android N we were simply checking if a fragment had a
FragmentManager set before we would trigger a deferred
start. Unfortunately this also gets set before a fragment transaction
is committed, so if setUserVisibleHint was called before a transaction
commit, we would start the fragment way too
early. FragmentPagerAdapter triggers this situation.

Unfortunately some apps relied on this timing in overrides of
setUserVisibleHint on their own fragments, and expected, however
erroneously, that after a call to super.setUserVisibleHint their
onStart methods had been run.

Bug 28184671

Change-Id: Ie40d5f6963d312c2fad4a48fb4f992d33e65c83b

core/java/android/app/Fragment.java

index 29f594f..a221c98 100644 (file)
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1030,14 +1031,46 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
      * This may be used by the system to prioritize operations such as fragment lifecycle updates
      * or loader ordering behavior.</p>
      *
      * This may be used by the system to prioritize operations such as fragment lifecycle updates
      * or loader ordering behavior.</p>
      *
+     * <p><strong>Note:</strong> Prior to Android N there was a platform bug that could cause
+     * <code>setUserVisibleHint</code> to bring a fragment up to the started state before its
+     * <code>FragmentTransaction</code> had been committed. As some apps relied on this behavior,
+     * it is preserved for apps that declare a <code>targetSdkVersion</code> of 23 or lower.</p>
+     *
      * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
      *                        false if it is not.
      */
     public void setUserVisibleHint(boolean isVisibleToUser) {
      * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
      *                        false if it is not.
      */
     public void setUserVisibleHint(boolean isVisibleToUser) {
-        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
-                && mFragmentManager != null && isAdded()) {
+        // Prior to Android N we were simply checking if this fragment had a FragmentManager
+        // set before we would trigger a deferred start. Unfortunately this also gets set before
+        // a fragment transaction is committed, so if setUserVisibleHint was called before a
+        // transaction commit, we would start the fragment way too early. FragmentPagerAdapter
+        // triggers this situation.
+        // Unfortunately some apps relied on this timing in overrides of setUserVisibleHint
+        // on their own fragments, and expected, however erroneously, that after a call to
+        // super.setUserVisibleHint their onStart methods had been run.
+        // We preserve this behavior for apps targeting old platform versions below.
+        boolean useBrokenAddedCheck = false;
+        Context context = getContext();
+        if (mFragmentManager != null && mFragmentManager.mHost != null) {
+            context = mFragmentManager.mHost.getContext();
+        }
+        if (context != null) {
+            useBrokenAddedCheck = context.getApplicationInfo().targetSdkVersion <= VERSION_CODES.M;
+        }
+
+        final boolean performDeferredStart;
+        if (useBrokenAddedCheck) {
+            performDeferredStart = !mUserVisibleHint && isVisibleToUser && mState < STARTED
+                    && mFragmentManager != null;
+        } else {
+            performDeferredStart = !mUserVisibleHint && isVisibleToUser && mState < STARTED
+                    && mFragmentManager != null && isAdded();
+        }
+
+        if (performDeferredStart) {
             mFragmentManager.performPendingDeferredStart(this);
         }
             mFragmentManager.performPendingDeferredStart(this);
         }
+
         mUserVisibleHint = isVisibleToUser;
         mDeferStart = mState < STARTED && !isVisibleToUser;
     }
         mUserVisibleHint = isVisibleToUser;
         mDeferStart = mState < STARTED && !isVisibleToUser;
     }