引言
在Android3.0開始,Android引入了Fragment。當初最主要的目的是為了給大屏幕手持設備提供更加靈活和動態的UI設計。隨著應用功能越來越多,界面越來越復雜,我們會利用Fragment對Activity的界面進行模塊化編程。比如一個Activity界面內有多個請求,每個請求得到的結果展示不同的UI片段,那么我們就可以將Activity利用Fragment來完成UI模塊化。目前在我目前的項目中,Fragment的地位已經舉足輕重了。當然我寫篇文章,并不是簡單的介紹Fragment的用法,而是結合我在項目中使用Fragment所遇到的問題進行說明。
Fragment的生命周期
Fragment與Activity一樣也是具有生命周期的,它的生命周期與Activity息息相關。下面看下Fragment生命周期流程圖(摘自Android官方開發文檔)
大體上Fragment的生命周期與Activity類似,但是也有自己獨有的生命周期方法。每個生命周期的意義大家可以看看Android Fragment,這里就不做詳解了。
Fragment與Activity之間生命周期的關系
每個Fragment實例存在于單獨的一個Activity實例中,因此Fragment必須以某種方式與Activity的生命周期合作。Fragment不僅與Activity的生命周期合作,而且他們之間有“密切”聯系。
上圖是Fragment創建到激活過程與Activity相關的生命周期圖,為了驗證該過程,我們寫一個demo程序來進行驗證。
打印Activity生命周期方法相關方法代碼:
public class MainActivity extends FragmentActivity {
private static final String TAG = "KissonChan";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Activity onCreate");
setContentView(R.layout.activity_main);
TestFragment fragment = (TestFragment)getFragmentManager().findFragmentById(R.id.test);
}
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
Log.d(TAG, "Activity onAttachFragment");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "Activity onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "Activity onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "Activity onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "Activity onStop");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "Activity onSaveInstanceState");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "Activity onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "Activity onRestart");
}
}
打印Fragment生命周期方法相關方法代碼:
public class TestFragment extends Fragment {
private static final String TAG = "KissonChan";
private View mRootView;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.e(TAG, "Fragment onAttach");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "Fragment onCreate");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.e(TAG, "Fragment onCreateView");
mRootView = inflater.inflate(R.layout.fragment_test, container, false);
return mRootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(TAG, "Fragment onActivityCreated");
}
@Override
public void onStart() {
super.onStart();
Log.e(TAG, "Fragment onStart");
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG, "Fragment onResume");
}
@Override
public void onPause() {
super.onPause();
Log.e(TAG, "Fragment onPause");
}
@Override
public void onStop() {
super.onStop();
Log.e(TAG, "Fragment onStop");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e(TAG, "Fragment onSaveInstanceState");
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e(TAG, "Fragment onDestroyView");
}
@Override
public void onDetach() {
super.onDetach();
Log.e(TAG, "Fragment onDetach");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "Fragment onDestroy");
}
}
程序的執行結果是:
05-12 11:38:42.901 32590-32590/? D/KissonChan: Activity onCreate
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onAttach
05-12 11:38:42.911 32590-32590/? D/KissonChan: Activity onAttachFragment
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onCreate
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onCreateView
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onActivityCreated
05-12 11:38:42.911 32590-32590/? D/KissonChan: Activity onStart
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onStart
05-12 11:38:42.911 32590-32590/? D/KissonChan: Activity onResume
05-12 11:38:42.911 32590-32590/? E/KissonChan: Fragment onResume
在Fragment從激活態到銷毀也有類似的生命周期過程,不過與創建過程是相反。當Fragment的生命周期方法調用之后才調用對應的Activity生命周期方法,如下圖。
注意:如果正常結束Activity,onSaveInstanceState方法將不被調用。如果用戶按下home鍵或者電源鍵等相關操作,將Activity置于后臺,那么onSaveInstanceState將被調用。
正常結束Activity程序運行結果是:
05-12 14:10:44.131 8266-8266/? E/KissonChan: Fragment onPause
05-12 14:10:44.131 8266-8266/? D/KissonChan: Activity onPause
05-12 14:10:44.381 8266-8266/? E/KissonChan: Fragment onStop
05-12 14:10:44.381 8266-8266/? D/KissonChan: Activity onStop
05-12 14:10:44.381 8266-8266/? E/KissonChan: Fragment onDestroyView
05-12 14:10:44.381 8266-8266/? E/KissonChan: Fragment onDestroy
05-12 14:10:44.381 8266-8266/? E/KissonChan: Fragment onDetach
05-12 14:10:44.381 8266-8266/? D/KissonChan: Activity onDestroy
按下home鍵程序運行結果是:
05-12 14:12:01.781 8266-8266/? E/KissonChan: Fragment onPause
05-12 14:12:01.781 8266-8266/? D/KissonChan: Activity onPause
05-12 14:12:02.041 8266-8266/? E/KissonChan: Fragment onSaveInstanceState
05-12 14:12:02.041 8266-8266/? D/KissonChan: Activity onSaveInstanceState
05-12 14:12:02.041 8266-8266/? E/KissonChan: Fragment onStop
05-12 14:12:02.041 8266-8266/? D/KissonChan: Activity onStop
如果對Fragment和Activity的生命周期有深入了解想法的小伙伴,可以自己閱讀相關源碼。我這里僅僅是拋磚引玉,做個簡單介紹,為后續介紹工作過程中碰到的問題做一個鋪墊。
Fragment的兩種聲明方式
Android提供了兩種聲明Fragment的方式,這兩種方式上使用會有不同。同時也有一些需要注意的地方。
在Activity布局中添加Fragment
這種方式數據比較常用,在Activity布局中聲明fragment。
<fragment
android:id="@+id/test"
android:name="com.dighammer.kisson.testfragment.TestFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
隨后,在Activity通過如下方法初始化Fragment。
TestFragment fragment = (TestFragment)getFragmentManager().findFragmentById(R.id.test);
在代碼中將Fragment添加到已有ViewGroup中
這種方式我自己用的比較多,方法如下:
FragmentTransaction transaction = getFragmentManager().beginTransaction();
TestFragment fragment = new TestFragment();
transaction.add(R.id.container,fragment);
//transaction.replace(R.id.container,fragment);
transaction.commit();
這里咱們需要注意FragmentTransaction中add和replace方法的區別。
- add方法是把Fragment添加到ViewGroup中,通過該方法可以添加多個Fragment。
- replace方法會把ViewGroup中的原有Fragment先remove掉然后再add新Fragment。
使用add方法可以避免再次創建Fragment實例,一般情況是建議使用add方法,并配合FragmentTransaction的show和hide方法來使用。
那些年,我踩過的Fragment的坑
第一個坑
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1331)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1349)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:735)
at android.app.BackStackRecord.commit(BackStackRecord.java:711)
at com.dighammer.kisson.testfragment.MainActivity$1.run(MainActivity.java:30)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5273)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
該類型的Crash開始的時候在我們的后臺崩潰日志出現很多次。
這里我寫了一段代碼,運行后可以百分比復現。
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
TestFragment fragment = new TestFragment();
transaction.add(R.id.container, fragment);
transaction.commit();
}
}, 5000);
}
當程序界面顯示出來后,按home鍵,過接近5秒后,就會出現該奔潰日志。
原因是FragmentTransaction中的commit方法必須在onSaveInstanceState之前調用。
那么如何解決了?在FragmentTransaction中提供了commitAllowingStateLoss方法,通過調用該方法就不用關心Activity的狀態是否保存。
第二個坑
情景再現,先看代碼。
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
TestFragment fragment = new TestFragment();
transaction.add(R.id.container, fragment);
transaction.commit();
fragment.setText("kisson chan");
}
}
運行后,會報如下錯誤。
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.dighammer.kisson.testfragment.TestFragment.setText(TestFragment.java:53)
at com.dighammer.kisson.testfragment.MainActivity.onCreate(MainActivity.java:18)
at android.app.Activity.performCreate(Activity.java:6041)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1109)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2283)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2392)
at android.app.ActivityThread.access$800(ActivityThread.java:154)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1308)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5273)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
這里是空指針異常,TestFragment中的TextView還未被初始化。但是,如果我們通過Activity布局中聲明Fragment則不會報錯,代碼如下。
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestFragment fragment = (TestFragment)getFragmentManager().findFragmentById(R.id.test);
fragment.setText("kisson");
}
}
原因其實很簡單,通過Activity布局中聲明Fragment,Fragment的初始化以及其布局中控件的初始化都發生在Activity的setContentView方法中。所以在setContentView方法調用之后,我們可以直接操作Fragment中的控件。
但是通過代碼將Fragment添加到ViewGroup的方式,并不能在Activity的onCreate方法中去操作Fragment中的控件,只能等到onCreate完成之后調用。
如果必須要在Activity的onCreate方法中操作Fragment中的控件,這里我提供一個參考辦法。
public class TestFragment extends Fragment{
private View mRootView;
private TextView mTextView;
private String mTextStr;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_test,container,false);
return mRootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mTextView = (TextView)mRootView.findViewById(R.id.text);
if (!TextUtils.isEmpty(mTextStr)){
mTextView.setText(mTextStr);
}
}
/**
* 如果mTextView為空,即mTextView還未被初始化,那么將text賦值給mTextStr,等到onActivityCreated調用后再setText
* 如果mTextView不為空,則直接調用TextView的setText方法進行賦值
* */
public void setText(String text){
if (mTextView == null){
mTextStr = text;
}
else {
mTextView.setText(text);
}
}
}
第三個坑
情景再現,先看代碼。
public class TestFragment extends Fragment{
private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_test,container,false);
return mRootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
((MainActivity) (getActivity())).print();
}
},5000);
}
}
繼續上代碼
public class MainActivity extends FragmentActivity {
private static final String TAG = "KissonChan";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
TestFragment fragment = new TestFragment();
transaction.add(R.id.container,fragment);
transaction.commit();
}
public void print() {
Log.d(TAG, "KissonChan");
}
}
當程序Activity顯示出來后,接著按下返回鍵結束Activity,靜待五秒后,出現以下崩潰日志。
Process: com.dighammer.kisson.testfragment, PID: 15870
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.dighammer.kisson.testfragment.MainActivity.print()' on a null object reference
at com.dighammer.kisson.testfragment.TestFragment$1.run(TestFragment.java:41)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5273)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
因為MainActivity已經被銷毀,五秒鐘后在TestFragment實例中會調用MainActivity的print方法,所以拋出空指針異常。
這里很奇怪,Activity已經被銷毀了,為什么Fragment還能繼續茍活。
這是因為Fragment并不會隨著Activity的回收而被系統回收,所有被創建的Fragment都會保存在一個Bundle里面。所以會導致以上崩潰。所以在一些延時操作或者線程操作中調用getActivity最好做下為空判斷!
結尾
Fragment作為模塊化UI編程類,極大方便了我們代碼設計。但是同時我們也要深刻理解其生命周期及其意義。有時間大家可以看看源碼,發現其中的奧秘!
文中圖片均摘自我個人CSDN,并無侵權!