Android Fragment的生命週期和返回棧

生命壹號的部落格發表於2014-09-20

上一章節中(Android讓Fragment載入到Activity中),我們對Fragment的生命週期進行了簡單介紹,這一章節將對生命週期和返回棧進行詳細介紹。

一、Fragment的生命週期初探:

因為Fragment必須嵌入在Acitivity中使用,所以Fragment的生命週期和它所在的Activity是密切相關的。

如果Activity是暫停狀態,其中所有的Fragment都是暫停狀態;如果Activity是stopped狀態,這個Activity中所有的Fragment都不能被啟動;如果Activity被銷燬,那麼它其中的所有Fragment都會被銷燬。

但是,當Activity在活動狀態,可以獨立控制Fragment的狀態,比如加上或者移除Fragment。

當這樣進行fragment transaction(轉換)的時候,可以把fragment放入Activity的back stack中,這樣使用者就可以進行返回操作。

使用Fragment時,需要繼承Fragment或者Fragment的子類(DialogFragment, ListFragment, PreferenceFragment, WebViewFragment),所以Fragment的程式碼看起來和Activity的類似。

每當建立一個Fragment時,首先新增以下三個回撥方法:

  • onCreate():系統在建立Fragment的時候呼叫這個方法,這裡應該初始化相關的元件,一些即便是被暫停或者被停止時依然需要保留的東西。
  • onCreateView():當第一次繪製Fragment的UI時系統呼叫這個方法,該方法將返回一個View,如果Fragment不提供UI也可以返回null。注意,如果繼承自ListFragment,onCreateView()預設的實現會返回一個ListView,所以不用自己實現。
  • onPause():當使用者離開Fragment時第一個呼叫這個方法,需要提交一些變化,因為使用者很可能不再返回來。

將Fragment載入到Activity當中有兩種方式:

  • 方式一:新增Fragment到Activity的佈局檔案當中
  • 方式二:在Activity的程式碼中動態新增Fragment(薦)

第一種方式雖然簡單但靈活性不夠。新增Fragment到Activity的佈局檔案當中,就等同於將Fragment及其檢視與activity的檢視繫結在一起,且在activity的生命週期過程中,無法切換fragment檢視。

第二種方式比較複雜,但也是唯一一種可以在執行時控制fragment的方式(載入、移除、替換)。

二、Fragment的生命週期詳解:

先來看一下官方文件的圖片吧:

我們再把Activity的生命週期和Fragment的生命週期對比一下,就清楚很多了:

我們還是在例項中來看一下Fragment的生命週期吧。

【例項】在MainActivity中載入一個Fragment:(完整版程式碼如下)

我們所建立的Fragment的佈局檔案fragment01.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:orientation="vertical" >
    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

MyFragment.java程式碼如下:(注意生命週期中每個方法的作用)

  1 package com.example.m01_fragmentlifecycle;
  2 
  3 import android.app.Activity;
  4 import android.app.Fragment;
  5 import android.os.Bundle;
  6 import android.util.Log;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 
 11 public class MyFragment extends Fragment {
 12     private final String TAG = "MyFragment";
 13     
 14     //獲得activity的傳遞的值
 15     @Override
 16     public void onAttach(Activity activity) {
 17         // TODO Auto-generated method stub
 18         super.onAttach(activity);
 19         Log.i(TAG, "--MyFragment->>onAttach");
 20     }
 21 
 22     //例項化成員變數
 23     @Override
 24     public void onCreate(Bundle savedInstanceState) {
 25         // TODO Auto-generated method stub
 26         super.onCreate(savedInstanceState);
 27         Log.i(TAG, "--MyFragment->>onCreate");
 28     }
 29 
 30     //給當前的fragment繪製UI佈局,可以使用執行緒更新UI
 31     @Override
 32     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 33             Bundle savedInstanceState) {
 34         Log.i(TAG, "--MyFragment->>onCreateView");
 35         View view = inflater.inflate(R.layout.fragment01, null);
 36         // TODO Auto-generated method stub
 37         return view;
 38     }
 39 
 40     //表示activity執行oncreate方法完成了的時候會呼叫此方法
 41     @Override
 42     public void onActivityCreated(Bundle savedInstanceState) {
 43         // TODO Auto-generated method stub
 44         super.onActivityCreated(savedInstanceState);
 45         Log.i(TAG, "--MyFragment->>onActivityCreated");
 46     }
 47 
 48     //和activity一致
 49     @Override
 50     public void onStart() {
 51         // TODO Auto-generated method stub
 52         super.onStart();
 53         Log.i(TAG, "--MyFragment->>onStart");
 54     }
 55 
 56     //和activity一致
 57     @Override
 58     public void onResume() {
 59         // TODO Auto-generated method stub
 60         super.onResume();
 61         Log.i(TAG, "--MyFragment->>onResume");
 62     }
 63 
 64     //和activity一致
 65     @Override
 66     public void onPause() {
 67         // TODO Auto-generated method stub
 68         super.onPause();
 69         Log.i(TAG, "--MyFragment->>onPause");
 70     }
 71 
 72     //和activity一致
 73     @Override
 74     public void onStop() {
 75         // TODO Auto-generated method stub
 76         super.onStop();
 77         Log.i(TAG, "--MyFragment->>onStop");
 78     }
 79 
 80     //表示fragment銷燬相關聯的UI佈局
 81     @Override
 82     public void onDestroyView() {
 83         // TODO Auto-generated method stub
 84         super.onDestroyView();
 85         Log.i(TAG, "--MyFragment->>onDestroyView");
 86     }
 87 
 88     //銷燬fragment物件
 89     @Override
 90     public void onDestroy() {
 91         // TODO Auto-generated method stub
 92         super.onDestroy();
 93         Log.i(TAG, "--MyFragment->>onDestroy");
 94     }
 95 
 96     //脫離activity
 97     @Override
 98     public void onDetach() {
 99         // TODO Auto-generated method stub
100         super.onDetach();
101         Log.i(TAG, "--MyFragment->>onDetach");
102     }
103 }

activity_main.xml的程式碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/line"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>

MainActivity.java的程式碼如下:

 1 package com.example.m01_fragmentlifecycle;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.app.FragmentManager;
 6 import android.app.FragmentTransaction;
 7 import android.util.Log;
 8 import android.view.Menu;
 9 
10 public class MainActivity extends Activity {
11     private final String TAG = "MainActivity";
12     private FragmentManager manager;
13     private FragmentTransaction transaction;
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         manager = getFragmentManager();
20         transaction = manager.beginTransaction();
21         MyFragment fragment = new MyFragment();
22         transaction.add(R.id.line, fragment);
23         transaction.commit();
24         Log.i(TAG, "--MainActivity->>onCreate");
25     }
26 
27     @Override
28     protected void onStart() {
29         // TODO Auto-generated method stub
30         super.onStart();
31         Log.i(TAG, "--MainActivity->>onStart");
32     }
33 
34     @Override
35     protected void onResume() {
36         // TODO Auto-generated method stub
37         super.onResume();
38         Log.i(TAG, "--MainActivity->>onResume");
39     }
40 
41     @Override
42     protected void onPause() {
43         // TODO Auto-generated method stub
44         super.onPause();
45         Log.i(TAG, "--MainActivity->>onPause");
46     }
47 
48     @Override
49     protected void onStop() {
50         // TODO Auto-generated method stub
51         super.onStop();
52         Log.i(TAG, "--MainActivity->>onStop");
53     }
54     
55     @Override
56     protected void onRestart() {
57         // TODO Auto-generated method stub
58         super.onRestart();
59         Log.i(TAG, "--MainActivity->>onRestart");
60     }
61     @Override
62     protected void onDestroy() {
63         // TODO Auto-generated method stub
64         super.onDestroy();
65         Log.i(TAG, "--MainActivity->>onDestroy");
66     }
67 
68     @Override
69     public boolean onCreateOptionsMenu(Menu menu) {
70         // Inflate the menu; this adds items to the action bar if it is present.
71         getMenuInflater().inflate(R.menu.main, menu);
72         return true;
73     }
74 }

可以看到,上面的程式碼在每個生命週期的方法裡都列印了日誌,然後我們來執行一下程式,可以看到列印日誌如下:

初次載入:(分成兩部分來看)

點選一下home鍵(或接入電話),列印日誌如下:

重新進入進入程式(或電話結束),列印日誌如下:

點選back鍵退出程式,列印日誌如下:

通過上面的日誌,我們能夠看出,Fragment和Activity的生命週期太相似了。只是有幾個Activity中沒有的新方法,需要重點介紹一下:

  • onAttach方法:Fragment和Activity建立關聯的時候呼叫(獲得activity的傳遞的值)
  • onCreateView方法:為Fragment建立檢視(載入佈局)時呼叫(給當前的fragment繪製UI佈局,可以使用執行緒更新UI)
  • onActivityCreated方法:當Activity中的onCreate方法執行完後呼叫(表示activity執行oncreate方法完成了的時候會呼叫此方法)
  • onDestroyView方法:Fragment中的佈局被移除時呼叫(表示fragment銷燬相關聯的UI佈局)
  • onDetach方法:Fragment和Activity解除關聯的時候呼叫(脫離activity)

三、Fragment返回棧的管理:

將Fragment新增到返回棧中:

假設現在我們有兩個Fragment:Fragment01和Fragment02,我們現在從Fragment01的介面跳到Fragment02,然後按Back鍵,發現程式是直接退出了,而不是返回到Fragment01。如果現在想實現以下功能:從Fragment01的介面跳到Fragment02,然後按Back鍵,會返回到Fragment01。這個功能該怎麼實現呢?這其實就利用到了返回棧的知識。

其實很簡單,FragmentTransaction中提供了一個addToBackStack()方法,可以將一個事務新增到返回棧中。

我們先回顧一下之前動態載入Fragment的程式碼,然後在此基礎之上,增加一行程式碼就可以將Fragment新增到返回棧中:(即第07行程式碼)

//步驟一:新增一個FragmentTransaction的例項
FragmentManager fragmentManager =getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
//步驟二:用add()方法加上Fragment的物件
RightFragment rightFragment = new RightFragment();
transaction.add(R.id.right, rightFragment);
transaction.addToBackStack(null); //步驟三:呼叫commit()方法使得FragmentTransaction例項的改變生效
transaction.commit();

第07行程式碼:我們在事務提交之前呼叫了FragmentTransaction的addToBackStack()方法,它可以接受一個名字用於描述返回棧的狀態,,一般傳入null即可。

【例項】現在通過程式碼來實現以下介面(下面的圖片為程式執行時載入的首頁),並且把每一個Fragment都加入到返回棧當中去,然後觀察其生命週期的變化。完整程式碼如下:

首先新建工程檔案m01_Fragment04,然後開始我們的程式碼之旅:

我們先把右側的四個Fragment建起來吧:

Fragment01.java主要部分的程式碼如下:

 1 package com.example.m01_fragment04;
 2 
 3 import android.app.Fragment;
 4 import android.os.Bundle;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 
 9 public class Fragment01 extends Fragment {
10     
11     @Override
12     public void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14     }
15     
16     @Override
17     public View onCreateView(LayoutInflater inflater, ViewGroup container,
18             Bundle savedInstanceState) {
19         View view = inflater.inflate(R.layout.f1, null);
20         return view;
21     }
22     @Override
23     public void onPause() {
24         super.onPause();
25     }
26 }

為避免囉嗦,這裡就不把Fragment01生命週期中的其他函式羅列出來了,我們只要知道在實際程式碼中這些函式都是加了的。

Fragment01的佈局檔案f1.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:orientation="vertical" >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="載入圖片" />
</LinearLayout>

然後依次新建出Fragment02、Fragment03、Fragment04的java程式碼和佈局檔案。

MainActivity的佈局檔案activity_main.xml程式碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/left"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:background="#CCCCCC"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="載入圖片" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="海報管理" />

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="照片管理" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="新聞管理" />        

    </LinearLayout>

    <LinearLayout
        android:id="@+id/right"       
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>

其中,第一個LinearLayout表示左側的按鈕,第二個LinearLayout留給右邊的Fragment。

MainActivity.java的程式碼如下:

 1 package com.example.m01_fragment04;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.app.FragmentManager;
 6 import android.app.FragmentTransaction;
 7 import android.view.Menu;
 8 import android.view.View;
 9 import android.view.View.OnClickListener;
10 import android.widget.Button;
11 
12 public class MainActivity extends Activity  implements OnClickListener{
13 
14     private FragmentManager manager;
15     private FragmentTransaction transaction;
16     private Button button1,button2,button3,button4;
17     
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22         
23     manager = getFragmentManager();
24         button1 = (Button)this.findViewById(R.id.button1);
25         button1.setOnClickListener(this);
26         button2 = (Button)this.findViewById(R.id.button2);
27         button2.setOnClickListener(this);
28         button3 = (Button)this.findViewById(R.id.button3);
29         button3.setOnClickListener(this);
30         button4 = (Button)this.findViewById(R.id.button4);
31         button4.setOnClickListener(this);
32         
33     }
34 
35     @Override
36     public boolean onCreateOptionsMenu(Menu menu) {
37         // Inflate the menu; this adds items to the action bar if it is present.
38         getMenuInflater().inflate(R.menu.main, menu);
39         return true;
40     }
41 
42     //通過點選不同的按鈕,跳轉到不同的Fragment
43     @Override
44     public void onClick(View v) {
45         // TODO Auto-generated method stub
46 transaction = manager.beginTransaction(); 47         switch (v.getId()) {
48         case R.id.button1:
49             Fragment01 fragment01 = new Fragment01();
50             transaction.replace(R.id.right, fragment01, "fragment01");
51             transaction.addToBackStack("fragment01");// 新增到Activity管理的回退棧中。
52             break;
53 
54         case R.id.button2:
55             Fragment02 fragment02 = new Fragment02();
56             transaction.replace(R.id.right, fragment02, "fragment02");
57             transaction.addToBackStack("fragment02");// 新增到Activity管理的回退棧中。
58             break;
59 
60         case R.id.button3:
61             Fragment03 fragment03 = new Fragment03();
62             transaction.replace(R.id.right, fragment03, "fragment03");
63             transaction.addToBackStack("fragment03");// 新增到Activity管理的回退棧中。
64             break;
65 
66         case R.id.button4:
67             Fragment04 fragment04 = new Fragment04();
68             transaction.replace(R.id.right, fragment04, "fragment04");
69             transaction.addToBackStack("fragment04");// 新增到Activity管理的回退棧中。
70             break;
71         }
72  transaction.commit();      
73     }
74     
75 }

上當程式碼中,通過點選不同的按鈕,就能跳到對應的Fragment,而這四個Fragment都已經加入到了返回棧當中。執行程式之後,也是這樣的。

注意第46行和第72行,transaction = manager.beginTransaction()意味著開始,transaction.commit()意味著結束。

我們就其中的fragment01和fragment02來討論一下他們的生命週期的變化:

執行程式後,介面如下,沒有任何fragment被載入:

點選左側第一個按鈕,載入fragment01:

點選左側第二個按鈕,載入fragment02(此時fragment01被替換,並被壓到了棧當中):

注:如果fragment01在替換的時候沒有被壓到棧中,那就會被銷燬,在執行完onDestroyView()方法後,會繼續執行onDestroy()和onDetach()方法。

按Back鍵,fragment01重新返回到螢幕:(fragment02被銷燬)

再按Back鍵,fragment01被銷燬:

注:Fragment的返回棧由Activity管理;而Activity的返回棧由系統管理。

相關文章