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();
// 清理视图引用,防止内存泄漏
}
}
注意:onDestroyView和onDestroy是两个不同的回调。我曾经在项目里把网络请求的取消写在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();
动态加载有几个要点:
- 容器:需要一个
FrameLayout或LinearLayout作为占位容器 - 事务:每次操作都要通过
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的核心知识点,你可以把它当作学习路线图:
8.5 避坑总结
最后,我把自己在项目中踩过的坑整理一下:
| 常见问题 | 原因 | 解决方案 |
|---|---|---|
| Fragment重叠 | 屏幕旋转后重新创建Fragment | 在onSaveInstanceState中保存状态,或设置setRetainInstance(true) |
| 空指针异常 | 在onCreateView中操作控件 |
所有控件操作移到onViewCreated中 |
| Fragment不显示 | 忘记调用commit() |
检查事务是否提交,建议用commitNow()在UI线程立即执行 |
| 内存泄漏 | Fragment持有Activity引用 | 在onDetach中置空监听器,避免使用非静态内部类 |
Fragment这东西,说白了就是Activity的“好帮手”。用好了,你的代码结构会非常清晰;用不好,各种奇奇怪怪的bug就来了。我个人建议:刚开始学的时候,先拿一个简单的页面练手,把生命周期打上日志,看看每个回调的调用顺序。嗯,这样印象会深刻很多。