日常爬坑 - Fragment传参失败

最近在bugly上看到一个很奇怪的问题,activity在给fragment传参时,使用了赋值的方式,如下:

val f = FragmentA()
f.b = "sss"

某些情况下,b的属性死活拿不到,导致程序出现空指针异常。

一、原因分析

1.1 androidx 版本:1.1.0

查看了FragmentActivity的代码,

protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreSaveState(p);
            ......
        }
    .....

}

savedInstanceState不为空时(如屏幕旋转,fragment实例被销毁响应onDetach),fragment会清空状态,一路跟踪代码下去,发现在FragmentManagerImplrestoreSaveState中,发现Fragment竟然被重建了!!!

 Fragment f = fs.instantiate(mHost.getContext().getClassLoader(), getFragmentFactory());

查看fragment创建的逻辑,发现原来Fragment的属性并不会赋值到新创建的fragmen中,在Fragment中唯一恢复数据的只有setArguments,这也是为什么官方建议我们使用setArguments传参的原因。

public Fragment instantiate(@NonNull ClassLoader classLoader,
            @NonNull FragmentFactory factory) {
        if (mInstance == null) {
            if (mArguments != null) {
                mArguments.setClassLoader(classLoader);
            }
            mInstance = factory.instantiate(classLoader, mClassName);
            mInstance.setArguments(mArguments);  // fragment唯一恢复数据的操作
            if (mSavedFragmentState != null) {
                mSavedFragmentState.setClassLoader(classLoader);
                mInstance.mSavedFragmentState = mSavedFragmentState;
            } else {
                // When restoring a Fragment, always ensure we have a
                // non-null Bundle so that developers have a signal for
                // when the Fragment is being restored
                mInstance.mSavedFragmentState = new Bundle();
            }
            mInstance.mWho = mWho;
            mInstance.mFromLayout = mFromLayout;
            mInstance.mRestored = true;
            mInstance.mFragmentId = mFragmentId;
            mInstance.mContainerId = mContainerId;
            mInstance.mTag = mTag;
            mInstance.mRetainInstance = mRetainInstance;
            mInstance.mRemoving = mRemoving;
            mInstance.mDetached = mDetached;
            mInstance.mHidden = mHidden;
            mInstance.mMaxState = Lifecycle.State.values()[mMaxLifecycleState];
            if (FragmentManagerImpl.DEBUG) {
                Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance);
            }
        }
        return mInstance;
    }

1.2 androidx 1.2.4

恢复时:
首先从FragmentManagerViewModel查找fragment是否有实例。

如果有实例,则恢复FragmentState,这种情况下,我们的fragment的属性还是存在着的。

如果没有实例,则通过反射重新创建Fragment,并且给新的Fragment设置保存的 mArguments

void restoreSaveState(@Nullable Parcelable state) {
        // If there is no saved state at all, then there's nothing else to do
        if (state == null) return;
        FragmentManagerState fms = (FragmentManagerState) state;
        if (fms.mActive == null) return;

        // Build the full list of active fragments, instantiating them from
        // their saved state.
        mFragmentStore.resetActiveFragments();
        for (FragmentState fs : fms.mActive) {
            if (fs != null) {
                FragmentStateManager fragmentStateManager;
                Fragment retainedFragment = mNonConfig.findRetainedFragmentByWho(fs.mWho);
                if (retainedFragment != null) {
                    if (isLoggingEnabled(Log.VERBOSE)) {
                        Log.v(TAG, "restoreSaveState: re-attaching retained "
                                + retainedFragment);
                    }
                  // 
                    fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
                            retainedFragment, fs);
                } else {
                    // 在这重新创建了Fragment
                    fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
                            mHost.getContext().getClassLoader(), getFragmentFactory(), fs);
                }
                Fragment f = fragmentStateManager.getFragment();
                f.mFragmentManager = this;
                if (isLoggingEnabled(Log.VERBOSE)) {
                    Log.v(TAG, "restoreSaveState: active (" + f.mWho + "): " + f);
                }
                fragmentStateManager.restoreState(mHost.getContext().getClassLoader());
                mFragmentStore.makeActive(fragmentStateManager);
                // Catch the FragmentStateManager up to our current state
                // In almost all cases, this is Fragment.INITIALIZING, but just in
                // case a FragmentController does something...unique, let's do this anyways.
                fragmentStateManager.setFragmentManagerState(mCurState);
            }
        }

      ........
    }

将mArguments赋值到Fragment中

 FragmentStateManager(@NonNull FragmentLifecycleCallbacksDispatcher dispatcher,
            @NonNull ClassLoader classLoader, @NonNull FragmentFactory fragmentFactory,
            @NonNull FragmentState fs) {
        mDispatcher = dispatcher;
    // 通过反射重新创建fragment
        mFragment = fragmentFactory.instantiate(classLoader, fs.mClassName);
        if (fs.mArguments != null) {
            fs.mArguments.setClassLoader(classLoader);
        }
        mFragment.setArguments(fs.mArguments);
        .....
    }

二、解决:

知道了出现的原因,那么就很好解决了

val f = FragmentA()
f.arguments = Bundle().apply { 
    putString("sk", "bbbb")
}

三、使用kotlin优化代码

使用扩展函数 + 委托属性可更加优雅

fun  Fragment.putArgument(
  key: String,
  value: T
) {
  if (this.arguments == null) {
    this.arguments = Bundle()
  }

  when (value) {
    is Int ->  this.requireArguments().putInt(key, value)
    is Boolean -> this.requireArguments().putBoolean(key, value)
    is String -> this.requireArguments().putString(key, value)
    is CharSequence -> this.requireArguments().putCharSequence(key, value)
    is Float -> this.requireArguments().putFloat(key, value)
    is Long -> this.requireArguments().putLong(key, value)
    is Bundle -> this.requireArguments().putBundle(key, value)
    is Serializable -> this.requireArguments().putSerializable(key, value)
    is Parcelable -> this.requireArguments().putParcelable(key, value)
    is Char -> this.requireArguments().putChar(key, value)
    is Byte -> this.requireArguments().putByte(key, value)
    else -> error("不支持的类型, $value")
  }
}

fun  Fragment.getArgument(key: String): T {
  return this.arguments?.get(key) as T
}

使用

// 设置数据
class ActivityA :Activity{
    fun startFragment(){
        val f = FragmentA()
        f.putArgument("book","小黄书")
    }
}


class FragmentA: Fragment{
    val bk: String by lazy {getArgument("book")} // 使用懒加载方式,保证只会获取一次数据
}

你可能感兴趣的:(日常爬坑 - Fragment传参失败)