在 Android 開發中,Fragment 是構建靈活界面的核心組件 —— 它既能像 “迷你 Activity” 一樣包含布局和邏輯,又能靈活地嵌入到不同 Activity 中復用。無論是平板的多面板布局,還是手機的單頁切換,Fragment 都能讓界面適配更高效。但 Fragment 的生命周期、與 Activity 的交互、回退棧管理等知識點也讓很多開發者頭疼:“為什么 Fragment 重疊了?”“getActivity () 為什么會返回 null?”“如何正確處理 Fragment 的跳轉?”
本文將從 Fragment 的基礎概念出發,系統講解其生命周期、創建方式、與 Activity 的通信,再到 ViewPager2 結合 Fragment、懶加載等高級用法,最后總結常見問題及解決方案,幫你徹底掌握 Fragment 的使用精髓。
一、Fragment 核心概念:為什么需要 Fragment?
1.1 Fragment 的定義與核心價值
Fragment(碎片)是一種可以嵌入到 Activity 中的 UI 片段,它擁有自己的布局、生命周期和事件處理邏輯。其核心價值在于:
- 界面復用:一個 Fragment 可在多個 Activity 中使用(如 “用戶信息 Fragment” 同時嵌入 “個人中心” 和 “設置” 頁面);
- 適配不同屏幕:在平板上顯示多 Fragment(如左側列表 + 右側詳情),在手機上顯示單個 Fragment(點擊列表后跳轉詳情);
- 模塊化開發:將復雜界面拆分為多個 Fragment,每個 Fragment 負責一部分功能(如電商詳情頁拆分為 “商品信息”“評價”“推薦” 三個 Fragment),便于團隊協作。
形象比喻:如果把 Activity 比作 “房間”,Fragment 就是 “家具”—— 房間可以擺放不同家具(Activity 嵌入多個 Fragment),同一家具也可以放到不同房間(Fragment 復用)。
1.2 Fragment 與 Activity 的關系
Fragment 不能獨立存在,必須依賴于 Activity—— 它的生命周期受 Activity 影響(如 Activity 銷毀時,Fragment 也會銷毀),但又有自己的獨立生命周期。
- 所屬關系:一個 Activity 可以包含多個 Fragment,一個 Fragment 只能屬于一個 Activity;
- 通信方式:Fragment 通過接口或 ViewModel 與 Activity 通信,Activity 可直接調用 Fragment 的方法;
- 布局容器:Fragment 需通過 Activity 的布局容器(如 FrameLayout)顯示,或通過代碼動態添加。
二、Fragment 生命周期:比 Activity 更復雜的狀態管理
Fragment 的生命周期比 Activity 多了幾個關鍵方法(如與 Activity 關聯、視圖創建相關),理解這些方法是正確使用 Fragment 的基礎。
2.1 完整生命周期方法及觸發時機
生命周期方法 | 觸發時機 | 核心職責 | 對應 Activity 方法 |
onAttach() | Fragment 與 Activity 關聯時調用 | 獲取 Activity 引用,初始化與 Activity 的通信 | - |
onCreate() | Fragment 創建時調用(早于視圖創建) | 初始化非視圖數據(如從 Arguments 取參數) | onCreate() |
onCreateView() | Fragment 創建視圖時調用 | 加載布局(inflate 布局文件),初始化控件 | onCreateView() |
onViewCreated() | 視圖創建完成后調用 | 初始化視圖邏輯(如設置點擊事件、加載數據) | - |
onActivityCreated() | 宿主 Activity 的 onCreate () 完成后調用 | 可安全使用 Activity 的資源(如 ActionBar) | - |
onStart() | Fragment 可見時調用 | 啟動動畫、注冊廣播等 | onStart() |
onResume() | Fragment 可交互時調用 | 開始監聽用戶輸入、啟動傳感器等 | onResume() |
onPause() | Fragment 失去焦點時調用 | 暫停動畫、保存臨時數據 | onPause() |
onStop() | Fragment 不可見時調用 | 停止耗時操作、取消廣播注冊 | onStop() |
onDestroyView() | Fragment 視圖銷毀時調用 | 清理視圖資源(如解綁 View 監聽、回收 Bitmap) | - |
onDestroy() | Fragment 銷毀時調用 | 釋放非視圖資源(如取消網絡請求) | onDestroy() |
onDetach() | Fragment 與 Activity 解除關聯時調用 | 移除與 Activity 的引用(避免內存泄漏) | - |
2.2 生命周期執行流程(首次加載)
當 Fragment 被添加到 Activity 時,完整流程為:
onAttach() → onCreate() → onCreateView() → onViewCreated() → onActivityCreated() → onStart() → onResume()
關鍵節點:
- onCreateView()是視圖創建的核心(必須返回布局 View);
- onViewCreated()中才能安全操作 View(此時 View 已創建);
- onActivityCreated()確保 Activity 已初始化完成(可安全調用getActivity())。
2.3 生命周期與 Activity 的聯動
Fragment 的生命周期受 Activity 影響,例如:
- Activity 執行onPause() → 所有 Fragment 執行onPause();
- Activity 旋轉銷毀(onDestroy()) → Fragment 執行onDestroyView()→onDestroy()→onDetach(),重建時重新執行完整生命周期;
- 若 Fragment 被移除(remove()) → 執行onDestroyView()→onDestroy()→onDetach()。
三、Fragment 基礎使用:創建、添加與移除
使用 Fragment 的核心步驟是 “創建 Fragment→添加到 Activity→管理其生命周期”,根據添加方式可分為 “靜態添加” 和 “動態添加”。
3.1 靜態添加:布局中直接聲明(簡單場景)
靜態添加是在 Activity 的布局文件中直接聲明 Fragment,適合固定不變的界面(如始終顯示的標題欄 Fragment)。
步驟 1:創建 Fragment 子類
public class StaticFragment extends Fragment {private static final String TAG = "StaticFragment";// 必須有默認構造方法(避免重建時崩潰)public StaticFragment() {}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {// 加載布局(第三個參數必須為false,避免重復添加到container)return inflater.inflate(R.layout.fragment_static, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 初始化控件(如設置文本)view.findViewById(R.id.btn_static).setOnClickListener(v -> Toast.makeText(getContext(), "靜態Fragment點擊", Toast.LENGTH_SHORT).show());}
}
對應的布局fragment_static.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#f0f0f0"android:padding="16dp"><Buttonandroid:id="@+id/btn_static"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="靜態Fragment按鈕"/>
</LinearLayout>
步驟 2:在 Activity 布局中聲明 Fragment
在 Activity 的布局文件(如activity_main.xml)中添加<fragment>標簽:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><!-- 靜態添加Fragment --><fragmentandroid:id="@+id/fragment_static"android:name="com.example.fragmentdemo.StaticFragment"android:layout_width="match_parent"android:layout_height="wrap_content"/><!-- 其他布局 --><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Activity中的內容"/>
</LinearLayout>
注意:
- android:name必須指定 Fragment 的完整類名;
- 必須設置android:id(否則 Fragment 無法被找到);
- 靜態添加的 Fragment 無法在運行時移除或替換(靈活性低)。
3.2 動態添加:代碼中控制(推薦方式)
動態添加是通過代碼將 Fragment 添加到 Activity 的容器中,支持運行時添加、移除、替換,是開發中最常用的方式。
步驟 1:創建可復用的 Fragment
public class DynamicFragment extends Fragment {private static final String ARG_TITLE = "title"; // 傳遞參數的keyprivate String mTitle; // 接收的參數// 提供工廠方法創建Fragment(推薦,避免直接new)public static DynamicFragment newInstance(String title) {DynamicFragment fragment = new DynamicFragment();// 通過Bundle傳遞參數Bundle args = new Bundle();args.putString(ARG_TITLE, title);fragment.setArguments(args);return fragment;}@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 從Arguments獲取參數(避免在構造方法中傳參,防止重建丟失)if (getArguments() != null) {mTitle = getArguments().getString(ARG_TITLE);}}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_dynamic, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 設置標題TextView tvTitle = view.findViewById(R.id.tv_title);tvTitle.setText(mTitle);// 添加點擊事件(示例:點擊替換為其他Fragment)view.findViewById(R.id.btn_replace).setOnClickListener(v -> {if (getActivity() instanceof MainActivity) {((MainActivity) getActivity()).replaceFragment(DynamicFragment.newInstance("新的Fragment"));}});}
}
布局fragment_dynamic.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="18sp"/><Buttonandroid:id="@+id/btn_replace"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="替換Fragment"/>
</LinearLayout>
步驟 2:在 Activity 中添加容器與管理代碼
Activity 布局(含 Fragment 容器):
<!-- 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"><!-- Fragment容器(必須有id) --><FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/><!-- 控制按鈕 --><Buttonandroid:id="@+id/btn_add"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="添加Fragment"/>
</LinearLayout>
Activity 代碼(管理 Fragment):
public class MainActivity extends AppCompatActivity {private FragmentManager mFragmentManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取Fragment管理器(AndroidX使用getSupportFragmentManager())mFragmentManager = getSupportFragmentManager();// 初始添加Fragment(避免旋轉屏幕重復添加)if (savedInstanceState == null) {addFragment(DynamicFragment.newInstance("初始Fragment"));}// 添加按鈕點擊事件findViewById(R.id.btn_add).setOnClickListener(v -> addFragment(DynamicFragment.newInstance("新添加的Fragment")));}// 添加Fragmentpublic void addFragment(Fragment fragment) {// 開啟事務FragmentTransaction transaction = mFragmentManager.beginTransaction();// 添加到容器(container是FrameLayout的id)transaction.add(R.id.container, fragment);// 添加到回退棧(按返回鍵可返回上一個Fragment)transaction.addToBackStack(null);// 提交事務transaction.commit();}// 替換Fragment(移除現有Fragment,添加新的)public void replaceFragment(Fragment fragment) {FragmentTransaction transaction = mFragmentManager.beginTransaction();// 替換容器中的Fragment(會移除現有所有Fragment)transaction.replace(R.id.container, fragment);transaction.addToBackStack(null);transaction.commit();}// 移除Fragmentpublic void removeFragment(Fragment fragment) {FragmentTransaction transaction = mFragmentManager.beginTransaction();transaction.remove(fragment);transaction.commit();}
}
核心 API 解析
- FragmentManager:Fragment 的管理器,負責添加、移除、查找 Fragment;
- FragmentTransaction:事務,用于執行 Fragment 的一系列操作(添加、替換等),需調用commit()生效;
- addToBackStack():將事務添加到回退棧,按返回鍵時會恢復到上一個狀態(如添加 A→添加 B,按返回鍵回到 A);
- newInstance():推薦的 Fragment 創建方式,通過 Bundle 傳遞參數(避免直接 new,防止重建時參數丟失)。
四、Fragment 與 Activity 的交互:數據傳遞與通信
Fragment 與 Activity 的交互是開發中的高頻需求(如 Fragment 點擊按鈕通知 Activity 更新標題,Activity 傳遞數據給 Fragment 顯示)。常用的通信方式有三種:接口回調、ViewModel 共享數據、EventBus 事件總線。
4.1 接口回調:最經典的通信方式
通過定義接口,讓 Activity 實現接口,Fragment 調用接口方法傳遞數據,適合簡單場景。
步驟 1:在 Fragment 中定義接口
public class CallbackFragment extends Fragment {// 定義回調接口public interface OnDataListener {void onDataChanged(String data); // Fragment傳遞數據給Activity}// 持有接口引用private OnDataListener mListener;// 綁定Activity時檢查是否實現接口@Overridepublic void onAttach(@NonNull Context context) {super.onAttach(context);// 確保Activity實現了接口if (context instanceof OnDataListener) {mListener = (OnDataListener) context;} else {throw new RuntimeException(context + "必須實現OnDataListener");}}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 點擊按鈕時調用接口傳遞數據view.findViewById(R.id.btn_send).setOnClickListener(v -> {if (mListener != null) {mListener.onDataChanged("來自Fragment的數據");}});}// 解除綁定時置空接口(避免內存泄漏)@Overridepublic void onDetach() {super.onDetach();mListener = null;}
}
步驟 2:Activity 實現接口接收數據
public class MainActivity extends AppCompatActivity implements CallbackFragment.OnDataListener {private TextView mTvResult;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTvResult = findViewById(R.id.tv_result);// 添加Fragmentif (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new CallbackFragment()).commit();}}// 實現接口方法,接收Fragment的數據@Overridepublic void onDataChanged(String data) {mTvResult.setText("收到數據:" + data);}
}
4.2 ViewModel:共享數據(AndroidX 推薦)
ViewModel 是 Jetpack 組件,可在 Activity 和 Fragment 之間共享數據,且不受生命周期影響(如屏幕旋轉數據不丟失),適合復雜場景。
步驟 1:添加依賴(AndroidX)
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.2'
步驟 2:創建共享的 ViewModel
// 共享的ViewModel,存儲需要傳遞的數據
public class SharedViewModel extends ViewModel {// LiveData:數據變化時通知觀察者private MutableLiveData<String> mSharedData = new MutableLiveData<>();// 設置數據public void setData(String data) {mSharedData.setValue(data);}// 獲取LiveData(對外暴露不可變的LiveData)public LiveData<String> getData() {return mSharedData;}
}
步驟 3:Fragment 發送數據
public class SenderFragment extends Fragment {private SharedViewModel mViewModel;@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 獲取與Activity共享的ViewModel(使用Activity的ViewModelStore)mViewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);// 點擊按鈕發送數據view.findViewById(R.id.btn_send).setOnClickListener(v -> {mViewModel.setData("來自SenderFragment的數據");});}
}
步驟 4:Activity 接收數據
public class MainActivity extends AppCompatActivity {private SharedViewModel mViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取ViewModelmViewModel = new ViewModelProvider(this).get(SharedViewModel.class);// 觀察數據變化mViewModel.getData().observe(this, data -> {// 更新UI((TextView) findViewById(R.id.tv_result)).setText(data);});// 添加發送數據的FragmentgetSupportFragmentManager().beginTransaction().add(R.id.container, new SenderFragment()).commit();}
}
優勢:
- 無需接口定義,代碼更簡潔;
- 數據在 Activity 旋轉時不會丟失;
- 支持多個 Fragment 與 Activity 共享數據。
4.3 EventBus:解耦的事件傳遞(復雜場景)
EventBus 是第三方庫,通過發布 - 訂閱模式實現組件間通信,適合多個 Fragment、Activity 之間的跨組件通信。
步驟 1:添加依賴
implementation 'org.greenrobot:eventbus:3.3.1'
步驟 2:定義事件類
// 事件類(存儲需要傳遞的數據)
public class MessageEvent {private String message;public MessageEvent(String message) {this.message = message;}public String getMessage() {return message;}
}
步驟 3:Fragment 發布事件
public class EventFragment extends Fragment {@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);// 點擊按鈕發布事件view.findViewById(R.id.btn_send).setOnClickListener(v -> {// 發布事件EventBus.getDefault().post(new MessageEvent("來自EventFragment的消息"));});}
}
步驟 4:Activity 訂閱事件
public class MainActivity extends AppCompatActivity {@Overrideprotected void onStart() {super.onStart();// 注冊EventBusEventBus.getDefault().register(this);}@Overrideprotected void onStop() {super.onStop();// 解注冊EventBus(必須調用,避免內存泄漏)EventBus.getDefault().unregister(this);}// 訂閱事件(方法名任意,需加@Subscribe注解)@Subscribe(threadMode = ThreadMode.MAIN) // 在主線程處理public void onMessageEvent(MessageEvent event) {// 更新UI((TextView) findViewById(R.id.tv_result)).setText(event.getMessage());}
}
適用場景:跨多個 Fragment、Activity 的通信(如首頁 Fragment 通知個人中心 Fragment 更新數據)。
五、Fragment 高級用法:ViewPager2 結合與懶加載
在實際開發中,Fragment 常與 ViewPager2 結合實現滑動切換(如首頁的 “推薦”“熱點”“關注” 標簽頁),并需要懶加載優化(僅在 Fragment 可見時加載數據)。
5.1 ViewPager2 + Fragment:滑動切換的標簽頁
ViewPager2 是 ViewPager 的升級版,支持垂直滑動、RTL 布局,且與 RecyclerView 共享適配器,更適合與 Fragment 結合。
步驟 1:添加 ViewPager2 依賴
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.fragment:fragment-ktx:1.5.5' // Fragment相關依賴
步驟 2:創建 ViewPager2 的適配器
public class ViewPager2Adapter extends FragmentStateAdapter {private List<String> mTitles; // 每個Fragment的標題public ViewPager2Adapter(@NonNull FragmentActivity fragmentActivity, List<String> titles) {super(fragmentActivity);mTitles = titles;}// 創建對應位置的Fragment@NonNull@Overridepublic Fragment createFragment(int position) {// 返回對應位置的Fragment(傳遞標題作為參數)return PagerFragment.newInstance(mTitles.get(position));}// 返回Fragment數量@Overridepublic int getItemCount() {return mTitles.size();}
}
步驟 3:創建 ViewPager2 的每個頁面 Fragment
public class PagerFragment extends Fragment {private static final String ARG_TITLE = "title";private String mTitle;public static PagerFragment newInstance(String title) {PagerFragment fragment = new PagerFragment();Bundle args = new Bundle();args.putString(ARG_TITLE, title);fragment.setArguments(args);return fragment;}@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (getArguments() != null) {mTitle = getArguments().getString(ARG_TITLE);}}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_pager, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);((TextView) view.findViewById(R.id.tv_title)).setText(mTitle);}
}
步驟 4:在 Activity 中配置 ViewPager2
public class ViewPager2Activity extends AppCompatActivity {private ViewPager2 mViewPager2;private TabLayout mTabLayout; // 用于顯示標簽@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_view_pager2);// 初始化數據(標簽標題)List<String> titles = Arrays.asList("推薦", "熱點", "關注", "視頻");// 配置ViewPager2mViewPager2 = findViewById(R.id.view_pager2);mViewPager2.setAdapter(new ViewPager2Adapter(this, titles));// 禁止滑動(可選)// mViewPager2.setUserInputEnabled(false);// 關聯TabLayout和ViewPager2(需添加TabLayoutMediator)mTabLayout = findViewById(R.id.tab_layout);new TabLayoutMediator(mTabLayout, mViewPager2, (tab, position) -> {tab.setText(titles.get(position)); // 設置標簽文本}).attach(); // 必須調用attach()}
}
布局文件
activity_view_pager2.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><!-- 標簽欄 --><com.google.android.material.tabs.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="wrap_content"/><!-- ViewPager2 --><androidx.viewpager2.widget.ViewPager2android:id="@+id/view_pager2"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/>
</LinearLayout>
fragment_pager.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="20sp"/>
</LinearLayout>
5.2 Fragment 懶加載:提升性能的關鍵
ViewPager2 默認會預加載相鄰的 Fragment(如顯示第 1 個,預加載第 2 個),若 Fragment 需要加載網絡數據,會導致浪費流量和性能。懶加載指 “僅當 Fragment 可見時才加載數據”。
實現方式(基于 ViewPager2)
public class LazyLoadFragment extends Fragment {private static final String ARG_TITLE = "title";private String mTitle;private boolean isLoaded = false; // 標記是否已加載數據private boolean isVisible = false; // 標記是否可見public static LazyLoadFragment newInstance(String title) {LazyLoadFragment fragment = new LazyLoadFragment();Bundle args = new Bundle();args.putString(ARG_TITLE, title);fragment.setArguments(args);return fragment;}@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (getArguments() != null) {mTitle = getArguments().getString(ARG_TITLE);}}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_lazy_load, container, false);}@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);((TextView) view.findViewById(R.id.tv_title)).setText(mTitle);// 視圖創建完成后,若已可見則加載數據if (isVisible && !isLoaded) {loadData();}}// ViewPager2中Fragment可見性變化時調用@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);isVisible = isVisibleToUser;// 可見且視圖已創建且未加載數據,則加載if (isVisibleToUser && getView() != null && !isLoaded) {loadData();}}// 加載數據(模擬網絡請求)private void loadData() {Log.d("LazyLoad", mTitle + "開始加載數據");new Thread(() -> {try {Thread.sleep(1000); // 模擬耗時// 更新UIrequireActivity().runOnUiThread(() -> {((TextView) getView().findViewById(R.id.tv_content)).setText(mTitle + "數據加載完成");isLoaded = true; // 標記為已加載});} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
核心邏輯:
- setUserVisibleHint():ViewPager2 中 Fragment 可見性變化時調用(isVisibleToUser為 true 表示可見);
- isLoaded標記:避免重復加載數據;
- 僅當 “可見 + 視圖已創建 + 未加載” 時才調用loadData()。
六、Fragment 常見問題及解決方案
Fragment 的使用中存在很多 “坑”,稍不注意就會導致崩潰或異常行為,以下是高頻問題及解決辦法。
6.1 Fragment 重疊問題(旋轉屏幕或配置變化)
現象:旋轉屏幕后,Fragment 重復顯示(多個相同 Fragment 疊加)。
原因:Activity 重建時,FragmentManager 會自動恢復 Fragment,若在onCreate()中再次add(),就會導致重復添加。
解決方案:在onCreate()中添加 Fragment 前判斷savedInstanceState == null:
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 僅在首次創建時添加Fragment,重建時不添加if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new MyFragment()).commit();}
}
6.2 getActivity () 返回 null 導致崩潰
現象:調用getActivity()時返回 null,觸發空指針異常。
原因:Fragment 已與 Activity 解除關聯(如onDetach()后),仍調用getActivity()。
解決方案:
- 避免在異步回調中使用getActivity()(如網絡請求回調,此時 Fragment 可能已銷毀);
- 使用requireActivity()替代getActivity()(內部會檢查,為 null 時拋出明確異常);
- 在onAttach()中保存 Activity 引用,onDetach()中置空:
private Activity mActivity;@Override public void onAttach(@NonNull Context context) {super.onAttach(context);mActivity = (Activity) context; }@Override public void onDetach() {super.onDetach();mActivity = null; // 解除引用 }// 使用時檢查非空 if (mActivity != null) {// 操作mActivity }
6.3 Fragment 事務提交異常(Can not perform this action after onSaveInstanceState)
現象:在onSaveInstanceState()后調用commit()提交事務,拋出異常。
原因:onSaveInstanceState()后,Activity 狀態已保存,此時提交事務可能導致狀態不一致。
解決方案:
- 使用commitAllowingStateLoss()替代commit()(允許狀態丟失,適合非關鍵事務);
- 避免在onPause() onStop()等生命周期后期提交事務;
- 若必須提交,確保在onResumeFragments()中執行。
6.4 回退棧問題(按返回鍵直接退出 Activity)
現象:添加 Fragment 到回退棧后,按返回鍵未顯示上一個 Fragment,而是直接退出 Activity。
原因:
- 未調用addToBackStack()(事務未加入回退棧);
- 回退棧為空(沒有可恢復的事務)。
解決方案:
- 添加事務時必須調用addToBackStack(null);
- 在 Activity 中重寫onBackPressed(),判斷回退棧是否為空:
@Override public void onBackPressed() {FragmentManager fm = getSupportFragmentManager();if (fm.getBackStackEntryCount() > 0) {// 回退棧有內容,彈出棧頂事務fm.popBackStack();} else {// 回退棧為空,退出Activitysuper.onBackPressed();} }
6.5 Fragment 內存泄漏
現象:Fragment 銷毀后仍被引用,導致無法回收,內存泄漏。
常見原因:
- Fragment 持有 Activity 的強引用(如匿名內部類new Thread()引用 Fragment,Fragment 引用 Activity);
- 異步任務(如網絡請求)未取消,持有 Fragment 引用;
- onDetach()后仍使用getActivity()。
解決方案:
- 異步任務在onDestroy()中取消(如cancel());
- 使用弱引用持有 Activity 或 Fragment:
// 弱引用持有Activity private WeakReference<Activity> mActivityRef;@Override public void onAttach(@NonNull Context context) {super.onAttach(context);mActivityRef = new WeakReference<>((Activity) context); }// 使用時獲取 Activity activity = mActivityRef.get(); if (activity != null && !activity.isFinishing()) {// 安全操作 }
- 避免在 Fragment 中使用靜態變量持有視圖或數據。
七、Fragment 最佳實踐總結
使用 Fragment 的核心原則是 “模塊化、低耦合、生命周期安全”,結合前文內容,最佳實踐可總結為:
1.創建與實例化:
- 始終使用newInstance()工廠方法創建 Fragment,通過Bundle傳遞參數;
- 禁止在構造方法中傳遞參數(Activity 重建時會丟失)。
2.生命周期管理:
- 在onViewCreated()中初始化視圖操作(避免在onCreateView()中操作 View);
- onDestroyView()中清理視圖資源(如ImageView.setImageBitmap(null));
- onDetach()中移除所有與 Activity 的引用。
3.與 Activity 通信:
- 簡單通信用接口回調,復雜通信用 ViewModel;
- 跨組件通信考慮 EventBus,但需注意注冊與解注冊。
4.性能優化:
- 結合 ViewPager2 時使用懶加載,避免預加載數據;
- 避免在 Fragment 中做耗時操作(移到子線程);
- 復用 Fragment 實例(如 ViewPager2 的適配器緩存 Fragment)。
5.異常處理:
- 調用getActivity()前先檢查是否為 null(或用requireActivity());
- 提交事務時注意時機,避免在onSaveInstanceState()后提交。
八、總結:Fragment 的核心價值與未來
Fragment 作為 Android 界面構建的核心組件,其 “模塊化” 和 “復用性” 的設計理念貫穿了 Android 開發的始終。從早期的Fragment到現在與ViewPager2、ViewModel的結合,Fragment 始終是構建靈活界面的最佳選擇。
掌握 Fragment 的關鍵在于理解其生命周期與 Activity 的聯動關系,以及如何通過合理的通信方式降低耦合。無論是簡單的頁面拆分,還是復雜的多面板適配,Fragment 都能提供優雅的解決方案。
未來,隨著 Jetpack 組件的發展(如Navigation組件對 Fragment 的管理),Fragment 的使用會更加簡化,但核心原理和最佳實踐始終不變 —— 理解這些本質,才能在各種場景中靈活運用 Fragment,構建出高效、穩定、可維護的 Android 應用。