8、Fragment详解:生命周期、通信、管理与实战

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

我刚开始学 Android 时,总觉得 Fragment 有点多余——一个 Activity 不够用吗?后来项目越做越大,才发现 Fragment 是真香。它让页面复用变得特别灵活,尤其是平板和手机适配,没有 Fragment 简直寸步难行。

8.1 Fragment 的生命周期

Fragment 的生命周期比 Activity 复杂一些。为什么?因为它依附于 Activity,Activity 活着它才能活着。

先看一张我画的流程图,帮你快速建立整体印象:

Fragment 生命周期核心流程 onAttach() onCreate() onCreateView() onActivityCreated() onStart() onResume() onDestroyView() onDestroy() onDetach() 绿色:活跃状态 | 蓝色:创建阶段 | 红色:销毁阶段

嗯,这里要注意:onCreateView() 是加载布局的地方,onActivityCreated() 是确保 Activity 已创建完成,可以安全操作 Activity 组件的地方。

核心区别:Fragment 有 onAttach()onDetach(),Activity 没有。这两个方法专门处理 Fragment 与 Activity 的绑定和解绑。

8.2 Fragment 与 Activity 通信

通信这块,我见过太多新手直接写 ((MainActivity) getActivity()).xxx()。能用,但耦合太强。你想想看,如果哪天 Activity 换了,代码全得改。

我个人习惯用接口回调。Fragment 定义一个接口,Activity 去实现。这样 Fragment 完全不知道 Activity 是谁,只知道自己要调用一个接口方法。

// 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;
    }
}

// 需要通知 Activity 时
listener.onItemClick("用户点击了某个条目");

小技巧:onDetach() 里把 listener 置为 null,防止内存泄漏。我曾经因为忘了这一步,导致 Activity 被 Fragment 持有引用,内存泄漏查了半天。

反过来,Activity 向 Fragment 传数据,用 Bundle 或者直接调用 Fragment 的公共方法就行。

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

// Fragment 中获取
String value = getArguments().getString("key");

8.3 FragmentManager 与 FragmentTransaction

FragmentManager 是 Fragment 的大管家。所有增删改查操作,都得通过它来调度。

FragmentTransaction 是具体执行者。每次操作都要 beginTransaction() 开启一个事务,然后 add()replace()remove(),最后 commit() 提交。

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();

ft.add(R.id.container, new MyFragment(), "TAG_MY");
ft.addToBackStack(null); // 加入返回栈
ft.commit();

注意:commit() 必须在 Activity 保存状态之前调用。如果在 onSaveInstanceState() 之后调用,会抛异常。可以用 commitAllowingStateLoss() 绕过,但我不建议滥用——数据丢失了别怪我没提醒。

我遇到过一个问题:连续快速点击按钮,导致 Fragment 重复添加。解决方案很简单——在 commit() 之前检查 isAdded() 或者用 popBackStackImmediate() 清理。

8.4 ViewPager + Fragment 实战

ViewPager 搭配 Fragment,是 App 首页最常用的组合。说白了就是左右滑动切换页面,每个页面是一个 Fragment。

核心适配器是 FragmentPagerAdapterFragmentStatePagerAdapter。区别在于:

适配器 特点 适用场景
FragmentPagerAdapter Fragment 常驻内存,不会销毁 页面较少(3-5个),切换频繁
FragmentStatePagerAdapter 不可见 Fragment 会被销毁,只保存状态 页面较多,节省内存
public class MyPagerAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragments;

    public MyPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.fragments = fragments;
    }

    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
}

// 使用
ViewPager viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), fragmentList));

避坑指南:使用 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 这个参数。旧版默认是 BEHAVIOR_SET_USER_VISIBLE_HINT,会导致 Fragment 的 onResume() 在不可见时也被调用。我踩过这个坑,日志里一堆奇怪的生命周期问题。

实战中,我通常还会配合 TabLayout 一起用:

TabLayout tabLayout = findViewById(R.id.tabLayout);
tabLayout.setupWithViewPager(viewPager);

// 设置 Tab 标题
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        // 页面切换时的逻辑
    }
});

嗯,到这里 Fragment 的核心内容基本讲完了。生命周期是基础,通信是桥梁,FragmentManager 是调度中心,ViewPager 是实战利器。把这四个点吃透,Fragment 这块你就拿下了。

一句话总结:Fragment 不是 Activity 的替代品,而是 Activity 的得力助手。用好了,代码复用率翻倍;用不好,生命周期能让你怀疑人生。


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