Glide的一个使用建议

2018/05/21

有一天,切换Android Studio的Logcat窗口,发现了一堆莫名奇妙标签为“FragmentManager”的打印:

05-21 15:46:05.713 32174-32174/silladus.. E/FragmentManager: Fragment no longer exists for key key: index 7
    Activity state: 
05-21 15:46:05.714 32174-32174/silladus.. E/FragmentManager:   Local Activity e497260 State:
        mResumed=true mStopped=false mFinished=false
        mChangingConfigurations=false
        mCurrentConfig={1.0 460mcc11mnc [zh_CN] ldltr sw360dp w360dp h616dp 480dpi nrml long port finger -keyb/v/h -nav/h s.302 themeId=0}
        mLoadersStarted=true
        Active Fragments in c636db7:
          #0: HomeFragment{5a635e3 #0 id=0x7f090054 HomeFragment}
            mFragmentId=#7f090054 mContainerId=#7f090054 mTag=HomeFragment
            mState=5 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
            mAdded=true mRemoving=false mFromLayout=false mInLayout=false
        ...

这是从哪冒出来的,怎么冒出来的,之前怎么没看到?

带着这些疑问找到FragmentManager类,搜索”Fragment no longer exists for key”,找到了下面一段代码

    @Override
    public Fragment getFragment(Bundle bundle, String key) {
        int index = bundle.getInt(key, -1);
        if (index == -1) {
            return null;
        }
        Fragment f = mActive.get(index);
        if (f == null) {
            throwException(new IllegalStateException("Fragment no longer exists for key "
                    + key + ": index " + index));
        }
        return f;
    }

发现mActive是一个Fragment集合SparseArray<Fragment> mActive;

当从mActive取不到指定下标的Fragment时会执行throwException方法,该方法代码如下:

    private void throwException(RuntimeException ex) {
        Log.e(TAG, ex.getMessage());
        LogWriter logw = new LogWriter(Log.ERROR, TAG);
        PrintWriter pw = new FastPrintWriter(logw, false, 1024);
        if (mHost != null) {
            Log.e(TAG, "Activity state:");
            try {
                mHost.onDump("  ", null, pw, new String[] { });
            } catch (Exception e) {
                pw.flush();
                Log.e(TAG, "Failed dumping state", e);
            }
        } else {
            Log.e(TAG, "Fragment manager state:");
            try {
                dump("  ", null, pw, new String[] { });
            } catch (Exception e) {
                pw.flush();
                Log.e(TAG, "Failed dumping state", e);
            }
        }
        pw.flush();
        throw ex;
    }

在该方法里打印了”Activity state:”,接着执行mHost.onDump()方法。mHostFragmentHostCallback,我们知道FragmentActivity有关联,在Activity中搜索打印里出现的内容”Local Activity”,找到了如下代码:

    /**
     * Print the Activity's state into the given stream.  This gets invoked if
     * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
     *
     * @param prefix Desired prefix to prepend at each line of output.
     * @param fd The raw file descriptor that the dump is being sent to.
     * @param writer The PrintWriter to which you should dump your state.  This will be
     * closed for you after you return.
     * @param args additional arguments to the dump request.
     */
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        dumpInner(prefix, fd, writer, args);
    }

    void dumpInner(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        writer.print(prefix); writer.print("Local Activity ");
                writer.print(Integer.toHexString(System.identityHashCode(this)));
                writer.println(" State:");
        String innerPrefix = prefix + "  ";
        writer.print(innerPrefix); writer.print("mResumed=");
                writer.print(mResumed); writer.print(" mStopped=");
                writer.print(mStopped); writer.print(" mFinished=");
                writer.println(mFinished);
        writer.print(innerPrefix); writer.print("mChangingConfigurations=");
                writer.println(mChangingConfigurations);
        writer.print(innerPrefix); writer.print("mCurrentConfig=");
                writer.println(mCurrentConfig);

        mFragments.dumpLoaders(innerPrefix, fd, writer, args);
        mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
        if (mVoiceInteractor != null) {
            mVoiceInteractor.dump(innerPrefix, fd, writer, args);
        }

        if (getWindow() != null &&
                getWindow().peekDecorView() != null &&
                getWindow().peekDecorView().getViewRootImpl() != null) {
            getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
        }

        mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);

        final AutofillManager afm = getAutofillManager();
        if (afm != null) {
            afm.dump(prefix, writer);
        }
    }

现在要找到这段代码调用的源头,有两个地方调用,其中在FragmentHostCallback的实现类里面调用了。好家伙,这不正是我们想要的吗!该实现类代码片段如下:

    class HostCallbacks extends FragmentHostCallback<Activity> {
        public HostCallbacks() {
            super(Activity.this /*activity*/);
        }

        @Override
        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
            Activity.this.dump(prefix, fd, writer, args);
        }

        ...
            
    }

HostCallback实例引用传给FragmentControllerFragmentController里面有HostCallback,有FragmentManagerFragmentManager通过HostCallback(ActivityFragmentHostCallback的实现类)调用Activity里面的打印,这时候思路都已经变得清晰起来。

回到该方法,dumpInner()里面完成部分打印后又调用了mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args),跳到该方法找到FragmentManager实现类FragmentManagerImpl方法的实现看到如下代码片段:

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
        String innerPrefix = prefix + "    ";

        int N;
        if (mActive != null) {
            N = mActive.size();
            if (N > 0) {
                writer.print(prefix); writer.print("Active Fragments in ");
                        writer.print(Integer.toHexString(System.identityHashCode(this)));
                        writer.println(":");
                for (int i=0; i<N; i++) {
                    Fragment f = mActive.valueAt(i);
                    writer.print(prefix); writer.print("  #"); writer.print(i);
                            writer.print(": "); writer.println(f);
                    if (f != null) {
                        f.dump(innerPrefix, fd, writer, args);
                    }
                }
            }
        }

        ...
            
    }

方法代码很多足有100行,但是看到前面部分的代码就知道这就是我们要找的。到现在,这段莫名其妙的打印轨迹已经接近水落石出,起因就是FragmentManager(FragmentManagerImpl实现)里的getFragment(Bundle bundle, String key)方法被调用了。现阶段是要搞清楚哪里调用了这个方法。

幸运的是,我们很快就找到了调用的源头,这一大段打印是进入主界面HomeFragment时出现的。回顾问题出现前后代码的比较发现了。Glide的with总体提供了接受4种类型的方法,Context、Activity、Fragment、View。其中参数类型Context、Activity直接返回当前Context类型的RequestManager,Fragment参数的会找一下所属的Activity,而View参数的还会寻找所在的Fragment。这就是问题所在:


st=>start: Glide.with(View view)
op1=>operation: RequestManagerRetriever.class
get(View view)|current
op2=>operation: findFragment(View view, Activity activity)
op3=>operation: findAllFragmentsWithViews(FragmentManager fm, ArrayMap<Fragment> result)
op4=>operation: findAllFragmentsWithViewsPreO(FragmentManager fm, ArrayMap<Fragment> result)
e=>end: FragmentManagerImpl.class
getFragment(Bundle bundle)
st->op1->op2->op3->op4->e
  • 1 Glide.with(View view)
  • 2 RequestManagerRetriever.class get(View view)
  • 3 findFragment(View view, Activity activity)
  • 4 findAllFragmentsWithViews(FragmentManager fm, ArrayMap<Fragment> result)
  • 5 findAllFragmentsWithViewsPreO(FragmentManager fm, ArrayMap<Fragment> result)
  • 6 FragmentManagerImpl.class getFragment(Bundle bundle)

  private void findAllFragmentsWithViewsPreO(
      @NonNull android.app.FragmentManager fragmentManager,
      @NonNull ArrayMap<View, android.app.Fragment> result) {
    int index = 0;
    while (true) {
      tempBundle.putInt(FRAGMENT_INDEX_KEY, index++);
      android.app.Fragment fragment = null;
      try {
        fragment = fragmentManager.getFragment(tempBundle, FRAGMENT_INDEX_KEY);
      } catch (Exception e) {
        // This generates log spam from FragmentManager anyway.
      }
      if (fragment == null) {
        break;
      }
      if (fragment.getView() != null) {
        result.put(fragment.getView(), fragment);
        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
          findAllFragmentsWithViews(fragment.getChildFragmentManager(), result);
        }
      }
    }
  }

到这里fragmentManager.getFragment()的调用已然找到。所以避免这些打印就是不传View,传View的参数要慎用。

Post Directory