【Fragment】Android Fragmentライフサイクルの詳細(図文)


Fragment API(ミラーアドレス):
http://androiddoc.qiniudn.com/reference/android/support/v4/app/Fragment.html
Fragment/Activityライフサイクルチャート
画像の出所:https://github.com/xxv/android-lifecycle
【Fragment】 Android Fragment生命周期详解(图文)_第1张图片
Fragment状態変化の一般的な方法
1. onHiddenChanged(boolean hidden)
Activity内部でshowとhideメソッドでFragmentを切り替えると,状態遷移が起こる.
2. setUserVisibleHint(boolean isVisibleToUser)
ViewPagerとFragmentを併用する場合,ViewPagerはこの方法でFragmentの表示と非表示を制御するので,この方法でViewPagerにおけるFragmentのリラッドモードを実現できる.
3. isVisible()
Fragmentオブジェクトがユーザーに表示されている場合はtrueを返します.これは、1.Activityに追加されたことを意味します.2.ビューオブジェクトがウィンドウにバインドされています.3.隠されていません.
isVisible()メソッドとgetUserVisibleHint()メソッドの違いに注意してください.
isVisible()メソッドはfragmentのonResume()ライフサイクルメソッドで使用でき、appが他のインタフェースからfragmentがattachのactivityに入ったときに現在のFragmentが表示されるかどうかを判断するために使用されます.onHiddenChangedメソッドの使用に合わせて、Fragmentインタフェースのリフレッシュによく使用されます.
特に、他のActivityからsetResultメソッドにより複数のFragmentを含むActivityに戻り、ターゲットActivityのonActivity Resultメソッドで非表示の状態を表示する1つのFragmentを指定する場合、isVisibleメソッドだけでは表示するFragmentのonHiddenChangedメソッドとonResumeメソッドを区別できない場合があるonActivity ResultメソッドはonResumeメソッドよりも優先されるため、この場合に使用する必要があります. isResumed()メソッドは、インタフェースのリフレッシュの呼び出しを制限します.
    @Override
    public void onResume() {
        super.onResume();
        if (isVisible()){
            //      ,     
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (isVisible() && isResumed()){
            //      ,     
        }
    }

4. setArguments(Bundle args)
Fragmentの使用に関するよくある質問
1.Fragment切り替えによるUI重複の問題
質問:
   
問題シミュレーションの再現:
1.まず開きます.デフォルトで選択されているのは、上の画像が正常であるように、最初のtabです.2.tab 2に切り替え、tab 1 hideを落とす.3.tab 1に戻っても、tab 1がfragmentに対応するライフサイクルはトリガーされません.4.それからホームキーはバックグラウンドに入って、私はactivityのonPause()の中で手動でIndexFragmentに空を割り当てて、長時間バックグラウンドをシミュレートして、システムはこの参照を破棄しました:IndexFragment=null;5.再起動しますが、tab 1のfragmentインスタンスはメモリに残っています.ただし、彼の参照は破棄されました.6.tab 2にカットします.ここではtab 1のhideをshow tab 2にしますが、tab 1のfragment参照が空なのでhideできません.tab 2がtab 1に重なるスクリーンが現れます.7.tab 1にカットすると、tab 1はオブジェクトを繰り返し作成します.
問題の分析:
fragmentの切り替えには2つの方法があります.
1.replace方式
transaction.replace(R.id.content, IndexFragment);
replace方式では上記の問題は発生しませんが、オブジェクトの作成が繰り返され、replaceがライフサイクルをすべて実行するたびに、これらのライフサイクル関数でデータを抽出すると、リフレッシュデータが繰り返しロードされます.
2.add-hide-show方式
transaction.add(R.id.content, IndexFragment); transaction.hide(otherfragment); transaction.show(thisfragment);
public class TestFragmentTab extends FragmentActivity {
	
	private static final String TAG = "TestFragmentTab";
	
	private int tabIds[] = new int[]{
			R.id.home_tab_main,
			R.id.home_tab_search,
			R.id.home_tab_category,
	};
	
	private FragmentTab1 mTab1;
	private FragmentTab2 mTab2;
	private FragmentTab3 mTab3;

	@Override
	public void onAttachFragment(Fragment fragment) {
		// TODO Auto-generated method stub
		super.onAttachFragment(fragment);
		Log.d(TAG,"onAttachFragment");
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(TAG,"onDestroy");
	}

	@Override
	protected void onPause() {
		// TODO Auto-generated method stub
		super.onPause();
		Log.d(TAG,"onPause");
	}

	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		super.onResume();
		Log.d(TAG,"onResume");
	}

	@Override
	protected void onStart() {
		// TODO Auto-generated method stub
		super.onStart();
		Log.d(TAG,"onStart");
	}

	@Override
	protected void onStop() {
		// TODO Auto-generated method stub
		super.onStop();
		Log.d(TAG,"onStop");
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		Log.d(TAG,"onCreate");
		setContentView(R.layout.layout_test_fragment_tab);
		
		RadioGroup tabButtonGroup = (RadioGroup) findViewById(R.id.bottom_bar);
		tabButtonGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
			
			@Override
			public void onCheckedChanged(RadioGroup group, int checkedId) {
				// TODO Auto-generated method stub
				for (int i = 0; i < tabIds.length; i++) {
					if (tabIds[i] == checkedId) {
						setSelection(i);
						break;
					}
				}
			}
		});
		
		setSelection(0);
	}
	
	private void setSelection(int position){
		
		FragmentManager fm = getSupportFragmentManager();
		FragmentTransaction ft = fm.beginTransaction();
		hideAllFragments(ft);
		
		switch (position) {
		case 0:
			if (mTab1 == null) {
				mTab1 = new FragmentTab1();
				ft.add(R.id.content, mTab1);
			}else {
				ft.show(mTab1);
			}
			break;
			
		case 1:
			if (mTab2 == null) {
				mTab2 = new FragmentTab2();
				ft.add(R.id.content, mTab2);
			}else {
				ft.show(mTab2);
			}
			break;
			
		case 2:
			if (mTab3 == null) {
				mTab3 = new FragmentTab3();
				ft.add(R.id.content, mTab3);
			}else {
				ft.show(mTab3);
			}
			break;

		default:
			break;
		}
		
		ft.commit();
	}
	
	private void hideAllFragments(FragmentTransaction ft){
		if (mTab1 != null) {
			ft.hide(mTab1);
		}
		
		if (mTab2 != null) {
			ft.hide(mTab2);
		}
		
		if (mTab3 != null) {
			ft.hide(mTab3);
		}
	}

}

2つ目の方法でFragmentを切り替えると、上図のようにFragmentが重なるという問題がよくあります.直接backキーがアプリケーションを終了してからアクセスすると、この問題は発生しません.アプリケーションが強制的に閉じられた後(携帯電話の執事ソフトウェアで手動で強く閉じたり、システムがメモリを節約するために自動的にアプリケーションを閉じたり)、再びアプリケーションに入ると、このようなスクリーン現象が発生します.
解析により、通常のbackキーがアプリケーションを終了すると、Activityおよび所属するFragmentオブジェクトは破棄されるため、再度アクセスするとTabに切り替えたときに対応するFragmentオブジェクトが作成されることが分かった.しかし、強制的にアプリケーションを閉じるとActivityは回収されますが、Fragmentオブジェクトは保持され、再びアプリケーションに移行すると、システムはFragmentのonAttachメソッドを呼び出してActivityに付加し、その後、2つのfragmentのonCreateViewメソッドをそれぞれ呼び出すので、この2つのFragmentに対応するView階層はActivityのView階層に追加されます.Fragmentを切り替えるとすべてのfragmentが非表示になってから選択したオブジェクトが表示されますが、ActivityではFragmentオブジェクトのメンバー変数が初期化されていないため、再びfragmentオブジェクトがインスタンス化され、その後add、show、hideは2回目に作成されたオブジェクトで操作されます.これまで保持されていたfragmentオブジェクトのビュー階層がActivityビューに反映され、hideされないため、上述したオーバーラップ現象が発生した.
解決策は3つあります.
1つ目:
ActivityのonAttachFragmentメソッドには、onAttachメソッドに対応するFragmentオブジェクトであるfragmentパラメータがあり、このfragmentオブジェクトを判断することによって、我々のFragmentTabXクラスに属し、クラスがまだインスタンス化されていない場合、Activityのメンバー変数mFragmentTabXがそのfragmentオブジェクトを指し、これにより、add/show/hideを元のfragmentオブジェクト上で操作できるため、オーバーラップ現象は発生しません.
@Override
	public void onAttachFragment(Fragment fragment) {
		// TODO Auto-generated method stub
		super.onAttachFragment(fragment);
		Log.d(TAG,"onAttachFragment");
		
		if (mTab1 == null && fragment instanceof FragmentTab1) {
			mTab1 = (FragmentTab1)fragment;
		}else if (mTab2 == null && fragment instanceof FragmentTab2) {
			mTab2 = (FragmentTab2)fragment;
		}else if (mTab3 == null && fragment instanceof FragmentTab3) {
			mTab3 = (FragmentTab3)fragment;
		}
	}

2つ目:
3つのパラメータのadd関数を呼び出し、タグにTagパラメータを追加します.
transaction.add(R.id.content, IndexFragment,”Tab1″);
次に、fragmentの初期化前に、参照判断を追加し、対応する参照を探し、空のままであればfragmentコンストラクション関数を呼び出して初期化します.
IndexFragment=FragmentManager.findFragmentByTag(“Tab1″);
3つ目:
onCreate関数に入ると、savedInstanceStateがnullであるかどうかを判断し、nullでない場合は、この4つのfragmentが保存されていることを示します.addという4つのfragmentを再削除するのではなく、Tagが以前保存していたデータから直接読み取ります.
        FragmentManager fManager;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		fManager = getFragmentManager();
		if (savedInstanceState != null) {
			allFrg = (AllOfficialAccountFragment) fManager.findFragmentByTag("allFrg");
			movieFrg = (MovieOfficialAccountFragment) fManager.findFragmentByTag("movieFrg");
			newsFrg = (NewsOfficialAccountFragment) fManager.findFragmentByTag("newsFrg");
			otherFrg = (OtherOfficialAccountFragment) fManager.findFragmentByTag("otherFrg");			
		}
		super.onCreate(savedInstanceState);		
	}

2.Fragment not attached to Activity異常
この問題は、FragmentがActivityに関連付けられていない場合やAttachのActivityが既にdestroyになっている場合に、コンテキストContextを必要とする関数を呼び出し、fragmentのgetResource()のような一般的な方法があるためです.解決策は主に以下のとおりです.
一:呼び出したコードをonStart()メソッドに移動する.
3:AttachされたActivityの対応する方法、例えばgetActivity().getResource...;
二:呼び出されたコードの前にFragmentのisAdd()メソッドを追加して判断し、この方法を推奨します!
3.FragmentでonActivity Result()注意通常、Fragment ActivityではFragmentをネストして使用します.Fragmentでは再びFragmentをネストすることもあります.この場合、2番目の階層およびより深い階層のサブFragmentオブジェクトがonActivity Result()イベントを受信できません.
FragmentActivityソースの表示:
	 @Override 
	 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
		 mFragments.noteStateNotSaved(); 
	     int index = requestCode>>16; 
	     if (index != 0) { 
	              index--; 
	              if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { 
	                   Log.w(TAG, "Activity result fragment index out of range: 0x" 
	                           + Integer.toHexString(requestCode)); 
	                   return; 
	               } 
	               Fragment frag = mFragments.mActive.get(index); 
	               if (frag == null) { 
	                   Log.w(TAG, "Activity result no fragment exists for index: 0x" 
	                           + Integer.toHexString(requestCode)); 
	               } else { 
	                   frag.onActivityResult(requestCode&0xffff, resultCode, data); 
	               } 
	               return; 
		} 
	             
	    super.onActivityResult(requestCode, resultCode, data); 
	}

FragmentActivityがネストされたFragmentを処理していない場合、つまり、第1レベルのFragmentにコールバックしただけで、配布を継続していないことがわかります.
したがって、第1レベルのFragmentのonActivity Result()でonActivity Resultイベントの配布を制御する必要があります.
4.FragmentTransactionのcommit問題FragmentTransactionにはcommit()とcommitAllowingStateLoss()の2つの提出方法があり、ソースコードで違いを見ることができます.
commit():
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready. 
A transaction can only be committed with this method prior to its containing activity saving its state. If the commit is attempted after that point, an exception will be thrown. This is because the state after the commit can be lost if the activity needs to be restored from its state. See commitAllowingStateLoss() for situations where it may be okay to lose the commit.

Returns:
Returns the identifier of this transaction's back stack entry, if addToBackStack(String) had been called. Otherwise, returns a negative number.

commitAllowingStateLoss():
Like commit but allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.

名前の通りcommit()メソッドを使用すると、Activity保存状態後にcommit動作が発生すると、例外が放出されることがわかります.
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
	at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
	at android.support.v4.app.FragmentManagerImpl.enqueueAction(Unknown Source)
	at android.support.v4.app.BackStackRecord.commitInternal(Unknown Source)
	at android.support.v4.app.BackStackRecord.commit(Unknown Source)

一方、commitAllowingStateLoss()メソッドでは、この呼び出しが許可されていますが、Activityがステータスに返信すると、以前のfragmentのコミットステータスが失われます.したがって、このようなcommitステータスの損失を許可する場合は、commitAllowingStateLoss()を使用できます.
参考記事:FragmentTransactionのcommitとcommitAllowingStateLossの違い
参照先:
Android Fragment君が知っておくべきことはすべて
Android解惑-なぜFragment.setArguments(Bundle bundle)でパラメータを渡すのか
Android Fragmentの真の完全解析(上)
Android Fragmentの真の完全解析(下)