Bundle对象的变异

Max*_*m G 8 android asynchronous android-handler android-bundle

我正在使用遗留代码,我在此函数中发现了一个不一致的行为:

@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, but not all 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

openFragment得到非空的Bundle args,但doOpenFragment会得到空的Bundle.片段通过调用来实现commitAllowingStateLoss()

快速修复可以使用Bundle.clone():

    final Bundle args666 = (Bundle) args.clone();
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            doOpenFragment(fragmentClass, addToBackStack, args666);
        }
    }, delay);
Run Code Online (Sandbox Code Playgroud)

它不会处理所有情况,deepCopy仅在api26中可用.

  1. 为什么会这样?
  2. 怎么解决?

[ 更新 ]

我玩@ Pavel的解决方案,事情变得更加怪异

    final Bundle args666 = args != null ? cloneThroughSerialization(args) : args;
    final Bundle args777 = args != null ? (Bundle) args.clone() : args;
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

[UPDATE2]

实际上,问题不在于postDelayed通话.我们来看看调用堆栈:

在此输入图像描述

goRightToTheCollectionScreenBundle中创建并打包(没有任何可疑,之后没有变异).

我猜,内部两个调用的问题来源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 {
        for (int i = 0; i < 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;
}
Run Code Online (Sandbox Code Playgroud)

小智 3

  1. 在调用 run() 之前,其他一些代码会修改相同的 Bundle。问题出在你的代码中。
  2. 您可以通过序列化进行深度克隆。

        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;
        }
    
    Run Code Online (Sandbox Code Playgroud)