Android Fragment 全解析

在 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 應用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90710.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90710.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90710.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

0-1BFS(雙端隊列,洛谷P4667 [BalticOI 2011] Switch the Lamp On 電路維修 (Day1)題解)

對于權重為0或1的路徑搜索中&#xff0c;使用雙端隊列可以對最短路問題進行時間復雜度的優化&#xff0c;由于優先隊列的O(longn)級別的插入時間&#xff0c;對于雙端隊列O(1)插入可以將時間復雜度減少至O(M); https://www.luogu.com.cn/problem/P4667 #include<bits/stdc…

基于LNMP架構的分布式個人博客搭建

1.運行環境主機主機名系統服務192.168.75.154Server-WebLinuxWeb192.168.75.155Server-NFS-DNSLinuxNFS/DNS2.基礎配置配置主機名&#xff0c;靜態IP地址開啟防火墻并配置部分開啟SElinux并配置服務器之間使用同ntp.aliyun.com進行時間同步服務器之間使用用ntp.aliyun.com進行時…

基于開源AI智能名片鏈動2+1模式S2B2C商城小程序的人格品牌化實現路徑研究

摘要&#xff1a;在數字化消費時代&#xff0c;人格品牌化已成為企業突破同質化競爭的核心策略。本文以開源AI智能名片、鏈動21模式與S2B2C商城小程序的融合為切入點&#xff0c;構建“技術賦能-關系重構-價值共生”的人格品牌化理論框架。通過分析用戶觸達、信任裂變與價值沉淀…

設計模式十一:享元模式(Flyweight Pattern)

享元模式是一種結構型設計模式&#xff0c;它通過共享對象來最小化內存使用或計算開銷。這種模式適用于大量相似對象的情況&#xff0c;通過共享這些對象的公共部分來減少資源消耗。基本概念享元模式的核心思想是將對象的內在狀態&#xff08;不變的部分&#xff09;和外在狀態…

Webpack/Vite 終極指南:前端開發的“渦輪增壓引擎“

開篇:當你的項目變成"俄羅斯套娃" "我的index.js怎么引入了87個文件?!" —— 這是每個前端開發者第一次面對復雜項目依賴時的真實反應。別擔心,今天我要帶你認識兩位"打包俠":老牌勁旅Webpack和新銳黑馬Vite 一、構建工具:前端世界的&qu…

Windows 下配置 GPU 用于深度學習(PyTorch)的完整流程

1. 安裝 NVIDIA 顯卡驅動 前往 NVIDIA官網 下載并安裝適合你顯卡型號&#xff08;如 5070Ti&#xff09;的最新版驅動。下載 NVIDIA 官方驅動 | NVIDIA安裝完成后建議重啟電腦。 2. 安裝 CUDA Toolkit 前往 CUDA Toolkit 下載頁。 選擇 Windows、x86_64、你的系統版本&#…

詳解力扣高頻SQL50題之180. 連續出現的數字【困難】

傳送門&#xff1a;180. 連續出現的數字 題目 表&#xff1a;Logs -------------------- | Column Name | Type | -------------------- | id | int | | num | varchar | -------------------- 在 SQL 中&#xff0c;id 是該表的主鍵。 id 是一個自增列。 找出所有至少連續…

VSCode 報錯 Error: listen EACCES: permission denied 0.0.0.0:2288

使用 npm run dev 啟動項目時報錯&#xff1a;error when starting dev server: Error: listen EACCES: permission denied 0.0.0.0:2288at Server.setupListenHandle [as _listen2] (node:net:1881:21)at listenInCluster (node:net:1946:12)at Server.listen (node:net:2044:…

[2025CVPR-圖象超分辨方向]DORNet:面向退化的正則化網絡,用于盲深度超分辨率

1. ?問題背景與挑戰? 盲深度超分辨率&#xff08;Blind Depth Super-Resolution, DSR&#xff09;的目標是從低分辨率&#xff08;LR&#xff09;深度圖中恢復高分辨率&#xff08;HR&#xff09;深度圖&#xff0c;但現有方法在真實場景下面臨顯著挑戰&#xff1a; ?已知…

關系與邏輯運算 —— 寄存器操作的 “入門鑰匙”

前言 哈嘍大家好&#xff0c;這里是 Hello_Embed 的新一篇學習筆記。在前文中&#xff0c;我們學習了如何用結構體指針操作硬件寄存器&#xff0c;而寄存器的配置往往離不開位運算和條件判斷 —— 比如通過邏輯運算精準修改某幾位的值&#xff0c;通過關系運算判斷硬件狀態。這…

使用 Python 將 CSV 文件轉換為帶格式的 Excel 文件

在日常的數據處理和報表生成工作中&#xff0c;CSV 格式因其簡潔性而被廣泛采用。但在展示數據時&#xff0c;CSV 文件往往缺乏格式和結構化樣式&#xff0c;不利于閱讀與分析。相比之下&#xff0c;Excel 格式&#xff08;如 .xlsx&#xff09;不僅支持豐富的樣式設置&#xf…

每天讀本書-《如何度過每天的24小時》

全景式書籍探索框架 1. “這本書是關于什么的&#xff1f;”——核心定位 一句話核心思想&#xff1a;這本書的核心并非教你如何高效地工作&#xff0c;而是倡導你將工作之外的“自由時間”視為一個“內在的另一天”&#xff0c;并投入智力與熱情去經營它&#xff0c;從而獲得精…

前端開發 React 狀態優化

為了更深入地理解 React 狀態管理的性能問題及其解決方案&#xff0c;本文將詳細分析 React Context 和 State 的性能問題&#xff0c;配以示例代碼說明優化策略。之后&#xff0c;討論 Redux 作為不可變庫的性能問題&#xff0c;并引出 Immer 作為優化解決方案。1. React Stat…

劍指offer第2版:雙指針+排序+分治+滑動窗口

一、p129-JZ21使奇數位于偶數前面&#xff08;不考慮相對位置&#xff09;&#xff08;hoare快排雙指針&#xff09; 調整數組順序使奇數位于偶數前面(二)_牛客題霸_牛客網 如果不考慮相對位置的話&#xff0c;那么我們可以模仿hoare快排&#xff0c;使用雙指針的思想&#xf…

14-C語言:第14天筆記

C語言&#xff1a;第14天筆記 內容提要 指針 變量指針與指針變量 指針變量做函數參數指針變量指向數組元素 數組指針與指針數組 數組指針回顧 變量指針與指針變量 變量指針&#xff1a;變量的地址值&#xff08;首地址&#xff09;&#xff0c;本質是指針、地址 指針變量&#…

【筆記】活度系數推導

文章目錄一、理想溶液的假設與局限性1.1 理想溶液的定義1.2 理想溶液的局限性二、活度與活度系數的引入2.1 活度的定義2.2 修正后的化學勢表達式三、活度系數的物理意義四、為什么需要活度系數&#xff1f;4.1 理論需求4.2 擴散理論中的必要性五、活度系數的具體作用5.1 在化學…

基于Docker的GPU版本飛槳PaddleOCR部署深度指南(國內鏡像)2025年7月底測試好用:從理論到實踐的完整技術方案

還是網上沒找到這個基于Docker的GPU版本飛槳PaddleOCR部署教程&#xff0c;于是就有了這一篇。 這個的確坑很多&#xff0c;可能后面變一個版本就不好用了&#xff0c;也是為什么這篇博客不完全粘貼代碼的原因。 端口是示例&#xff0c;可以隨意改。在人工智能與文檔數字化高速…

Python-初學openCV——圖像預處理(三)

目錄 一、邊緣填充 1、邊界復制 2、邊界反射 3、邊界反射101 4、邊界常數 5、邊界包裹 二、透視變換 三、圖像掩膜 1、制作掩膜 2、與運算 3、顏色替換 四、ROI切割 五、圖像添加水印 一、邊緣填充 我們對圖像進行處理后&#xff0c;需要對空出來的區域進行一個填充…

【ESP32設備通信】-W5500與ESP32 /ESP32 S3集成

W5500與ESP32 /ESP32 S3集成 文章目錄 W5500與ESP32 /ESP32 S3集成 1、W5500介紹 2、硬件準備與接線 3、代碼實現 3.1 以太網設置 3.2 簡單HTTP請求 3.3 HTTPS請求 3.4 查詢證書 ESP32 憑借其強大的 Wi-Fi 功能,一直是物聯網項目的熱門選擇。ESP32 現在支持帶有 SSL 的原生以太…

vue - 使用canvas繪制驗證碼

封裝繪制驗證碼 verify-code.vue<template><div class"captcha"><canvas ref"canvasRef" :width"width" :height"height" click"refreshCaptcha"></canvas></div> </template><scri…