Мутация объекта 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, но 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.
- Почему это происходит?
- Как это исправить?
[ОБНОВЛЕНИЕ]
Я играл с решением @Pavel, и все становится страннее
final Bundle args666 = args != null ? cloneThroughSerialization(args) : args;
final Bundle args777 = args != null ? (Bundle) args.clone() : args;
[UPDATE2]
На самом деле, проблема не с postDelayed
вызов. Давайте посмотрим на стек вызовов:
в 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 ответа
- Другой код модифицирует тот же Bundle перед вызовом run(). Проблема в вашем коде.
Вы можете глубоко клонировать через сериализацию.
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; }
Код, который вы опубликовали, выглядит нормально, поэтому, вероятно, ваш пакет изменен в другом месте (даже если ваша задержка postDelayed равна 0, исполняемый файл будет выполнен немного позже, и, возможно, вы тем временем модифицируете пакет). Попробуйте выполнить его напрямую без postDelayed, чтобы увидеть, сохраняется ли проблема. Вы можете опубликовать больше своего кода, возможно, мы сможем выяснить, где еще вы касаетесь этого пакета.
Если больше ничего не помогает, вы всегда можете скопировать метод из API26 в свой код и использовать его (крайний случай - это кажется простой проблемой, поэтому вам не нужно это делать)
Не удалось смоделировать связку аргументов, идущих в "пустое" Сделан простой код с фрагментом, вызывающим пример 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);
}
}