Как объяснено в Потеря состояния транзакций и активности фрагмента под маркером "Избегать выполнения транзакций внутри методов асинхронного обратного вызова" :
Избегайте выполнения транзакций внутри методов асинхронного обратного вызова. Это включает в себя часто используемые методы, такие как AsyncTask#onPostExecute()
и LoaderManager.LoaderCallbacks#onLoadFinished()
.Проблема с выполнением транзакций в этих методах заключается в том, что они не знают о текущем состоянии жизненного цикла Activity, когда они вызываются.
Похоже, это проблема, которая возникает у вас с updateView()
вызывается из асинхронной задачи, но давайте проверим эту гипотезу.
Следующее демонстрационное приложение создает фрагмент, имитирует фоновую обработку и обратный вызов, который имитирует ваш асинхронный обратный вызов.В коде есть флаг: mFixIt
, когда установлено значение true, приложение ведет себя должным образом (не взрывается), а если значение false, приложение перестает работать.
С mFixIt == false.Триггер - это кнопка «Домой», которая переводит приложение в состояние остановки:
Вот трассировка стека:
14967-15003 E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.example.illegalstatepopbackstack, PID: 14967
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2080)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2106)
at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:832)
at com.example.illegalstatepopbackstack.MainActivity.updateView(MainActivity.java:70)
at com.example.illegalstatepopbackstack.ui.main.MainFragment$1.run(MainFragment.java:63)
at java.lang.Thread.run(Thread.java:764)
Теперь с mFixIt
== true.Разница на этот раз в том, что приложение распознает асинхронный обратный вызов, когда действие находится в остановленном состоянии, записывает, что это произошло, и завершает обработку при перезапуске приложения.Визуал - это просто нажатие кнопки «Домой» и восстановление из «последних».Приложение просто вставляет фрагмент в начале, а при повторном запуске изменяет верхний текст TextView
и извлекает фрагмент из backstack.
Как видно, обработка завершается, как и ожидалось.
Это тривиальный пример.Если ваша обработка более сложна или вы просто хотите более формально справиться с этой ситуацией, я рекомендую взглянуть на это решение .
Вот код для демонстрационного приложения:
MainActivity.java
public class MainActivity extends AppCompatActivity {
// Set to true to fix the problem; false will cause the IllegalStateException
private boolean mFixIt = false;
private MainFragment mFragment;
private TextView mTextView;
private boolean mIsPaused;
private boolean mUpdateViewNeeded;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mTextView = findViewById(R.id.textView);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
// Create out fragment
mFragment = MainFragment.newInstance();
fm.beginTransaction()
.replace(R.id.container, mFragment)
.addToBackStack(FRAGMENT_TAG)
.commit();
} else {
// Find the restored fragment.
mFragment = (MainFragment) fm.findFragmentByTag(FRAGMENT_TAG);
}
}
@Override
protected void onStop() {
super.onStop();
// Simulate a background task that does something useful. This one just waits a few
// second then does a callback to updateView(). The activity will be fully paused by then.
mFragment.doSomethingInBackground();
mIsPaused = true;
Log.d("MainActivity","<<<< stopped");
}
@Override
protected void onStart() {
super.onStart();
mIsPaused = false;
if (mUpdateViewNeeded) {
// Execute delayed processing now that the activity is resumed.
updateView(getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG));
}
}
public void updateView(Fragment fragment) {
if (mIsPaused && mFixIt) {
// Delay processing
mUpdateViewNeeded = true;
} else {
// Do out update work. If we are paused, this will get an IllegalStateException. If
// we are resumed, this will work as intended.
mTextView.setText("Replaced...");
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
mUpdateViewNeeded = false;
}
}
public static final String FRAGMENT_TAG = "MyFragment";
}
MainFragment.java
public class MainFragment extends Fragment {
MainActivity mMainActivity;
public static MainFragment newInstance() {
return new MainFragment();
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.main_fragment, container, false);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mMainActivity = (MainActivity) context;
}
@Override
public void onDetach() {
super.onDetach();
mMainActivity = null;
}
@Override
public void onStart() {
super.onStart();
}
public void doSomethingInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mMainActivity != null) {
mMainActivity.updateView(MainFragment.this);
}
}
}).start();
}
}
main_activity.xml
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light"
android:gravity="center"
android:text="To be replaced..."
android:textSize="36sp"
android:textStyle="bold" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" />
main_fragment.xml
<android.support.constraint.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_green_light"
tools:context=".ui.main.MainFragment">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainFragment"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>