
今回は、Fragment利用時の画面回転のライフサイクルについてになります。
前回の「Fragmentを使ってみる(ライフサイクルについて)」の続きな感じです(ΦωΦ)フフフ…
画面の回転が発生するとActivityで次の流れで実行されActivityが再構築されます。
1.onPause 2.onStop 3.onDestroy 4.onCreate 5.onStart 6.onResume
FragmentはActivityと同様のライフサイクルを持っているため、
上記の流れと同じイベントが発生されます。
そのため、画面が回転されるたびにFragmentのonCreateViewも毎回呼ばれてしまいます。
その状態でonCreateでFragmentの初期化処理として、
ネット上からデータを持ってきたりとか行うような処理があった場合に、
画面が回転されるたびに、毎回実行されてしまい非効率になっていします(´;ω;`)ブワッ
それを回避する為に用意されているのが、setRetainInstanceになるそうです。
setRetainInstanceを設定するとどうなるかというと、次のサイトを参考に抜粋しましたъ(゚Д゚)グッジョブ!!
Activity再生成時のデータの保存・復元(Fragment#setRetainInstance)
Fragment#setRetainInstance(true) を呼び出すことでどうなるかというと、
Activity再生成時に Fragment#onDestroy、onCreate が呼ばれなくなる。
Activity再生成時には Fragment#onDetach、onAttach が呼び出されるだけ。
Fragmentインスタンスが破棄されないので Fragmentインスタンス内にデータをそのまま保持しておくことが可能。
とのことです。φ(゚Д゚ )フムフム…
早速、使って見て動作を確認したい思います(`・ω・´)シャキーン
■AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.fragment5" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.fragment5.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>
■fragment_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#eeeeee" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> </LinearLayout>
■activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/layout_fragment_1" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ff0000" android:orientation="vertical" /> </LinearLayout>
■MainFragment.java
package com.example.fragment5; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class MainFragment extends Fragment { public static String TAG = "MainFragment"; /* * Fragmentが Activityに対して何らかのコールバックを提供する場合、 * Activityが必要なインタフェースを備えているかどうかチェックなどを行う。 * * @see android.support.v4.app.Fragment#onAttach(android.app.Activity) */ @Override public void onAttach(Activity act) { super.onAttach(act); Log.d(TAG, "Fragment-onAttach"); } /* * Fragment がユーザに見える状態になるまで保持しておくべきコンポーネントの初期化。 * * @see android.support.v4.app.Fragment#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "Fragment-onCreate"); // fragment再生成抑止。Fragment を破棄させないようにする。 setRetainInstance(true); } /* * Fragment が持つ View を構築する状態です。 View を持たない Fragment は、ここで null を返す * * @see * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater, * android.view.ViewGroup, android.os.Bundle) */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "Fragment-onCreateView"); // Bundleで保存されたデータを復元 String title = getArguments().getString("title"); // Log.d("onCreateView", title); // 第1引数:レイアウトXMLファイルのリソースID、 // 第2引数: // 第3引数:trueにするかfalseにするかで戻り値となるルートビュー(View)が変わる。 // trueの場合には、第2引数で渡したViewGrou、falseの場合には第1引数で渡したリソースIDがルートビューになる View view = inflater.inflate(R.layout.fragment_main, container, false); // TextView text = (TextView) view.findViewById(R.id.text_view); text.setText(title); // return view; } /* * Activity の onCreate の状態の処理が終わったことを示す状態です。 * * @see android.support.v4.app.Fragment#onActivityCreated(android.os.Bundle) */ @Override public void onActivityCreated(Bundle bundle) { super.onActivityCreated(bundle); Log.d(TAG, "Fragment-onActivityCreated"); } /* * Fragment の UI が構築され、ユーザに見える状態です。 * * @see android.support.v4.app.Fragment#onStart() */ @Override public void onStart() { super.onStart(); Log.d(TAG, "Fragment-onStart"); } /* * Fragment の UI が構築され、ユーザとのインタラクションが出来るようになった状態です。 * * @see android.support.v4.app.Fragment#onResume() */ @Override public void onResume() { super.onResume(); Log.d(TAG, "Fragment-onResume"); } /* * ユーザが別の画面への遷移をしようとして Fragment から離れていこうとした状態です。 Activity * と同じく、この時点で、永続化するべき情報を保存するようにしておきます。 必ずしもこの後の状態へ遷移し、Fragment * がメモリから破棄されるわけではありません。 * * @see android.support.v4.app.Fragment#onPause() */ @Override public void onPause() { super.onPause(); Log.d(TAG, "Fragment-onPause"); } /* * Fragment がユーザに見えない状態です。 * * @see android.support.v4.app.Fragment#onStop() */ @Override public void onStop() { super.onStop(); Log.d(TAG, "Fragment-onStop"); } /* * Fragment が扱う View などのコンポーネントに紐付いた各種リソースを開放するための状態 ここで Fragment への参照が View * やコンポーネントに残っていると、メモリリークを起こします。 * * @see android.support.v4.app.Fragment#onDestroyView() */ @Override public void onDestroyView() { super.onDestroyView(); Log.d(TAG, "Fragment-onDestroyView"); } /* * Fragment が完全にメモリから破棄される直前の状態です。 * * @see android.support.v4.app.Fragment#onDestroy() */ @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "Fragment-onDestroy"); } /* * Fragment が Activity から切り離される状態です。 * * @see android.support.v4.app.Fragment#onDetach() */ @Override public void onDetach() { super.onDetach(); Log.d(TAG, "Fragment-onDetach"); } }
■MainActivity.java
package com.example.fragment5; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.util.Log; public class MainActivity extends FragmentActivity { public static String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Activity-onCreate"); // Fragmentを管理するFragmentManagerを取得 FragmentManager manager = getSupportFragmentManager(); // 追加や削除などを1つの処理としてまとめるためのトランザクションクラスを取得 FragmentTransaction tx = manager.beginTransaction(); // 1つ目のfragmentを生成 MainFragment fragment1 = new MainFragment(); Bundle bundle1 = new Bundle(); bundle1.putString("title", "キタ――(゚∀゚)――!!"); // フラグメントに渡す値をセット fragment1.setArguments(bundle1); // Fragment をスタックに追加する // メインレイアウトに対して追加先のビューのID、Fragment、Fragmentのタグ。 // add() したときに既にバックスタックに同じタグの Fragment が存在する場合、 // Fragment は新規作成されず、既にインスタンス化してある Fragment が再表示される。 tx.add(R.id.layout_fragment_1, fragment1, "layout_fragment_1"); // tx.add(R.id.layout_fragment_2, fragment2, "layout_fragment_2"); // tx.commit(); } @Override public void onStart() { super.onStart(); Log.d(TAG, "Activity-onStart"); } @Override public void onRestart() { super.onRestart(); Log.d(TAG, "Fragment-onRestart"); } @Override public void onResume() { super.onResume(); Log.d(TAG, "Activity-onResume"); } @Override public void onPause() { super.onPause(); Log.d(TAG, "Activity-onPause"); } @Override public void onStop() { super.onStop(); Log.d(TAG, "Activity-onStop"); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "Activity-onDestroy"); } }
実行結果は次のようになります。
setRetainInstance(true);を入れる前の挙動は次のようになっていましたが、
そのうち、6、10番目が呼ばれなくなっていました。
(onDestroy、onCreate が呼ばれなくなる)
ただし、15番目でFragmentのonCreateが呼ばれてしまっていましたΣ(゚Д゚ υ) アリャ
1.Fragment-onPause 2.Activity-onPause 3.Fragment-onStop 4.Activity-onStop 5.Fragment-onDestroyView 6.Fragment-onDestroy ← 呼ばれなくなる 7.Fragment-onDetach 8.Activity-onDestroy 9.Fragment-onAttach 10.Fragment-onCreate ← 呼ばれなくなる 11.Activity-onCreate 12.Fragment-onCreateView 13.Fragment-onActivityCreated 14.Fragment-onAttach 15.Fragment-onCreate ← 呼ばれてる(´・ω`・)エッ? 16.Fragment-onCreateView 17.Fragment-onActivityCreated 18.Fragment-onStart 19.Fragment-onStart 20.Activity-onStart 21.Activity-onResume 22.Fragment-onResume 23.Fragment-onResume
(´ε`;)ウーン…って悩んでいたら同じように悩んでる方をハヶ━m9( ゚д゚)っ━ン!!
【Android】Fragmentで画面を傾けた際に初期化しないようにsetRetainInstanceを使えって言うけど、うまく行かないときの対処法
フラグメントで回転すると、エラーになる。
原因的には上記の設定で、FragmentのonCreateなどは抑制できたけど、
ActivityのonCreateは実行されてしまいますので、
その結果、ActivityのonCreateで行っているFragmentの生成処理が実行されてしまい、
FragmentのonCreateが2度呼ばれているみたいでした。
なので、既にFragmentが作成されているかチェックをしてあげて、
ある場合には実行しないようにすることで回避(゚∀゚)キタコレ!!
次のチェックをMainActivityのonCreateを次のようにします。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "Activity-onCreate"); // Fragmentを管理するFragmentManagerを取得 FragmentManager manager = getSupportFragmentManager(); //既にFragmentが作成されているかチェック if (manager.findFragmentByTag("layout_fragment_1") == null) { // 追加や削除などを1つの処理としてまとめるためのトランザクションクラスを取得 FragmentTransaction tx = manager.beginTransaction(); // 1つ目のfragmentを生成 MainFragment fragment1 = new MainFragment(); Bundle bundle1 = new Bundle(); bundle1.putString("title", "キタ――(゚∀゚)――!!"); // フラグメントに渡す値をセット fragment1.setArguments(bundle1); // Fragment をスタックに追加する // メインレイアウトに対して追加先のビューのID、Fragment、Fragmentのタグ。 // add() したときに既にバックスタックに同じタグの Fragment が存在する場合、 // Fragment は新規作成されず、既にインスタンス化してある Fragment が再表示される。 tx.add(R.id.layout_fragment_1, fragment1, "layout_fragment_1"); // tx.add(R.id.layout_fragment_2, fragment2, "layout_fragment_2"); // tx.commit(); } }
その結果、画面回転時のライフサイクルが次のようになりました。
1.Fragment-onPause 2.Activity-onPause 3.Fragment-onStop 4.Activity-onStop 5.Fragment-onDestroyView 6.Fragment-onDetach 7.Activity-onDestroy 8.Fragment-onAttach 9.Activity-onCreate 10.Fragment-onCreateView 11.Fragment-onActivityCreated 12.Fragment-onStart 13.Activity-onStart 14.Activity-onResume 15.Fragment-onResume
最初の頃に比べてだいぶスッキリ( ´∀`)bグッ!
そして、前回の「Fragmentを使ってみる(ライフサイクルについて)」で謎だった
Fragment画面回転時にonStartが2度実行されてしまう現象も解消されたっぽい!ワーイヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノワーイ
これが原因なのかな?o(゚Д゚ = ゚Д゚)o キョロキョロ
まとめるとFragmentの画面回転時のライフサイクルパターンとして次のケースがある。
1)Fragmentを単純に作成した場合
1.Fragment-onPause 2.Activity-onPause 3.Fragment-onStop 4.Activity-onStop 5.Fragment-onDestroyView 6.Fragment-onDestroy 7.Fragment-onDetach 8.Activity-onDestroy 9.Fragment-onAttach 10.Fragment-onCreate 11.Activity-onCreate 12.Fragment-onCreateView 13.Fragment-onActivityCreated 14.Fragment-onAttach 15.Fragment-onCreate 16.Fragment-onCreateView 17.Fragment-onActivityCreated 18.Fragment-onStart 19.Fragment-onStart 20.Activity-onStart 21.Activity-onResume 22.Fragment-onResume 23.Fragment-onResume
2)FragmentでsetRetainInstance(true);を設定した場合 (onDestroy、onCreate が呼ばれなくなる)
1.Fragment-onPause 2.Activity-onPause 3.Fragment-onStop 4.Activity-onStop 5.Fragment-onDestroyView 6.Fragment-onDetach 7.Activity-onDestroy 8.Fragment-onAttach 9.Activity-onCreate 10.Fragment-onCreateView 11.Fragment-onActivityCreated 12.Fragment-onAttach 13.Fragment-onCreate 14.Fragment-onCreateView 15.Fragment-onActivityCreated 16.Fragment-onStart 17.Fragment-onStart 18.Activity-onStart 19.Activity-onResume 20.Fragment-onResume 21.Fragment-onResume
3)Fragmentを作成時に、Activity側で作成済みのFragmentかチェックと、 setRetainInstance(true);を設定した場合
1.Fragment-onPause 2.Activity-onPause 3.Fragment-onStop 4.Activity-onStop 5.Fragment-onDestroyView 6.Fragment-onDetach 7.Activity-onDestroy 8.Fragment-onAttach 9.Activity-onCreate 10.Fragment-onCreateView 11.Fragment-onActivityCreated 12.Fragment-onStart 13.Activity-onStart 14.Activity-onResume 15.Fragment-onResume
3番目が一番(・∀・)イイネ!!
以上です(`・ω・´)ゞビシッ!!