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

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

我刚开始做Android开发时,总觉得Fragment这东西有点多余——一个Activity不够用吗?后来项目越做越大,一个界面要切换好几个模块,我才发现Fragment是真香。它能让你的界面像搭积木一样灵活组合。

今天我们就来聊聊Fragment的三大核心:生命周期、加载方式、通信机制。嗯,这些搞懂了,你就能在项目里游刃有余了。

8.1 Fragment的生命周期

Fragment的生命周期比Activity复杂一些。为什么?因为它既要管自己的状态,又要跟着Activity的状态走。

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

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

这里有个关键点:onCreateView是加载布局的地方,onViewCreated是操作视图的地方。我在项目中遇到过不少新手把这两步搞混,结果在onCreateView里直接操作控件,导致空指针异常。

核心区别onCreateView返回布局,onViewCreated表示布局已经加载完成,可以安全操作控件。

来看一个完整的生命周期示例:

public class MyFragment extends Fragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Fragment与Activity关联
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 初始化数据
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // 加载布局
        return inflater.inflate(R.layout.fragment_my, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 操作控件,设置监听
        TextView tv = view.findViewById(R.id.text_view);
        tv.setText("Fragment已加载");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        // 清理视图引用,防止内存泄漏
    }
}

注意onDestroyViewonDestroy是两个不同的回调。我曾经在项目里把网络请求的取消写在onDestroy里,结果Fragment视图销毁了但实例还在,导致回调时更新了不存在的视图——空指针就来了。

8.2 静态加载与动态加载

Fragment的加载方式有两种:静态和动态。说白了,静态就是写在XML里,动态就是在代码里加。

8.2.1 静态加载

静态加载很简单,直接在Activity的布局文件里放一个<fragment>标签:

<?xml version="1.0" encoding="utf-8"?>
<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/static_fragment"
        android:name="com.example.MyFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

这种方式适合固定不变的界面。比如一个应用的底部导航栏,每个Tab对应一个Fragment,用静态加载就很方便。

小技巧:静态加载的Fragment,Activity可以直接通过getSupportFragmentManager().findFragmentById()获取到实例。

8.2.2 动态加载

动态加载更灵活。你可以在代码里随时添加、替换、移除Fragment。我在项目中用得最多的就是这种方式。

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

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

// 添加Fragment
transaction.add(R.id.fragment_container, fragment, "my_fragment");
// 或者替换
// transaction.replace(R.id.fragment_container, fragment);

// 提交事务
transaction.commit();

动态加载有几个要点:

  • 容器:需要一个FrameLayoutLinearLayout作为占位容器
  • 事务:每次操作都要通过FragmentTransaction提交
  • 回退栈:调用addToBackStack()可以让用户按返回键回到上一个Fragment

避坑指南:我曾经在项目里连续调用了两次transaction.replace(),结果第二个Fragment把第一个覆盖了,但第一个Fragment的视图还在内存里。后来我养成了习惯——每次提交事务前,先检查isAdded()方法,确保Fragment没有被重复添加。

8.3 Fragment与Activity通信

Fragment和Activity之间怎么传数据?这是面试常问的问题,也是项目里绕不开的坑。

8.3.1 Activity → Fragment

Activity向Fragment传数据,最常用的方式是通过Bundle

// Activity中
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("key", "Hello Fragment");
fragment.setArguments(bundle);

// Fragment中
String data = getArguments().getString("key");

我个人习惯在Fragment里写一个静态的newInstance()方法,把参数封装好:

public static MyFragment newInstance(String data) {
    MyFragment fragment = new MyFragment();
    Bundle args = new Bundle();
    args.putString("key", data);
    fragment.setArguments(args);
    return fragment;
}

这样调用方只需要传参数,不用关心Bundle的细节。嗯,代码看起来也清爽很多。

8.3.2 Fragment → Activity

Fragment向Activity传数据,我推荐用接口回调。为什么?因为Fragment不应该直接持有Activity的引用,否则容易造成内存泄漏。

// 定义接口
public interface OnFragmentInteractionListener {
    void onFragmentMessage(String message);
}

// Fragment中
public class MyFragment extends Fragment {
    private OnFragmentInteractionListener listener;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            listener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    // 在某个事件中调用
    private void sendMessage() {
        listener.onFragmentMessage("Hello from Fragment");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null; // 防止内存泄漏
    }
}

// Activity中实现接口
public class MainActivity extends AppCompatActivity implements OnFragmentInteractionListener {
    @Override
    public void onFragmentMessage(String message) {
        // 处理来自Fragment的消息
        Log.d("TAG", "收到消息: " + message);
    }
}

核心原则:Fragment不直接操作Activity的控件,而是通过接口回调让Activity自己处理。这样Fragment的复用性更高,换一个Activity也能正常工作。

8.3.3 其他通信方式

除了接口回调,还有几种方式:

  • ViewModel:用Android Architecture Components的ViewModel,Fragment和Activity共享数据。这是目前官方推荐的方式。
  • EventBus:用事件总线库,适合跨多个Fragment的通信。但要注意不要滥用,否则代码会变得难以追踪。
  • 直接调用:通过getActivity()获取Activity实例,然后调用它的方法。这种方式简单粗暴,但耦合度高,我不建议在复杂项目里用。

我的建议:小项目用接口回调就够了。大项目建议上ViewModel,它天然支持生命周期感知,不会因为屏幕旋转而丢失数据。

8.4 Fragment知识体系总览

下面这张图总结了Fragment的核心知识点,你可以把它当作学习路线图:

Fragment知识体系 Fragment 生命周期 加载方式 通信机制 onCreateView onViewCreated onDestroyView 静态加载 动态加载 Bundle传参 接口回调 ViewModel 掌握这三块,Fragment就算入门了

8.5 避坑总结

最后,我把自己在项目中踩过的坑整理一下:

常见问题 原因 解决方案
Fragment重叠 屏幕旋转后重新创建Fragment onSaveInstanceState中保存状态,或设置setRetainInstance(true)
空指针异常 onCreateView中操作控件 所有控件操作移到onViewCreated
Fragment不显示 忘记调用commit() 检查事务是否提交,建议用commitNow()在UI线程立即执行
内存泄漏 Fragment持有Activity引用 onDetach中置空监听器,避免使用非静态内部类

Fragment这东西,说白了就是Activity的“好帮手”。用好了,你的代码结构会非常清晰;用不好,各种奇奇怪怪的bug就来了。我个人建议:刚开始学的时候,先拿一个简单的页面练手,把生命周期打上日志,看看每个回调的调用顺序。嗯,这样印象会深刻很多。