Android Fragment 全解析

在 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 应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Monkey-旭

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值