FragmentTransaction.commitAllowingStateLoss

非同期処理とフラグメント操作を組み合わせると少し問題が発生する。
前回の項をベースに、CustomAsyncTask.javaを追加し、

適当な非同期処理として例えば10秒経ったら、MainActivityに定義したreplaceFragmentというメソッドを呼ぶ。
MainActivity.javaで、

package com.bgstation0.android.sample.fragmenttransaction_;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

	// メンバフィールドの定義
	CustomAsyncTask task = null;	// CustomAsyncTaskオブジェクトtaskをnullに.
	Context context = null;	// contextをnullで初期化.
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // contextにthisを格納.
        context = this;
        
        // savedInstanceがnullの時.
        if (savedInstanceState == null){

        	// Buttonの初期化.
            Button button1 = (Button)findViewById(R.id.button1);	// button1を取得.
            button1.setOnClickListener(new OnClickListener() {	// リスナーをセット.
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				// CustomAsyncTaskによる非同期処理を生成し, 実行.
    		    	task = new CustomAsyncTask(context);	// CustomAsyncTaskオブジェクトを作成し, taskに格納.
    		    	task.execute(10);	// task.executeに10を渡して実行.
    			}
    			
    		});
            Button button2 = (Button)findViewById(R.id.button2);
            button2.setOnClickListener(new OnClickListener() {	// リスナーをセット.
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				
    			}
    			
    		});
            
        }
        
    }
    
    // フラグメントの置換
    public void replaceFragment(){
    	
    	try{
    		FragmentManager fragmentManager = getFragmentManager();	// fragmentManagerの取得.
    		Fragment1 fragment1 = new Fragment1();	// fragment1を生成.
    		FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();	// fragmentTransactionの取得.
    		fragmentTransaction.replace(R.id.framelayout1, fragment1, "fragment1");	// fragment1をreplace.
    		fragmentTransaction.commit();
    		Log.d("MainActivity", "Fragment1");
    		Toast.makeText(context, "replace success!", Toast.LENGTH_LONG).show();
    	}
    	catch(Exception ex){
    		Toast.makeText(context, ex.toString(), Toast.LENGTH_LONG).show();
    		Log.d("MainActivity", ex.toString());
    	}
    }
    
}

Button1を押したら、CustomAsyncTaskが実行され、10秒後に、replaceFragmentによって、フラグメント置換処理が行われる。
これをやってみるのだが、通常は、

Buttonを押して何もせず10秒待つ

Buttonを押して何もせず10秒待つと、

Fragmentが追加され、用意しておいたToastも出る。
Fragmentが追加され、用意しておいたToastも出る。

Fragmentが追加され、用意しておいたToastも出る。
しかし、

ここで10秒待たずに
ここで10秒待たずに

ここで10秒待たずに、

ホームボタンなどで非表示にしてしまうと
ホームボタンなどで非表示にしてしまうと

ホームボタンなどで非表示にしてしまうと、

10秒後にこんなExceptionが出てしまう
10秒後にこんなExceptionが出てしまう

10秒後にこんなExceptionが出てしまう。

ログにもバッチリと出てる
ログにもバッチリと出てる

ログにもバッチリと出てる。
これは、Fragmentを非表示にしたときにonSaveInstanceStateで状態を保存するのだが、その後にFragmentを操作してしまうと、onRestoreInstanceStateで復元する時に保存した状態と操作後の状態が異なってしまい、一貫性がなくなってしまうため、例外を発生させるようになっているため。
そこで、

commitをcommitAllowingStateLossに差し替えると、保存しておいた状態を失ってでも操作を実行するようになる。

FragmentTransaction  |  Android Developers

つまり、

この状態でButton1を押す
この状態でButton1を押す

この状態でButton1を押す。
非同期処理の間に、

ホーム画面にして
ホーム画面にして

ホーム画面にして、

10秒経っても、今度は置換成功。
10秒経っても、今度は置換成功。

10秒経っても、今度は置換成功。

アプリをまた選んで表示状態に戻す
アプリをまた選んで表示状態に戻す

アプリをまた選んで表示状態に戻す。

ちゃんとFragmentは置換されてる
ちゃんとFragmentは置換されてる

ちゃんとFragmentは置換されてる。

Sample/android/FragmentTransaction/commitAllowingStateLoss/src/FragmentTransaction at master · bg1bgst333/Sample · GitHub