在 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
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 加载布局(第三个参数必须为false,避免重复添加到container)
return inflater.inflate(R.layout.fragment_static, container, false);
}
@Override
public 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">
<Button
android: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 -->
<fragment
android:id="@+id/fragment_static"
android:name="com.example.fragmentdemo.StaticFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!-- 其他布局 -->
<TextView
android: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"; // 传递参数的key
private 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;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 从Arguments获取参数(避免在构造方法中传参,防止重建丢失)
if (getArguments() != null) {
mTitle = getArguments().getString(ARG_TITLE);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dynamic, container, false);
}
@Override
public 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">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"/>
<Button
android: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) -->
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<!-- 控制按钮 -->
<Button
android: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;
@Override
protected 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"))
);
}
// 添加Fragment
public 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();
}
// 移除Fragment
public 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时检查是否实现接口
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// 确保Activity实现了接口
if (context instanceof OnDataListener) {
mListener = (OnDataListener) context;
} else {
throw new RuntimeException(context + "必须实现OnDataListener");
}
}
@Override
public 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的数据");
}
});
}
// 解除绑定时置空接口(避免内存泄漏)
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
步骤 2:Activity 实现接口接收数据
public class MainActivity extends AppCompatActivity implements CallbackFragment.OnDataListener {
private TextView mTvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvResult = findViewById(R.id.tv_result);
// 添加Fragment
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new CallbackFragment())
.commit();
}
}
// 实现接口方法,接收Fragment的数据
@Override
public 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;
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取ViewModel
mViewModel = new ViewModelProvider(this).get(SharedViewModel.class);
// 观察数据变化
mViewModel.getData().observe(this, data -> {
// 更新UI
((TextView) findViewById(R.id.tv_result)).setText(data);
});
// 添加发送数据的Fragment
getSupportFragmentManager().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 {
@Override
public 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 {
@Override
protected void onStart() {
super.onStart();
// 注册EventBus
EventBus.getDefault().register(this);
}
@Override
protected 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
@Override
public Fragment createFragment(int position) {
// 返回对应位置的Fragment(传递标题作为参数)
return PagerFragment.newInstance(mTitles.get(position));
}
// 返回Fragment数量
@Override
public 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;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mTitle = getArguments().getString(ARG_TITLE);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_pager, container, false);
}
@Override
public 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; // 用于显示标签
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_pager2);
// 初始化数据(标签标题)
List<String> titles = Arrays.asList("推荐", "热点", "关注", "视频");
// 配置ViewPager2
mViewPager2 = 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.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<!-- ViewPager2 -->
<androidx.viewpager2.widget.ViewPager2
android: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">
<TextView
android: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;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mTitle = getArguments().getString(ARG_TITLE);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_lazy_load, container, false);
}
@Override
public 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可见性变化时调用
@Override
public 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); // 模拟耗时
// 更新UI
requireActivity().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 { // 回退栈为空,退出Activity super.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 应用。