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 应用。