Fragment實際開發中的總結(一)

在實際項目開發使用Fragment的時候,也碰到一些異常和存在的問題,下面做下簡單的總結筆記,後面還會不定時補充更新。
1.關於Fragment的生命週期的幾點認識
  •  Fragment的完整生命週期開始於綁定到它的父Activity,結束於從父Activity上分離。通過分別調用onAttach和onDetach來表示這些事件。
  • 在Fragment/Activity 被暫停之後,由於任何其他處理程序都可以被調用,可能就會出現它的父Activity進程沒有完成它的全部生命週期被終止從而導致onDetach不會被調用的情況。
  • onAttach事件在Fragment的UI被創建之前,以及Fragment自身或它的父Activity完成它們的初始化之前被觸發。通常情況下,onAttach事件用來獲取一個Fragment的父Activity的引用,爲進一步的初始化工作準備。
  • 對Activity的進程來說,在沒有響應的onDestroy方法被調用而被終止的情況很常見,所以Fragment不能依賴觸發onDestory方法來銷燬它。
  • 如果Fragment需要和它的父Activity的UI交互,需要一直等到onActivityCreated事件被觸發。該事件被觸發意味着Frament所在的Activity已經完成了對初始化並且它的UI也已經完全構建好了。
2.Fragment開發中遇到的問題
  • Fragment getActivity爲空的情況解決辦法
我們模仿QQ首頁的實現Demo來模擬解決getActivity爲空的問題,實現界面如下:
public class SwitchActivity extends FragmentActivity {

	private Button btn_message,btn_call;
	private CallFragment callFragment;
    private MessageFragment messageFragment;
	public static final int MESSAGE_FRAGMENT_TYPE = 1;
	public static final int CALL_FRAGMENT_TYPE = 2;
	public int currentFragmentType = -1;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_switch);
		
		btn_message = (Button)findViewById(R.id.btn_message);
		btn_call = (Button)findViewById(R.id.btn_call);
		btn_message.setOnClickListener(onClicker);
		btn_call.setOnClickListener(onClicker);
		FragmentManager fragmentManager = getSupportFragmentManager();
		if (savedInstanceState != null) { 
			//當savedInstanceState不爲空的時候,說明當前的Activity是被回收後重建,
			//我們重新建立Fragment和Activity的聯繫。
            int type = savedInstanceState.getInt("currentFragmentType");
            messageFragment = (MessageFragment)fragmentManager.findFragmentByTag("message");
            callFragment = (CallFragment)fragmentManager.findFragmentByTag("call");
            if(type > 0)
            	loadFragment(type);
		} else {
			FragmentTransaction transaction = fragmentManager
					.beginTransaction();
			Fragment mainFragment = fragmentManager.findFragmentByTag("message");
			if (mainFragment != null) {
				transaction.replace(R.id.fl_content, mainFragment);
				transaction.commit();
			} else {
				loadFragment(MESSAGE_FRAGMENT_TYPE);
			}
		}
	}
	/**
	 * 當某種原因Activity被銷燬回收掉(如:App進入後臺運行或者Activity壓入任務棧內存不足時候被回收),
	 * onSaveIntanceState方法保存當前的狀態。當用戶操作當前Activity要返回前臺或者
	 * 我們的Activity會被重建(如:橫屏操作),此時Activity與Fragment間失去聯繫,
	 * 我們這個時候調用getActivity()會返回爲null
	 */
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putInt("lastFragmentTag", currentFragmentType);
	}
	
	private void switchFragment(int type) {
		switch (type) {
		case MESSAGE_FRAGMENT_TYPE:
			loadFragment(MESSAGE_FRAGMENT_TYPE);
			break;
		case CALL_FRAGMENT_TYPE:
			loadFragment(CALL_FRAGMENT_TYPE);
			break;
		}
	}

	private void loadFragment(int type) {
		FragmentManager fragmentManager = getSupportFragmentManager();
		FragmentTransaction transaction = fragmentManager.beginTransaction();
		if (type == CALL_FRAGMENT_TYPE) {
			if (callFragment == null) {
				callFragment = new CallFragment();
				transaction.add(R.id.fl_content, callFragment, "zhishi");
			} else {
				transaction.show(callFragment);
			}
			if (messageFragment != null) {
				transaction.hide(messageFragment);
			}
			currentFragmentType = MESSAGE_FRAGMENT_TYPE;
		} else {
			if (messageFragment == null) {
				messageFragment = new MessageFragment();
				transaction.add(R.id.fl_content, messageFragment, "wenda");
			} else {
				transaction.show(messageFragment);
			}
			if (callFragment != null) {
				transaction.hide(callFragment);
			}
			currentFragmentType = CALL_FRAGMENT_TYPE;
		}
		transaction.commitAllowingStateLoss();
	}
	
	private OnClickListener onClicker = new OnClickListener() {
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.btn_message:
				btn_message.setTextColor(Color.parseColor("#df3031"));
				btn_call.setTextColor(Color.WHITE);
				btn_message.setBackgroundResource(R.drawable.baike_btn_pink_left_f_96);
				btn_call.setBackgroundResource(R.drawable.baike_btn_trans_right_f_96);
				switchFragment(MESSAGE_FRAGMENT_TYPE);
				break;
			case R.id.btn_call:
				btn_message.setTextColor(Color.WHITE);
				btn_call.setTextColor(Color.parseColor("#df3031"));
				btn_message.setBackgroundResource(R.drawable.baike_btn_trans_left_f_96);
				btn_call.setBackgroundResource(R.drawable.baike_btn_pink_right_f_96);
				switchFragment(CALL_FRAGMENT_TYPE);
				break;
			}
		}
	};
}
從以上代碼我們知道,Activity頁面實現對MessageFragment和CallFragment的切換功能。我們的頁面可能在某種原因下系統被銷燬回收掉(如:App進入後臺運行或者Activity壓入任務棧內存不足時候被回收), 在Activity被回收的時候我們可以調用onSaveIntanceState方法保存當前的某些狀態。當用戶操作當前Activity要返回前臺或者我們的Activity會被重建(如:橫屏操作),此時Activity與Fragment間失去聯繫, 我們這個時候調用getActivity()會返回爲null。針對這種情況,我們可以的解決方案如下:
當Activity被重新建立的時候,onCreate的時候,我們判斷savedInstanceState參數不爲空的時候,即知道我們的Activity是重建的,此時我們的Fragment可能還存在,我們就沒有必要重建Fragment,我們只需要通過調用FragmentManager的findFragmentByTag方法重新連接Fragment和Activity. 
針對以上的解決方案,我們可以在Android系統的實現代碼裏找到類似的實現。我們看到從Fragment誕生開始,很多App開始用ViewPager+PagerAdapter的子類(即:FragmentPagerAdapter和FragmentStatePagerAdapter)來製作首頁。在ViewPager + Fragment的首頁實現組合情況下,我們不用擔心Fragment裏調用getActivity()爲空的問題。我們看FragmentPagerAdapter的源碼,我們知道FragmentPagerAdapter和FragmentStatePagerAdapter都是繼承抽象基類PagerAdapter,我們重點看FragmentPagerAdapter的源碼instantiateItem方法的實現:
@Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        final long itemId = getItemId(position);
        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            FragmentCompat.setMenuVisibility(fragment, false);
            FragmentCompat.setUserVisibleHint(fragment, false);
        }
        return fragment;
    }
我們從Android官方文檔可以知道,instantiateItem方法的作用:Create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).我們知道instantiateItem會給我們制定位置返回一個頁面。我們看到以下兩句話:
// Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
如果我們的Fragment存在的話,我們直接關聯fragment和Activity,如果不存在的話,我們新建Fragment.
  • Fragment Tansactions 和Activity的狀態丟失的問題
我們的代碼出現過如下異常:
java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
這種異常的出現是由於,在Activity的狀態保存之後,嘗試去提交一個FragmentTransaction。這種現象被稱爲活動狀態丟失(Activity State Loss)。我在實際開發中,異常小概率出現的情況發生在我們切換消息和電話按鈕的時候,拋出以上的異常。通過後來查找問題,發現原因由於onSaveInstanceState()方法調用後會調用FragmentTransaction的commit方法。這個transaction將不會被記住,因爲它沒有在第一時間記錄爲這個Activity的狀態的一部分。這個transaction將會丟失,可能導致UI狀態不一致。此處,Android系統檢測到不一致會拋出一個IllegalStateException異常。最簡單的解決辦法就是在除onCreate方法外的週期,儘量去忽視狀態一致性的檢查,我們將commit方法改爲commitAllowingStateLoss。更復雜的狀態的丟失解決辦法參考這篇文字http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html。
本文總結會繼續不定時更新,有錯誤的地方還望園友指正,附上Demo

轉載請註明出處:http://blog.csdn.net/johnnyz1234/article/details/45919907





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章