9、Fragment入门:Fragment生命周期、静态与动态加载、Fragment与Activity通信

Fragment,说白了就是Activity里的“子页面”。

我刚开始做Android开发时,总觉得Fragment是个多余的东西。一个Activity搞定一切不好吗?直到我接手了一个平板和手机双端适配的项目……嗯,那之后我才真正理解Fragment的价值。

9.1 Fragment的生命周期

Fragment的生命周期比Activity复杂一些。它依附于Activity,所以Activity的生命周期会直接影响Fragment。

我个人习惯把Fragment生命周期分成三个阶段:

  • 创建阶段:onAttach → onCreate → onCreateView → onViewCreated → onActivityCreated
  • 运行阶段:onStart → onResume
  • 销毁阶段:onPause → onStop → onDestroyView → onDestroy → onDetach

这里有个关键点——onCreateViewonViewCreated的区别。我在项目中遇到过不少新手把初始化逻辑写在onCreateView里,结果视图还没完全创建好就操作控件,导致空指针。正确的做法是:onCreateView只负责inflate布局,真正的视图初始化放到onViewCreated里做。

核心要点:Fragment的生命周期回调顺序是固定的,但具体执行时机受Activity状态影响。比如Activity处于后台时,Fragment的onStart不会被调用。

个人经验:我曾经在onDestroyView里忘记释放资源,导致内存泄漏。后来我养成了一个习惯——所有绑定视图的引用都在onDestroyView里置空,避免Fragment重建时持有旧视图。

9.2 静态加载Fragment

静态加载,说白了就是在布局文件里直接写<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
        android:id="@+id/header_fragment"
        android:name="com.example.HeaderFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <fragment
        android:id="@+id/content_fragment"
        android:name="com.example.ContentFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

静态加载的优点是简单直接,缺点也很明显——你没法在运行时替换它。所以,我一般只在固定布局(比如标题栏、底部导航)里用静态加载。

注意:静态加载的Fragment,它的生命周期是由Activity直接管理的。Activity销毁时,Fragment也会跟着销毁。如果你需要保持Fragment的状态,记得在onSaveInstanceState里保存数据。

9.3 动态加载Fragment

动态加载才是Fragment的真正威力所在。你可以在运行时添加、替换、移除Fragment,实现灵活的页面切换。

核心API就三个:

  • add():添加Fragment到容器
  • replace():替换容器中的Fragment
  • remove():移除Fragment

操作步骤很简单:

// 获取FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();

// 创建Fragment实例
MyFragment fragment = new MyFragment();

// 替换容器中的Fragment
transaction.replace(R.id.fragment_container, fragment);
transaction.addToBackStack(null); // 可选,加入返回栈
transaction.commit();

这里有个坑——commit()是异步的,它会在主线程的下一个空闲时机执行。如果你需要立即执行,可以用commitNow()。但要注意,commitNow()不能在onSaveInstanceState之后调用,否则会抛异常。

避坑指南:我曾经在Activity的onCreate里直接调用commit(),结果Fragment没有显示。后来发现是因为onCreate执行时,Activity的视图还没完全准备好。正确的做法是在onResumeonPostCreate里提交事务。

9.4 Fragment与Activity通信

Fragment和Activity之间怎么传数据?这是初学者最常问的问题。我总结了三种常用方式:

方式一:接口回调(推荐)

这是最规范的方式。Fragment定义接口,Activity实现接口。

// Fragment中定义接口
public class MyFragment extends Fragment {
    public interface OnItemClickListener {
        void onItemClick(String data);
    }

    private OnItemClickListener listener;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnItemClickListener) {
            listener = (OnItemClickListener) context;
        }
    }

    // 在合适的地方调用
    private void notifyClick(String data) {
        if (listener != null) {
            listener.onItemClick(data);
        }
    }
}

// Activity中实现接口
public class MainActivity extends AppCompatActivity 
        implements MyFragment.OnItemClickListener {
    @Override
    public void onItemClick(String data) {
        // 处理Fragment传来的数据
    }
}

方式二:ViewModel共享数据

如果你用了Jetpack,ViewModel是最优雅的方式。多个Fragment和Activity共享同一个ViewModel实例。

// 定义ViewModel
public class SharedViewModel extends ViewModel {
    private MutableLiveData<String> selectedItem = new MutableLiveData<>();

    public LiveData<String> getSelectedItem() {
        return selectedItem;
    }

    public void selectItem(String item) {
        selectedItem.setValue(item);
    }
}

// Fragment中使用
SharedViewModel viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
viewModel.getSelectedItem().observe(getViewLifecycleOwner(), item -> {
    // 更新UI
});

方式三:Bundle传参

适合简单的初始化数据传递。注意,Bundle只能传基本类型和序列化对象。

// 创建Fragment时传参
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString("key", "value");
fragment.setArguments(args);

// Fragment中接收
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    String value = getArguments().getString("key");
}

我的建议:能用ViewModel就别用接口回调。ViewModel的生命周期更清晰,而且天然支持数据持久化。接口回调适合那些需要即时响应的场景,比如按钮点击。

9.5 知识体系总览

下面这张图是我自己整理的Fragment知识结构,你可以对照着复习:

Fragment入门 生命周期 onAttach → onCreate onCreateView → onViewCreated onStart → onResume onPause → onStop onDestroyView → onDestroy onDetach 加载方式 静态加载:布局中声明 动态加载:代码中管理 add / replace / remove commit / commitNow 通信方式 接口回调 ViewModel共享 Bundle传参 EventBus(不推荐) 核心原则:Fragment是Activity的“子页面” 生命周期受Activity影响,通信推荐ViewModel

9.6 避坑总结

最后,我把自己踩过的坑整理成了一张表,你写代码时对照着看:

常见问题 原因 解决方案
Fragment不显示 commit()在视图未准备好时调用 在onResume或onPostCreate中提交
空指针异常 在onCreateView中操作视图 移到onViewCreated中操作
Fragment重叠 Activity重建时重复添加Fragment 使用findFragmentByTag检查是否已存在
内存泄漏 Fragment持有Activity引用未释放 在onDestroyView中置空视图引用
数据丢失 Fragment重建时未保存状态 在onSaveInstanceState中保存数据

最后提醒一句:Fragment不是银弹。如果你的页面逻辑很简单,用View或自定义View可能更合适。别为了用Fragment而用Fragment,这是很多新手容易犯的毛病。


公众号:蓝海资料掘金营,微信deep3321