Fragment.setRetainInstance

回転した時に、ActivityやFragmentの状態を保持する方法はいろいろある。
strings.xmlで、

Fragment1の中にTextViewとButton。
fragment1_main.xmlは、

このようにTextViewとButton。
activity_main.xmlは、

FrameLayoutだけ。
Fragment1.javaは、

package com.bgstation0.android.sample.fragment_;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

//フラグメント1
public class Fragment1 extends Fragment implements OnClickListener{

	// メンバフィールド
	static final String TAG = "Fragment1";	// TAGを"Fragment1"で初期化.
	public int mNumber = 0;	// mNumberを0で初期化.
	
	// コンストラクタ
	public Fragment1(){
		
	}
	
	// フラグメント生成時
	@Override
	public void onCreate(Bundle savedInstanceState){
		
		// 既定の処理.
		super.onCreate(savedInstanceState);	// 親クラスのonCreateを呼ぶ.
		
		// ログを残す.
		Log.d(TAG, "Fragment1.onCreate");	// "Fragment1.onCreate"とログに残す.
		
	}
	
	// フラグメント破棄時
	@Override
	public void onDestroy(){
		
		// ログを残す.
		Log.d(TAG, "Fragment1.onDestroy");	// "Fragment1.onDestroy"とログに残す.
		
		// 既定の処理.
		super.onDestroy();	// 親クラスのonDestroyを呼ぶ.
		
	}
	
	// ビュー生成時
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
		
		// ログを残す.
		Log.d(TAG, "Fragment1.onCreateView");	// "Fragment1.onCreateView"とログに残す.
				
		// 既定の処理.
		View view = inflater.inflate(R.layout.fragment1_main, null);	// inflater.inflateでR.layout.fragment1_mainからビューを作成.
        Button button1 = (Button)view.findViewById(R.id.fragment1_button1);	// Fragment1のbutton1を取得.
        button1.setOnClickListener(this);	// リスナーとしてthisをセット.
        TextView tv = (TextView)view.findViewById(R.id.fragment1_textview);
        tv.setText(Integer.toString(mNumber));
		return view;
		
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		mNumber++;	// mNumberを増やす.
		View view = getView();
		TextView tv = (TextView)view.findViewById(R.id.fragment1_textview);
        tv.setText(Integer.toString(mNumber));
	}
	
}

Buttonを押したら、mNumberを増やして、TextViewに表示。
MainActivity.javaは、

生成時、Fragment1を追加。
funcというログ出力だけのメソッドを用意。
ここまでやって最初は、

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.bgstation0.android.sample.fragment_"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="15"
        android:targetSdkVersion="15" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

普通のAndroidManifest.xmlにしている。

起動時
起動時

起動時。
Buttonを押すと、

TextViewの数値が増える
TextViewの数値が増える

TextViewの数値が増える。
横にすると、

数値は0に戻る
数値は0に戻る

数値は0に戻る。
数値を維持したい場合、

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.bgstation0.android.sample.fragment_"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="15"
        android:targetSdkVersion="15" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="orientation|keyboardHidden|screenSize" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

android:configChangesのorientationで、回転時のActivity/Fragment再生成を阻止できる。

起動時
起動時

起動時。

Buttonで増える
Buttonで増える

Buttonで増える。

確かに維持はできてる
確かに維持はできてる

確かに維持はできてる。

Activityは再生成されない
Activityは再生成されない

Activityは再生成されない。

Fragmentも回転による再生成では呼ばれていない。これは起動時の生成。
Fragmentも回転による再生成では呼ばれていない。これは起動時の生成。

Fragmentも回転による再生成では呼ばれていない。これは起動時の生成。

ところでアクションバーのドロイド君アイコンが小さくなっていることに気付いただろうか。
android:configChangesのorientationだと、Activity/Fragmentが再生成されないので、向きによってデザインを変えたり、UIを意図的に更新したり、見た目上のアイコンの大きさを変えないとか、再生成されないと困る場合が少しある。
Activityは再生成させ、Fragmentは再生成させない方法が、Fragment.setRetainInstanceをtrueにすること。

Fragment  |  Android Developers

AndroidManifest.xmlを、

元に戻して、Fragment1.javaを、

onCreateでsetRetainInstanceをtrueに。
onAttachで引数のactivityをMainActivityのmContextにキャストしてfuncを呼んでいるが、これ自体はどうでもいいのだが、onDetachでmContextをnullにしているのが重要。参照したままだとメモリリークするらしい。

起動時
起動時

起動時。

Buttonで増やして
Buttonで増やして

Buttonで増やして、

回転で維持。アイコンも見た目上の大きさは変わらない。
回転で維持。アイコンも見た目上の大きさは変わらない。

回転で維持。アイコンも見た目上の大きさは変わらない。

Fragment1がonDetachされているのにonDestroyされていないのが破棄されていない証拠
Fragment1がonDetachされているのにonDestroyされていないのが破棄されていない証拠

Fragment1がonDetachされているのにonDestroyされていないのが破棄されていない証拠。
mCurrentFragmentでインスタンスがまったく同じなのもわかる。
(あくまでFragmentインスタンスが維持されるかどうかの話がメインで、アクションバーのアイコンやテキストの大きさについてはいずれ別途扱った方がいいかも。混乱誤解を生みやすいし僕も理解できてない。)

Sample/android/Fragment/setRetainInstance/src/Fragment at master · bg1bgst333/Sample · GitHub