2013年10月29日火曜日

Fragmentを使ってみる(画面回転時のライフサイクルについて)

今回は、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番目が一番(・∀・)イイネ!!

以上です(`・ω・´)ゞビシッ!!

参考URL

0 件のコメント:

コメントを投稿