news 2026/10/5 1:49:03

android-training-course-in-chinese 教程精讲:Fragment 与宿主 Activity 的交互通信(接口回调模式)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
android-training-course-in-chinese 教程精讲:Fragment 与宿主 Activity 的交互通信(接口回调模式)
  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

本篇技术指南围绕《Android 官方培训课程中文版》(android-training-course-in-chinese)中 "与其他 Fragment 交互" 一课展开,讲解在基于 Fragment 的动态 UI 架构下,如何通过宿主 Activity 与回调接口实现 Fragment 之间的消息传递。读完本文,你将掌握"Fragment 内定义接口、onAttach() 中绑定实现、Activity 实现接口接收事件、findFragmentById() 反向传参"这一完整链路,并能写出兼容平板两窗格(two-pane)与手机单窗格(one-pane)布局的新闻阅读器式应用。

Fragment 通信架构原则:自包含组件,交互只经宿主 Activity

为了最大化复用 Fragment UI 组件,每一个 Fragment 都应被构建成完全自包含、模块化的组件:自己定义布局、自己管理行为。当多个这样的可复用 Fragment 组合进同一个 Activity 时,Activity 便承担起"复合 UI"的组装职责。

本课给出的核心架构铁律是:

所有 Fragment 之间的交互都应通过与之关联的 Activity 来完成,两个 Fragment 之间不应直接交互。

例如,当用户在"新闻标题列表 Fragment"中点击某一条目时,列表 Fragment 不应直接去修改右侧"文章内容 Fragment"的界面,而是先把事件上抛给 Activity,由 Activity 决定下一步做什么(更新右侧 Fragment,或替换整个界面)。这种"自下而上回调、自上而下派发"的模式,让 Fragment 之间保持零耦合:任何一个 Fragment 都可以被换掉、复用到别的 Activity 中,而不会破坏整体逻辑。

第一步:在 Fragment 中定义回调接口

要让 Fragment 与容器 Activity 交互,标准做法是:在 Fragment 类内部定义一个接口,由宿主 Activity 实现它。Fragment 在自己的onAttach()生命周期方法中获取该接口的实现(即宿主 Activity 本身),之后随时可以调用接口方法向 Activity 发消息。

(译注:onAttach()是 Fragment 与 Activity 关联时自动触发的生命周期回调;只要 Fragment 中实现了onAttach()方法,系统就会在它被附加到 Activity 时自动调用。)

以"新闻标题列表"Fragment 为例,完整代码如下:

public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // 容器 Activity 必须实现该接口 // (译注:"容器 Activity"意即"包含该 Fragment 的 Activity") public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // 确认容器 Activity 已实现该回调接口。否则,抛出异常 try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } ... }

关键点拆解:

  • 接口定义在 Fragment 内部(OnHeadlineSelectedListener),使回调契约与触发方天然绑定,便于阅读和维护;
  • onAttach()中做强制类型转换:将宿主 Activity 强转为接口类型并存入成员变量mCallback;
  • try/catch捕获ClassCastException:如果容器 Activity 忘了实现该接口,会在 Fragment 附加时立即以清晰、可定位的异常信息暴露出来(例如 "com.example.MainActivity must implement OnHeadlineSelectedListener"),而不是等到用户点击时才出现莫名其妙的空指针或类型错误。

兼容性说明:本课程基于 Android Support Library v4 时代的 API 编写(参见 basics/fragments/creating.md 中import android.support.v4.app.Fragment的写法)。在更高 API 级别中,onAttach(Activity)已被onAttach(Context context)取代,判断逻辑通常改写为if (context instanceof OnHeadlineSelectedListener)后再赋值,思路完全相同。

定义好接口后,Fragment 就通过mCallback(OnHeadlineSelectedListener的实例)随时向 Activity 传递消息。例如,当用户点击列表条目时,触发下方回调:

@Override public void onListItemClick(ListView l, View v, int position, long id) { // 向宿主 Activity 传送事件 mCallback.onArticleSelected(position); }

ListFragment在用户点击列表项时自动调用onListItemClick(),这里把position(被点击条目的索引)作为参数上抛给宿主 Activity。Fragment 自身完全不知道也不关心 Activity 收到后要做什么——解耦由此达成。

第二步:宿主 Activity 实现接口并接收回调

要接收回调事件,宿主 Activity 必须声明implements该 Fragment 中定义的接口,并提供接口方法的实现:

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // 用户从 HeadlinesFragment 选择了一篇文章的标题 // 在这里做点什么,以显示该文章 } }

至此,事件链已经完整闭合:

  1. 用户点击列表项 →onListItemClick()被调用;
  2. Fragment 调用mCallback.onArticleSelected(position);
  3. mCallback实际指向宿主 Activity → Activity 的onArticleSelected()被触发;
  4. Activity 在回调中编排下一步 UI 操作。

值得注意:由于onAttach()中做过类型校验,Activity 若漏写implements会在运行早期抛出明确的异常;而这里onArticleSelected(int position)的实现,就是"复合 UI"逻辑真正落地的位置。

第三步:Activity 向 Fragment 传递消息

回调把"用户选了哪篇文章"送进了 Activity。接下来 Activity 需要把这条消息反向推送给负责展示文章内容的 Fragment。手段是:通过findFragmentById()获取 Fragment 实例,再直接调用它的 public 方法。

例如,假设 Activity 中还包含另一个用于展示文章详情的 FragmentArticleFragment,Activity 就可以在回调中拿到被选位置的数据并转发给它。完整示例(含两窗格与单窗格两种分支处理):

public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // 用户从 HeadlinesFragment 选择了一篇文章的标题 // 在这里做点什么,以显示该文章 ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { // 若 articleFrag 有效,则表示我们正在处理两格布局(two-pane layout)…… // 调用 ArticleFragment 的方法,以更新其内容 articleFrag.updateArticleView(position); } else { // 否则,我们正在处理单格布局(one-pane layout)。此时需要 swap frags... // 创建 Fragment,向其传递包含被选文章的参数 ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // 无论 fragment_container 视图里是什么,用该 Fragment 替换它。并将 // 该事务添加至回栈,以便用户可以往回导航(译注:回栈,即 Back Stack。 // 在有多个 Activity 的 APP 中,将这些 Activity 按创建次序组织起来的 // 栈,称为回栈) transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // 执行事务 transaction.commit(); } } }

这段代码展示了两种典型分支,对应两种屏幕布局策略:

分支一:两窗格布局(two-pane)。findFragmentById(R.id.article_fragment)返回非空,说明平板等宽屏上ArticleFragment已经静态地躺在布局里。此时无需创建新实例,直接调用其 public 方法updateArticleView(position)就地刷新内容即可——两个 Fragment 依旧互不直接引用,消息由 Activity 中转。

分支二:单窗格布局(one-pane)。手机上R.id.article_fragment不存在,返回null。此时需要动态"换屏":新建ArticleFragment,把被选位置position放进Bundle,再通过setArguments(args)绑定给新 Fragment,最后用FragmentTransaction执行replace(R.id.fragment_container, newFragment)+addToBackStack(null)+commit()。这样用户按返回键时,事务会从回栈弹出,界面"撤销"回列表页。

两种传参方式的语义区别值得强调:

  • setArguments(Bundle):在 Fragment 创建前注入参数,参数会被系统在 Activity 重建、Fragment 重新附加时保留(与savedInstanceState协同工作),适合"初始数据";
  • 调用 public 方法(如updateArticleView(position)):适合更新已存在 Fragment 的运行时状态。

配套布局:两窗格与单窗格布局是如何定义的

本课默认读者已完成前两课(basics/fragments/creating.md 与 basics/fragments/fragment-ui.md),其中给出了与上述代码一一对应的布局方案,这里一并梳理:

**宽屏(平板)**使用res/layout-large/news_articles.xml,通过 XML 一次性静态放置两个 Fragment,并用layout_weight分配宽度(来自 basics/fragments/creating.md):

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>

这里@+id/article_fragment正是上一节findFragmentById()要查找的 ID;layout_weight="2"使文章区占三分之二宽度。

**手机(小屏)**使用同名但位于无large限定符目录下的res/layout/news_articles.xml,只放一个空的FrameLayout作为 Fragment 容器(来自 basics/fragments/fragment-ui.md):

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />

两窗格与单窗格的差异,正是上节回调代码中findFragmentById()判空分支的存在原因。下图直观展示了同一 Activity 在不同屏幕尺寸下的两种配置:

图1:两个 Fragment 显示在不同尺寸屏幕上同一 Activity 的不同配置中。宽阔屏幕上两个 Fragment 并列显示;手机上一次只能显示一个,必须在用户导航时替换 Fragment(图片来自 basics/fragments/fragment-ui.md)。

另外,onCreate()中运行时添加初始 Fragment 时,务必先检查savedInstanceState != null就提前返回,避免 Activity 重建后叠加重叠的 Fragment(详见 basics/fragments/fragment-ui.md 的MainActivity示例)。

最佳实践与注意事项汇总

综合本课及配套课程,可沉淀出以下可直接落地到项目的实践要点:

  1. Fragment 永不直接互调。所有跨 Fragment 的消息一律经宿主 Activity 中转,否则组件无法独立复用,也难以测试。
  2. 接口校验前置到onAttach()。用try/catch包裹强转,宿主未实现接口时立刻抛出ClassCastException,错误信息中带上 Activity 类名,排障成本最低。
  3. 先判空再更新。对静态存在的 Fragment 使用findFragmentById()前,先判空以区分两窗格/单窗格布局,避免在单窗格设备上 NPE。
  4. addToBackStack(null)的语义(来自 basics/fragments/fragment-ui.md):移除或替换 Fragment 并加入回栈时,被移除的 Fragment 是停止而非销毁,用户按返回键可还原;不加入回栈则会直接销毁。addToBackStack()的可选字符串参数仅供FragmentManager.BackStackEntry高级操作使用,常规场景传null即可。
  5. 支持库 API 的选择:支持 Android 1.6(API level 4)及以上设备时,Activity 需继承FragmentActivity并使用getSupportFragmentManager();最低版本 ≥ 11 可用原生Activity;若引入 v7 appcompat 库,则应继承AppCompatActivity(其本身是FragmentActivity的子类),参见 basics/fragments/creating.md。
  6. 静态<fragment>标签的限制:通过 XML 静态添加的 Fragment 无法在运行时移除或替换,因此凡需要"换屏"的交互场景,都必须走FragmentTransaction动态添加/替换的路线。

本章在课程中的位置

本课是 basics/fragments/index.md("使用 Fragment 建立动态 UI")三课中的收官一课,前两课分别解决"创建 Fragment"(basics/fragments/creating.md)与"运行时动态增删替换"(basics/fragments/fragment-ui.md)。将三课连起来即得到一个完整的、基于 Fragment 的响应式多窗格新闻阅读器实现思路;全书的课程导航见 SUMMARY.md。官方配套的完整示例工程名为 FragmentBasics,建议边读本课边对照示例中的MainActivity、HeadlinesFragment与ArticleFragment三个类验证本文所述的回调链路。

  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

相关推荐

上一篇:Box2D终极性能指南:AVX2 vs SSE2对比分析
下一篇:Maestro:快速跑通移动 UI 自动化测试

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 1:37:56

MobileViG实战:轻量图神经网络图像分类从训练到部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:37:51

OpenBMC开发环境构建实战:Yocto与BitBake从入门到落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:37:22

YOLO肺结节检测数据集:5000张CT标注与训练全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:35:55

Flowable动态审批人配置:Spring Boot工作流告别硬编码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:35:55

Halcon C++工业相机实时采集与SDK配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:35:40

工业嵌入式存储方案:MKV46F256VLH16与MR25H40CDF的SPI通信与掉电保护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华