Bundle对象的变异

时间:2017-08-18 09:23:15

标签: 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");
    }
}

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);

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

  1. 为什么会这样?
  2. 如何解决?
  3. [更新

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

        final Bundle args666 = args != null ? cloneThroughSerialization(args) : args;
        final Bundle args777 = args != null ? (Bundle) args.clone() : args;
    

    enter image description here

    [UPDATE2]

    实际上,问题不在postDelayed来电。让我们看一下调用堆栈:

    enter image description here

    goRightToTheCollectionScreen中创建并打包了Bundle(没有任何可疑,之后没有变异)。

    我想,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;
    }
    

3 个答案:

答案 0 :(得分: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;
        }
    

答案 1 :(得分:2)

您发布的代码似乎没问题,因此您的软件包可能会在其他位置进行修改(即使您的postDelayed延迟为0,runnable稍后会执行,可能会同时修改软件包)。尝试直接执行它而不使用postDelayed,以查看问题是否仍然存在。您可以发布更多代码,也许我们可以找出您触摸该捆绑包的其他位置。

如果没有其他帮助,您可以随时将方法从API26复制到您的代码并使用它(边缘情况 - 这似乎是一个简单的问题,所以你不应该这样做)

答案 2 :(得分:0)

无法将bundle args建模为“empty”。用片段调用Handler示例和指定的args值制作简单的代码。如果我建模错误请给我提示

public class MainActivity extends FragmentActivity {

private  static final String LOG_TAG = "Main activity";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.news_articles)

        // Create an instance of ExampleFragment
        final HeadlinesFragment firstFragment = new HeadlinesFragment();

        // Add the fragment to the 'fragment_container' FrameLayout
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, firstFragment).commit();

        long delay = 0;
        final boolean addToBackStack = true;
        final Bundle args666 = new Bundle();
        args666.putInt("hi", 666);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                doOpenFragment(firstFragment, addToBackStack, args666);
            }
        }, delay);
    }

protected void doOpenFragment(HeadlinesFragment firstFragment,
                              final boolean addToBackStack,
                              final Bundle args){
    int value = args.getInt("hi");
    Log.d(LOG_TAG, "The value is " + value);
}
}