<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>朴哲一的博客</title>
  <subtitle>日日是好日</subtitle>
  <link href="https://dimon94.github.io/atom.xml" rel="self"/>
  <link href="https://dimon94.github.io/"/>
  <updated>2025-09-25T03:04:42.854Z</updated>
  <id>https://dimon94.github.io/</id>
  
  <author>
    <name>朴哲一</name>
    
  </author>
  
  <generator>Hexo</generator>
  <follow_challenge>
    <feedId>102401408877157380</feedId>
    <userId>71048939542365180</userId>
  </follow_challenge>
  
  <entry>
    <title>我宣布，这才是 AI 开发的“圣杯”（上）</title>
    <link href="https://dimon94.github.io/2025/09/25/%E6%88%91%E5%AE%A3%E5%B8%83%EF%BC%8C%E8%BF%99%E6%89%8D%E6%98%AF%20AI%20%E5%BC%80%E5%8F%91%E7%9A%84%E2%80%9C%E5%9C%A3%E6%9D%AF%E2%80%9D%EF%BC%88%E4%B8%8A%EF%BC%89/"/>
    <id>https://dimon94.github.io/2025/09/25/%E6%88%91%E5%AE%A3%E5%B8%83%EF%BC%8C%E8%BF%99%E6%89%8D%E6%98%AF%20AI%20%E5%BC%80%E5%8F%91%E7%9A%84%E2%80%9C%E5%9C%A3%E6%9D%AF%E2%80%9D%EF%BC%88%E4%B8%8A%EF%BC%89/</id>
    <published>2025-09-25T02:39:16.000Z</published>
    <updated>2025-09-25T03:04:42.854Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;你有没有觉得，和 AI 一起写代码，时常像在带一个天赋异禀但不守规矩的实习生？&lt;/p&gt;
&lt;p&gt;它能瞬间写出你半小时都憋不出的函数，也能在你最需要稳定输出时，给你一段天马行空的“艺术创作”。我们试图用复杂的 Prompt、用各种框架去约束它，但多数时候，我们仍像个疲惫的指挥官，在混乱的战场上勉力维持秩序。&lt;/p&gt;
&lt;p&gt;因为我们搞错了一件事：开发的核心，从来不是写代码。&lt;/p&gt;
&lt;p&gt;几十年来，代码为王。我们写的文档、画的图，都是为了服务于那最终的代码。代码是唯一的事实，文档是终将腐朽的脚手架。&lt;/p&gt;
&lt;p&gt;但今天，AI 正在颠覆这一切。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;GitHub 官方发布的一个开源项目 spec-kit（已获 2.4万+ Star），背后揭示了一种全新的开发范式——规约驱动开发（Spec-Driven Development, SDD）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/35914B6C-AED5-433F-8526-50841C0AA001_2/qX0rolSqlQtfxxA6cZuCvPYFtw0jQCOjH9AuAyqXm3Iz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;它的核心思想简单而彻底：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Spec-Driven Development (SDD) inverts this power structure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;规约驱动开发（SDD）颠覆了这种权力结构。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在这场“权力反转”中，代码不再是国王，而“规约”（Specification）——一份定义了“做什么”和“为什么做”的文档——加冕为王。代码，只是规约忠实的仆人。&lt;/p&gt;
&lt;p&gt;这不仅是效率的提升，这是一场软件开发的工业革命。&lt;/p&gt;
&lt;p&gt;这篇文章，就是我亲身实践这场革命的完整记录。我将带你见证，一个模糊的商业想法，是如何在一个下午之内，被 AI 变成一份包含完整架构设计和50个开发任务的专业工程蓝图。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;Spec-Driven-Development，AI时代的「开发宪章」&quot;&gt;&lt;a href=&quot;#Spec-Driven-Development，AI时代的「开发宪章」&quot; class=&quot;headerlink&quot; title=&quot;Spec-Driven Development，AI时代的「开发宪章」&quot;&gt;&lt;/a&gt;Spec-Driven Development，AI时代的「开发宪章」&lt;/h1&gt;&lt;p&gt;在深入案例前，我们必须先理解 SDD 的世界观。它到底是什么？&lt;/p&gt;
&lt;p&gt;如果说我之前摸索的 &lt;code&gt;cc-devflow&lt;/code&gt; 是一套精巧的“流程管理办法”，那么 SDD 就是一部指导AI行为的“开发宪章”。&lt;/p&gt;
&lt;p&gt;SDD 的强大，在于它为混乱的AI开发世界，建立了三条不容置疑的核心原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;规约为通用语 (Specifications as the Lingua Franca)&lt;/strong&gt; 规约，是人类与AI之间沟通的唯一语言。它取代了代码，成为项目的“唯一事实来源”。我们的一切讨论、修改、迭代，都围绕规约进行。代码，仅仅是规约在某个特定技术栈下的自动翻译。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规约即可执行 (Executable Specifications)&lt;/strong&gt; 规约不能是模糊的、供人揣测的散文。它必须精确、完整、无歧义，足以被AI直接理解并生成对应的代码和测试用例。这从根本上消除了“需求”与“实现”之间的鸿沟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反馈即进化 (Bidirectional Feedback)&lt;/strong&gt; 生产环境的运行数据、用户的行为、甚至是系统报错，都会反向流动，成为修订“规约”的输入。这形成了一个从想法到规约，再到代码，最终回归到规约的完美闭环。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/DF24FE27-EB49-4D3C-AE91-1CAA3AF03C74_2/fCHOFirCR5y5PrFqlcOx9hyx7DIS88KM0GhsruQWlOQz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;理解了这三点，你就能明白，SDD 不仅仅是“让AI写代码”，而是建立了一套全新的、以规约为中心的、可持续进化的开发契约。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;我如何用15分钟，为“小红书商机挖掘器”绘制完整工程蓝图&quot;&gt;&lt;a href=&quot;#我如何用15分钟，为“小红书商机挖掘器”绘制完整工程蓝图&quot; class=&quot;headerlink&quot; title=&quot;我如何用15分钟，为“小红书商机挖掘器”绘制完整工程蓝图&quot;&gt;&lt;/a&gt;&lt;strong&gt;我如何用15分钟，为“小红书商机挖掘器”绘制完整工程蓝图&lt;/strong&gt;&lt;/h1&gt;&lt;p&gt;理论听起来总是优雅的。&lt;/p&gt;
&lt;p&gt;现在，让我们进入实战，看看这部“宪章”的威力。&lt;/p&gt;
&lt;p&gt;我的目标很简单：开发一个“小红书商机挖掘器”。&lt;/p&gt;
&lt;p&gt;能分析评论，挖掘真实需求，并验证其商业可行性。&lt;/p&gt;
&lt;p&gt;这是一个典型的、充满不确定性的模糊想法。&lt;/p&gt;
&lt;p&gt;在传统模式下，这至少需要一个产品经理、一个架构师、一个项目经理花费数天时间来回拉扯。&lt;/p&gt;
&lt;p&gt;但在SDD模式下，我只用了三条命令。&lt;/p&gt;
&lt;h2 id=&quot;init-constitution-—-为项目“立宪法”&quot;&gt;&lt;a href=&quot;#init-constitution-—-为项目“立宪法”&quot; class=&quot;headerlink&quot; title=&quot;/init &amp;amp; /constitution — 为项目“立宪法”&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;code&gt;/init &amp;amp; /constitution&lt;/code&gt; — 为项目“立宪法”&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;首先，我通过 &lt;code&gt;uv tool install&lt;/code&gt; 安装了 &lt;code&gt;spec-kit&lt;/code&gt;，并用 &lt;code&gt;specify init &amp;lt;项目名&amp;gt; &lt;/code&gt;初始化了我的项目。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;1️⃣：uv tool install specify-cli --from git+https://github.com/github/spec-kit.git&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;2️⃣：&lt;code&gt;specify init &amp;lt;项目名&amp;gt; &lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/ed2d9f28-b24a-2747-ff91-87bc7383237e/r5FLv3NuU5v3HKrFCULjgwJHJbhjWGFHw7syRS7QJ08z/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/192fd63b-bfac-dc2b-fd99-8dab5922569e/x8x06yGP7g26OHZgxrwjL3e9tNpCifgDic2ApM4DwcIz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;init 项目后，会自动生成.claude 和 .specify 两个文件夹。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/6d8e7184-fb41-6855-f682-98ab2d33f4bd/lKpqAD2SacNODV5QnAeUTa4HQUyMrHMlQrzfiG4yjlsz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为我们在 init 的时候选择的是 Claude Code，spec-kit 会自动生成Claude Code 能够识别的 commands。&lt;/p&gt;
&lt;p&gt;.specify 文件夹里包含了memory（宪章模板） 、scripts（脚本）、templates（plan&amp;#x2F;sepc&amp;#x2F;task 等模板）&lt;/p&gt;
&lt;p&gt;init 项目后，启动Claude Code 会自动相关命令。（偶尔会抽抽识别不出来）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/a6c60141-7ff2-6e83-3b59-383446d5ebe1/SekqgVmWH7G2hz7tUq5K0bhQNsQiRzcpA8fJyxt2048z/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;我用官方的宪章文案再加上我平时的一些让 AI 遵守的原则，使用 &lt;code&gt;/constitution&lt;/code&gt; 命令创建您项目的治理原则和发展指南，这些原则将指导所有后续的开发。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/29806654-3663-dff2-c9b4-e8a94b03d2f3/x66ALYvS2ti4LdwoJP1PZ1xUpIxyRUWY5IG1w8vrBkgz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;执行后，一个关键的文件诞生了：.specify&amp;#x2F;memory&amp;#x2F;constitution.md。&lt;/p&gt;
&lt;p&gt;这，就是我们项目的“宪法”。它不是普通的配置文件，它是一系列不可动摇的原则，是给AI戴上的“思想钢印”，确保它未来所有的行为都严格遵守我们的工程纪律。&lt;/p&gt;
&lt;p&gt;比如，其中最震撼的条款之一：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Article III: Test-First Imperative&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;This is NON-NEGOTIABLE: All implementation MUST follow strict Test-Driven Development.&lt;br&gt;No implementation code shall be written before:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Unit tests are written&lt;br&gt;Tests are validated and approved by the user&lt;br&gt;Tests are confirmed to FAIL (Red phase)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;第三条：测试优先原则 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;这是不可协商的：所有实现必须严格遵循测试驱动开发。 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;在编写以下内容之前，不得编写任何实现代码： &lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;blockquote&gt;
&lt;p&gt;单元测试已编写 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;blockquote&gt;
&lt;p&gt;测试已通过用户验证和批准 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;blockquote&gt;
&lt;p&gt;测试已确认失败（红色阶段）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“不可协商”、“严格遵循测试驱动开发”、“确认测试失败”。&lt;/p&gt;
&lt;p&gt;在项目开始的第一分钟，我就以“宪法”的形式，将软件工程的最佳实践，注入了AI的灵魂。&lt;/p&gt;
&lt;h2 id=&quot;specify-—-AI化身“顶级产品经理”&quot;&gt;&lt;a href=&quot;#specify-—-AI化身“顶级产品经理”&quot; class=&quot;headerlink&quot; title=&quot;/specify — AI化身“顶级产品经理”&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;code&gt;/specify&lt;/code&gt; — AI化身“顶级产品经理”&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;我向AI下达了第一个指令，用一长串自然、甚至有些口语化的中文描述了我的想法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#x2F;specify  开发一个工具，专门用于分析小红书里用户的评论，挖掘某类ToC 用户的真实需求，并且通过爬虫技术和 AI 能力挖掘需求且验证需求，验证是否为商机，我希望的是找到具体为某个需求付费的客户，这个客户量是否足够大，需求是否真实和用户是否愿意掏出真金白银付费，且实际可落地的需求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/b9f4415a-fc34-f273-cddd-3157a16e8985/ruN0bp72fgnO94qmbpKUGojb6gk7flbH7ky5CKnTlIwz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;AI没有立刻开始写代码，而是扮演起了产品经理的角色：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自动创建了一个语义化的分支&lt;/strong&gt;：&lt;code&gt;001-toc-ai&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成了一份结构化的规约文档&lt;/strong&gt;：&lt;code&gt;spec.md&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这份 &lt;code&gt;spec.md&lt;/code&gt; 里，包含了用户故事、功能清单、非功能性需求、甚至还有AI自己提出的“待澄清项 &lt;code&gt;[NEEDS CLARIFICATION]&lt;/code&gt;”。&lt;/p&gt;
&lt;p&gt;它将我的发散想法，瞬间收敛成了一份专业的PRD文档。&lt;/p&gt;
&lt;p&gt;这一步的价值，是&lt;strong&gt;驯服模糊性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;正如官方文档所说：“这改变了传统的软件开发生命周期——需求和设计变成了持续的活动，而不是离散的阶段。”&lt;/p&gt;
&lt;h2 id=&quot;plan-—-AI化身“首席架构师”&quot;&gt;&lt;a href=&quot;#plan-—-AI化身“首席架构师”&quot; class=&quot;headerlink&quot; title=&quot;/plan — AI化身“首席架构师”&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;code&gt;/plan&lt;/code&gt; — AI化身“首席架构师”&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;有了“做什么”的规约，接下来是“怎么做”。&lt;/p&gt;
&lt;p&gt;我继续用自然语言下达指令，描述我的技术偏好：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#x2F;plan 我希望能够运行在浏览器里,网页作为前端页面,后端只需要在本地即&lt;br&gt;可,Python作为爬虫技术,openrouter作为AI模型的提供商,模型使用openai&amp;#x2F;gpt-5,最后输出的内容可以是.md为格式的报告内容,可以下载到本地,元数据存储在本地SQLite 数据库中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AI再次启动，这次它的角色是“首席架构师”。它没有直接输出代码，而是生成了一整套的架构设计蓝图，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;plan.md&lt;/code&gt;：记录了技术选型、性能目标、约束条件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-model.md&lt;/code&gt;：定义了数据实体和表结构。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contracts/api-contract.yaml&lt;/code&gt;：一份标准的OpenAPI合约文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/4F95DCE1-F6AB-4C77-B8D0-0A2318C0ECAE_2/6DIoWuxw3P0fywZNxKwXskJdFbbtpgyIkywUq0y1xw8z/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;最关键的是，AI的这次设计，并非天马行空，而是&lt;strong&gt;严格遵守了我们之前立下的“宪法”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;plan.md&lt;/code&gt; 文件中，我看到了一个“合宪性检查”（Constitution Check）部分，AI会逐条自审其设计是否符合“KISS原则”、“TDD原则”等宪法条款。&lt;/p&gt;
&lt;p&gt;这一步的价值在于，&lt;strong&gt;设计必须遵从宪法&lt;/strong&gt;，确保了架构的规范性和一致性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/6BACF1D3-8125-427B-889C-2A581EC50ABC_2/aU6EwY4w4gQEWhwiD0YXwh2rjDm3mASUhvRUdxqpRREz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;tasks-—-AI化身“资深项目总监”&quot;&gt;&lt;a href=&quot;#tasks-—-AI化身“资深项目总监”&quot; class=&quot;headerlink&quot; title=&quot;/tasks — AI化身“资深项目总监”&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;code&gt;/tasks&lt;/code&gt; — AI化身“资深项目总监”&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;规约已定，蓝图已出。最后一步，生成可执行的路线图。&lt;/p&gt;
&lt;p&gt;我只输入了一个简单的命令：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#x2F;tasks&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/E640F092-9FBD-4982-8569-0BA81FEA8851/0eca5bb7-cea9-04b3-b430-dfbf7d300562/1tYLx88zj9gwcXdRhkhxWMLUI47nGnwPM64YlSJn4Y8z/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;AI迅速分析了所有前面生成的文档，然后输出了一份tasks.md文件。这份文件，是一个包含50个具体开发任务的甘特图！&lt;/p&gt;
&lt;p&gt;它清晰地列出了：&lt;/p&gt;
&lt;p&gt;任务分布：Setup, Contract Tests, Data Models, Core Services…&lt;br&gt;依赖关系：哪些任务必须按顺序执行。&lt;br&gt;并行机会：哪些任务可以并行处理以提高效率（用[P]标记）。&lt;br&gt;最让我惊叹的是任务的顺序：&lt;/p&gt;
&lt;p&gt;排在最前面的，永远是“Contract Tests”（合约测试）。&lt;/p&gt;
&lt;p&gt;这完美印证了宪法的内容：&lt;/p&gt;
&lt;p&gt;“TDD 贯彻始终，永远是测试先行”。&lt;/p&gt;
&lt;p&gt;AI不是在空喊口号，而是将测试驱动开发的理念，落实到了每一个具体的执行步骤中。&lt;/p&gt;
&lt;p&gt;至此，从一个模糊的想法，到一个包含完整架构设计和50个开发任务的专业工程计划，总共耗时不到15分钟。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;我的认知被刷新：它凭什么比一个开发团队更“靠谱”？&quot;&gt;&lt;a href=&quot;#我的认知被刷新：它凭什么比一个开发团队更“靠谱”？&quot; class=&quot;headerlink&quot; title=&quot;我的认知被刷新：它凭什么比一个开发团队更“靠谱”？&quot;&gt;&lt;/a&gt;我的认知被刷新：它凭什么比一个开发团队更“靠谱”？&lt;/h1&gt;&lt;p&gt;看到这里，你可能会问：这太神奇了，但它凭什么这么靠谱？它的核心魔法到底是什么？&lt;/p&gt;
&lt;p&gt;在我看来，spec-kit的魔法不在于生成代码，而在于它通过两大支柱，为善于创造却疏于纪律的AI，注入了“软件工程的灵魂”。&lt;/p&gt;
&lt;h2 id=&quot;魔法揭秘1：模板-Templates-—-AI的“写作脚手架”&quot;&gt;&lt;a href=&quot;#魔法揭秘1：模板-Templates-—-AI的“写作脚手架”&quot; class=&quot;headerlink&quot; title=&quot;魔法揭秘1：模板(Templates) — AI的“写作脚手架”&quot;&gt;&lt;/a&gt;魔法揭秘1：模板(Templates) — AI的“写作脚手架”&lt;/h2&gt;&lt;p&gt;【引：我的感悟：“流程、清单、模板的价值，在上下文管理中，好用到超乎你地想象”。】&lt;/p&gt;
&lt;p&gt;spec-kit的.specify&amp;#x2F;templates&amp;#x2F;目录下，存放着各种输出物的模板。这些模板就像一个精密的“写作脚手架”，通过内置的规则和清单，强力约束着AI的行为。&lt;/p&gt;
&lt;p&gt;例如，模板会强制AI：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免猜测：当信息不足时，必须使用[NEEDS CLARIFICATION]标记，而不是自己编造一个“合理”的假设。&lt;/li&gt;
&lt;li&gt;保持抽象：在写spec.md时，只关注“What”和“Why”，严禁涉及“How”（技术实现）。&lt;/li&gt;
&lt;li&gt;自我检查：模板内置的清单，就像“单元测试”，强迫AI在输出前检查自己是否遗漏了关键点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;魔法揭秘2：宪法-Constitution-—-AI的“思想钢印”&quot;&gt;&lt;a href=&quot;#魔法揭秘2：宪法-Constitution-—-AI的“思想钢印”&quot; class=&quot;headerlink&quot; title=&quot;魔法揭秘2：宪法(Constitution) — AI的“思想钢印”&quot;&gt;&lt;/a&gt;魔法揭秘2：宪法(Constitution) — AI的“思想钢印”&lt;/h2&gt;&lt;p&gt;如果说模板解决了“单次输出”的质量，那么constitution.md则解决了“长期演进”的纪律。&lt;/p&gt;
&lt;p&gt;这部“宪法”是不可变的根本大法，它定义了项目的架构DNA。例如，除了TDD，它还规定了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Article I: Library-First Principle: 所有功能都必须先作为独立的库来实现，强制模块化。&lt;/li&gt;
&lt;li&gt;Article VII: Simplicity Gate: 严禁过度设计和未来证明（future-proofing）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些原则通过“思想钢印”的方式，确保了无论项目如何演进，无论未来由哪个版本的AI模型来维护，其架构风格和工程质量都能保持高度一致。&lt;/p&gt;
&lt;p&gt;模板是AI的“行为准则”，宪法是AI的“价值信仰”。 两者结合，构成了一个强大的约束系统，让AI从一个自由散漫的“艺术家”，变成了一个纪律严明的“工程大师”。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;快速上手：今天就为你的AI“立宪”&quot;&gt;&lt;a href=&quot;#快速上手：今天就为你的AI“立宪”&quot; class=&quot;headerlink&quot; title=&quot;快速上手：今天就为你的AI“立宪”&quot;&gt;&lt;/a&gt;&lt;strong&gt;快速上手：今天就为你的AI“立宪”&lt;/strong&gt;&lt;/h1&gt;&lt;p&gt;spec-kit 和它背后的SDD范式，远不止一个提效工具那么简单。它预示着一个新时代的到来：&lt;/p&gt;
&lt;p&gt;开发者的核心价值，正在从“代码的生产者”，转变为高质量“规约”的设计者、AI架构的监督者、以及整个系统意图的定义者。&lt;/p&gt;
&lt;p&gt;我们正在从一个给AI下达零碎指令的“老板”，变成一个与AI签订严谨契约的“合伙人”。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&quot;本期互动&quot;&gt;&lt;a href=&quot;#本期互动&quot; class=&quot;headerlink&quot; title=&quot;本期互动&quot;&gt;&lt;/a&gt;本期互动&lt;/h1&gt;&lt;p&gt;你觉得SDD模式最先会在哪个领域&amp;#x2F;场景爆发？&lt;/p&gt;
&lt;p&gt;是个人项目、创业公司，还是大厂的创新团队？&lt;/p&gt;
&lt;p&gt;在评论区聊聊你的看法。&lt;/p&gt;
&lt;h2 id=&quot;下期预告-·-敬请期待&quot;&gt;&lt;a href=&quot;#下期预告-·-敬请期待&quot; class=&quot;headerlink&quot; title=&quot;下期预告 · 敬请期待&quot;&gt;&lt;/a&gt;下期预告 · 敬请期待&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;（上篇）我们用15分钟规划了一个项目，（下篇）我们将亲手执行这50个任务，把“小红书商机挖掘器”真正跑起来，并把全部代码开源！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关注我们，见证实战全过程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用AI重塑工作流，而不只是生成内容。&lt;/strong&gt;&lt;/p&gt;
]]></content>
    
      <category term="AI研习录"/>
    
    
      <category term="AI"/>
    
  </entry>
  
  <entry>
    <title>我以为找到了AI开发的“圣杯”，结果差点被Token账单劝退……直到我悟了</title>
    <link href="https://dimon94.github.io/2025/09/18/%E6%88%91%E4%BB%A5%E4%B8%BA%E6%89%BE%E5%88%B0%E4%BA%86AI%E5%BC%80%E5%8F%91%E7%9A%84%E2%80%9C%E5%9C%A3%E6%9D%AF%E2%80%9D%EF%BC%8C%E7%BB%93%E6%9E%9C%E5%B7%AE%E7%82%B9%E8%A2%ABToken%E8%B4%A6%E5%8D%95%E5%8A%9D%E9%80%80%E2%80%A6%E2%80%A6%E7%9B%B4%E5%88%B0%E6%88%91%E6%82%9F%E4%BA%86%20copy/"/>
    <id>https://dimon94.github.io/2025/09/18/%E6%88%91%E4%BB%A5%E4%B8%BA%E6%89%BE%E5%88%B0%E4%BA%86AI%E5%BC%80%E5%8F%91%E7%9A%84%E2%80%9C%E5%9C%A3%E6%9D%AF%E2%80%9D%EF%BC%8C%E7%BB%93%E6%9E%9C%E5%B7%AE%E7%82%B9%E8%A2%ABToken%E8%B4%A6%E5%8D%95%E5%8A%9D%E9%80%80%E2%80%A6%E2%80%A6%E7%9B%B4%E5%88%B0%E6%88%91%E6%82%9F%E4%BA%86%20copy/</id>
    <published>2025-09-18T13:37:16.000Z</published>
    <updated>2025-09-25T02:36:59.030Z</updated>
    
    <content type="html"><![CDATA[&lt;h2 id=&quot;那个让我热血沸腾的下午&quot;&gt;&lt;a href=&quot;#那个让我热血沸腾的下午&quot; class=&quot;headerlink&quot; title=&quot;那个让我热血沸腾的下午&quot;&gt;&lt;/a&gt;那个让我热血沸腾的下午&lt;/h2&gt;&lt;p&gt;我至今还记得第一次看到 CCPM (&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2F1dG9tYXplaW8vY2NwbQ==&quot;&gt;https://github.com/automazeio/ccpm&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;) 项目的那个下午。&lt;/p&gt;
&lt;p&gt;看到这个项目 GitHub 上 4.7K 个 star，心想：妥了！&lt;/p&gt;
&lt;p&gt;屏幕上，一行行命令被敲下，AI智能体（Agent）像一支训练有素的特种部队，自动分析需求、创建任务、并行处理开发……一切井然有序，充满了未来感。&lt;/p&gt;
&lt;p&gt;我当时的感觉，就像是寻觅多年的屠龙刀终于现世。我激动地拍着大腿，告诉自己：就是它了，敏捷开发的终极答案！纯 AI 口喷代码要升级为自动化开发了！！！&lt;/p&gt;
&lt;p&gt;我摩拳擦掌，立刻在自己的项目里部署了这套系统，准备大干一场。我幻想着，从此以后，我只需要提出需求，我的“AI军团”就会夜以继日地为我工作，交付完美的代码。&lt;/p&gt;
&lt;p&gt;然而，我没高兴太久。两天后，一个残酷的现实几乎把我从云端拽回了地面。&lt;/p&gt;
&lt;h2 id=&quot;踩坑实录：我的“AI军团”怎么成了一群“金鱼”？&quot;&gt;&lt;a href=&quot;#踩坑实录：我的“AI军团”怎么成了一群“金鱼”？&quot; class=&quot;headerlink&quot; title=&quot;踩坑实录：我的“AI军团”怎么成了一群“金鱼”？&quot;&gt;&lt;/a&gt;踩坑实录：我的“AI军团”怎么成了一群“金鱼”？&lt;/h2&gt;&lt;p&gt;一开始还算顺利，但很快，问题就一个接一个地冒了出来。&lt;/p&gt;
&lt;p&gt;我让Claude Code 开发一个AI 小说编译器，一开始用 GPT-5 生成的 PRD（大家都说 GPT-5 计划能力比 Claude 强，适合做架构师），CCPM一顿输出，在 GitHub 项目里生成了一堆 Issue，开始生成一堆开发流的子代理开始深入开发，我发现每个开发流开发的代码都比较差强人意。&lt;/p&gt;
&lt;p&gt;更要命的是，它们好像都患上了“短期失忆症”，记忆力跟金鱼差不多，只有7秒。&lt;/p&gt;
&lt;p&gt;每一次启动一个子任务，它都得把所有的项目资料、代码规范、需求文档重新读一遍。&lt;/p&gt;
&lt;p&gt;我感觉自己不像个运筹帷幄的指挥官，倒像个絮絮叨叨的老妈子，跟在一群健忘的机器人屁股后面，一遍遍重复着：“这是需求文档，拿去看！”，“这是代码规范，别忘了！”，“刚刚那个API是这么定义的，你倒是记一下啊！”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/29BF2A0D-BD4D-4A0D-80A9-F6C63A2BF1CB/1E4C54A8-9397-435E-84A1-A1E463DC6021_2/wYwBOru9vegZRsTkf1stplWG9CDenzQE5ekNcIlc1ycz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;这种“失忆”的代价是巨大的。&lt;/p&gt;
&lt;p&gt;那天，我怀着忐忑的心情点开了我的Token账单，心瞬间凉了半截。那串不断跳动的数字狠狠地告诉我：我的“AI军团”主要工作不是在写代码，而是在烧钱。&lt;/p&gt;
&lt;p&gt;每一次“失忆”，都是一次昂贵得离谱的上下文重复加载。&lt;/p&gt;
&lt;p&gt;我第一次对AI自动化的前景产生了怀疑。这条路，真的走得通吗？至少以这种方式，肯定走不通。&lt;/p&gt;
&lt;p&gt;后面我查到，Claude Code 子代理（Sub-agent）与主代理的上下文不共享。为了完成任务，子代理需要反复查询和加载项目资料，导致上下文信息重复、混乱且极易丢失。&lt;/p&gt;
&lt;p&gt;上下文的反复加载，使得每一次调用都成本高昂，让自动化流程在经济上变得不可行。&lt;/p&gt;
&lt;h2 id=&quot;坑底的思考：问题不在AI，在“管理模式”&quot;&gt;&lt;a href=&quot;#坑底的思考：问题不在AI，在“管理模式”&quot; class=&quot;headerlink&quot; title=&quot;坑底的思考：问题不在AI，在“管理模式”&quot;&gt;&lt;/a&gt;坑底的思考：问题不在AI，在“管理模式”&lt;/h2&gt;&lt;p&gt;碰壁之后，我冷静了一下。我关掉IDE，开始复盘。&lt;/p&gt;
&lt;p&gt;我问自己一个问题：如果这是一个真人团队，我会怎么管理？&lt;/p&gt;
&lt;p&gt;我绝不会找来10个刚毕业的实习生，把他们关在一个会议室里，扔给他们一堆文档，然后说“你们开始干活吧，自由发挥”。那只会是一场灾难。&lt;/p&gt;
&lt;p&gt;一个高效的团队，必然有清晰的层级和分工。&lt;/p&gt;
&lt;p&gt;这时，我脑子里闪过一道光，瞬间想通了。&lt;/p&gt;
&lt;p&gt;CCPM那一套并行模式，像什么呢？&lt;/p&gt;
&lt;p&gt;虽然项目名称叫做 PM，但是它却像一个没有项目经理的“扁平化委员会”。每个AI Agent都是平等的成员，信息共享基本靠吼（反复读取公共资料），最终导致信息过载、责任不清、效率低下。&lt;/p&gt;
&lt;p&gt;我真正需要的，应该是什么呢？&lt;/p&gt;
&lt;p&gt;我需要一个“高效指挥部”！必须有一个唯一的项目经理（PM），他掌握所有信息和最终决策权。团队其他成员都是领域专家（前端、后端、测试等）。专家们接任务、做研究、写分析报告，然后把报告统一交给PM来整合和执行。专家不需要、也不应该知道项目的全部细节，那只会干扰他们的专业判断。&lt;/p&gt;
&lt;p&gt;这个“指挥官-专家”模式，就是我找到的答案！我立刻为我的AI团队重新制定了铁律：&lt;/p&gt;
&lt;p&gt;唯一的“指挥官”: 必须有一个主Agent，它独占所有“写”权限，是唯一的代码“操盘手”，手握最终代码库的写入权限。&lt;br&gt;专注的“专家们”: 其他所有子Agent，都降级为“研究员”和“规划师”。它们只负责思考和写文档（比如需求分析、技术方案），然后把报告交上来。它们不准碰代码。&lt;br&gt;这样一来，上下文永远是统一的，“指挥官”拥有最完整的记忆。Token成本也瞬间降低，因为不再需要反复加载那昂贵的基础信息了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/29BF2A0D-BD4D-4A0D-80A9-F6C63A2BF1CB/CFF5F8C2-5C09-4775-81D9-94BF6ACD00BE_2/4ODCvczKXZoDb62fm5yic9EXb31MbxgvsTZyjxfBTE4z/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;动手开干：打造我的“AI开发流水线”cc-devflow&quot;&gt;&lt;a href=&quot;#动手开干：打造我的“AI开发流水线”cc-devflow&quot; class=&quot;headerlink&quot; title=&quot;动手开干：打造我的“AI开发流水线”cc-devflow&quot;&gt;&lt;/a&gt;动手开干：打造我的“AI开发流水线”&lt;code&gt;cc-devflow&lt;/code&gt;&lt;/h2&gt;&lt;p&gt;有了新思路，我立刻开始敲代码。&lt;/p&gt;
&lt;p&gt;这一次，我的目标不再是造一个更聪明的AI，而是设计一套更聪明的“工作流程”。我要让开发者真正成为指挥官，只需要下达一个命令，剩下的交给这条自动流水线。&lt;/p&gt;
&lt;p&gt;我把它命名为&lt;code&gt;cc-devflow&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它的核心，就是那套“指挥部”规矩，我把它变成了真实可用的代码：&lt;/p&gt;
&lt;h3 id=&quot;一行命令的魔法：-flow-new&quot;&gt;&lt;a href=&quot;#一行命令的魔法：-flow-new&quot; class=&quot;headerlink&quot; title=&quot;一行命令的魔法：/flow-new&quot;&gt;&lt;/a&gt;一行命令的魔法：&lt;code&gt;/flow-new&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;过去，启动一个复杂任务需要敲一连串命令。现在，你只需要在终端输入：&lt;br&gt;&lt;code&gt;/flow-new &amp;quot;REQ-123|用户下单支持&amp;quot;&lt;/code&gt;&lt;br&gt;这就像你走到AI项目经理“老王”的工位，把需求文档拍在他桌上说：“老王，这个需求你跟一下，从头到尾。” 简单、直接。&lt;/p&gt;
&lt;h3 id=&quot;透明的进度条：-flow-status&quot;&gt;&lt;a href=&quot;#透明的进度条：-flow-status&quot; class=&quot;headerlink&quot; title=&quot;透明的进度条：/flow-status&quot;&gt;&lt;/a&gt;透明的进度条：&lt;code&gt;/flow-status&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;项目开始后，你随时可以像老板一样，问一句：“老王，那事儿到哪一步了？”&lt;br&gt;&lt;code&gt;/flow-status REQ-100 --detailed&lt;/code&gt;&lt;br&gt;他会立刻给你一份清晰的进度报告，告诉你需求分析、任务拆解、编码、测试分别进行到哪个阶段了，绝不藏着掖着。&lt;/p&gt;
&lt;h3 id=&quot;不怕掉线的安全感：-flow-restart&quot;&gt;&lt;a href=&quot;#不怕掉线的安全感：-flow-restart&quot; class=&quot;headerlink&quot; title=&quot;不怕掉线的安全感：/flow-restart&quot;&gt;&lt;/a&gt;不怕掉线的安全感：&lt;code&gt;/flow-restart&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;最让我安心的是这个功能。就算开发过程中电脑蓝屏了、网络断了，你都不用慌。回来后，只需对“老王”说一声：&lt;br&gt;&lt;code&gt;/flow-restart &amp;quot;REQ-100&lt;/code&gt;”&lt;br&gt;他就能立刻从上次中断的地方接着干，绝不会把做过的工作再做一遍，更不会忘记自己干到哪了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://resv2.craft.do/user/full/86216952-a1bc-6ff4-661b-46aa5fdbb1d4/doc/29BF2A0D-BD4D-4A0D-80A9-F6C63A2BF1CB/81392339-353C-49EC-B8E7-F718C93DEBFF_2/2cKAn5I1kSRcKyAvUdBKhOzdiIbXbQq8Zb1mxMbCeUwz/Image.png&quot; alt=&quot;Image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当我终于用&lt;code&gt;cc-devflow&lt;/code&gt;完整地跑通第一个需求，看着屏幕上自动弹出的GitHub Pull Request创建成功的通知时，我知道，我成功了。&lt;/p&gt;
&lt;h2 id=&quot;我的新角色：从“码农”到AI“产品经理”&quot;&gt;&lt;a href=&quot;#我的新角色：从“码农”到AI“产品经理”&quot; class=&quot;headerlink&quot; title=&quot;我的新角色：从“码农”到AI“产品经理”&quot;&gt;&lt;/a&gt;我的新角色：从“码农”到AI“产品经理”&lt;/h2&gt;&lt;p&gt;这次经历，彻底改变了我对未来工作的看法。我意识到，我的工作性质，可能永远地改变了。&lt;/p&gt;
&lt;p&gt;我不再是那个埋头写每一行代码的人了。&lt;/p&gt;
&lt;p&gt;我的价值，不再是“写得快不快”，而是“想得清不清楚”。我需要把需求定义得无比清晰，这样我的AI团队才能正确地理解和执行。&lt;/p&gt;
&lt;p&gt;我大部分的时间，开始花在设计和优化&lt;code&gt;.claude/agents/&lt;/code&gt;目录下的那些提示词（Prompt）和工作流程（SOP）上，就像在为我的AI员工们编写岗位说明书和培训手册。&lt;/p&gt;
&lt;p&gt;我成为了我自己的AI团队的“产品经理”和“流程架构师”。&lt;/p&gt;
&lt;p&gt;这并不可怕，相反，这很酷。它把我们从重复的、机械的体力劳动中解放出来，去做更具创造性和战略性的思考。你，准备好成为AI的管理者了吗？&lt;/p&gt;
&lt;h2 id=&quot;写在最后：这不是终点，而是我们共同的起点&quot;&gt;&lt;a href=&quot;#写在最后：这不是终点，而是我们共同的起点&quot; class=&quot;headerlink&quot; title=&quot;写在最后：这不是终点，而是我们共同的起点&quot;&gt;&lt;/a&gt;写在最后：这不是终点，而是我们共同的起点&lt;/h2&gt;&lt;p&gt;故事讲到这里，也该结尾了。&lt;/p&gt;
&lt;p&gt;我把&lt;code&gt;cc-devflow&lt;/code&gt;这个项目，连同我所有的思考和踩坑经验，都开源了。&lt;/p&gt;
&lt;p&gt;GitHub链接：&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL0RpbW9uOTQvY2MtZGV2Zmxvdw==&quot;&gt;https://github.com/Dimon94/cc-devflow&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;因为我踩过的坑，不希望你再踩一遍。这条探索之路，我一个人走很辛苦，但如果有一群志同道合的人一起走，或许能走得更快、更远。&lt;/p&gt;
&lt;p&gt;你可以通过下面这行简单的命令，立刻“认领”你自己的AI开发团队：&lt;br&gt;&lt;code&gt;npx tiged Dimon94/cc-devflow/.claude .claude&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果你对这个项目有任何想法，或者你也有自己的“踩坑故事”，都非常欢迎来GitHub的Issue里分享。&lt;code&gt;cc-devflow&lt;/code&gt;不是一个完美的终点，而是我们共同探索AI协作新范式的起点。&lt;/p&gt;
]]></content>
    
      <category term="AI研习录"/>
    
    
      <category term="AI"/>
    
  </entry>
  
  <entry>
    <title>三杯曼哈顿</title>
    <link href="https://dimon94.github.io/2024/12/23/%E4%B8%89%E6%9D%AF%E6%9B%BC%E5%93%88%E9%A1%BF/"/>
    <id>https://dimon94.github.io/2024/12/23/%E4%B8%89%E6%9D%AF%E6%9B%BC%E5%93%88%E9%A1%BF/</id>
    <published>2024-12-23T09:56:16.000Z</published>
    <updated>2025-09-22T09:18:12.569Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;&lt;img src=&quot;https://images.unsplash.com/photo-1735040736883-9e0bc7e6f1ba?q=80&amp;w=1974&amp;auto=format&amp;fit=crop&amp;ixlib=rb-4.0.3&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA==&quot; alt=&quot;曼哈顿&quot;&gt;&lt;/p&gt;
&lt;p&gt;那天晚上，我在深夜酒吧遇见了一个自称来自1984年的女人。&lt;br&gt;她穿着一件褪色的蓝色连衣裙，坐在吧台最角落的位置。&lt;br&gt;调酒师正在为她调制第三杯曼哈顿。&lt;br&gt;冰块在玻璃杯中碰撞，发出清脆的声响，像是某种暗号。&lt;/p&gt;
&lt;p&gt;“你相信平行宇宙吗？”她突然问我。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;没等我回答，她继续说：”在我的时空里，现在应该是凌晨3点17分。我的丈夫正在床上睡觉，而我在厨房里煮着咖啡。”&lt;br&gt;我看了看手表，现在正好是凌晨3点17分。&lt;br&gt;“真巧，”我说，”现在也是这个时间。”&lt;br&gt;她笑了，那种笑容让我想起了很久以前养过的一只猫。&lt;br&gt;那只猫在离开前的最后一个晚上，也是这样看着我。&lt;/p&gt;
&lt;p&gt;“没什么巧合，”她说，”时间是个圆。就像这个杯子的边缘。”&lt;br&gt;她用手指轻轻划过酒杯的边缘，发出一声几不可闻的嗡鸣。&lt;/p&gt;
&lt;p&gt;爵士乐还在继续。萨克斯风手似乎永远不会疲倦。&lt;br&gt;我注意到她左手无名指上有一道浅浅的戒指印，却没戴戒指。&lt;/p&gt;
&lt;p&gt;“你丈夫知道你在这里吗？”我问。&lt;br&gt;“知道啊，”她说，”他在1984年的床上等我回去。但我想他已经习惯了。毕竟我每个星期四都会来这里，坐在这个位置，点三杯曼哈顿。”&lt;br&gt;“为什么是三杯？”&lt;br&gt;“第一杯是为了记住，第二杯是为了忘记，第三杯是为了在记住和忘记之间找到平衡。”&lt;br&gt;她说这话时，目光穿过我，望向某个我看不见的远方。&lt;/p&gt;
&lt;p&gt;当她喝完第三杯酒，站起身准备离开时，我注意到她的影子却仍坐在吧台前。&lt;br&gt;她像是没有察觉，径直走向门口。&lt;br&gt;推开门的瞬间，一阵冷风吹进来，带着雨水的气息。&lt;/p&gt;
&lt;p&gt;我转头看向她留下的座位。&lt;br&gt;她的影子已经消失了，只剩下三个空酒杯。&lt;br&gt;最后一个杯子里，冰块还在缓慢融化。&lt;/p&gt;
&lt;p&gt;调酒师走过来收拾杯子，我问他：”那个女人经常来吗？”&lt;br&gt;“什么女人？”他一边擦拭吧台一边问。&lt;br&gt;我指了指角落的位置，那里现在空无一人，连杯子都不见了。&lt;br&gt;只有一滴水珠在木质台面上，映着头顶的灯光，闪烁着奇异的光芒。&lt;br&gt;外面开始下雨了。雨滴打在窗户上的声音，和1984年的某个夜晚，或许完全一样。&lt;/p&gt;
]]></content>
    
      <category term="短篇小说"/>
    
    
      <category term="AI短篇小说"/>
    
      <category term="AI生成"/>
    
  </entry>
  
  <entry>
    <title>雪山来信</title>
    <link href="https://dimon94.github.io/2024/12/18/%E9%9B%AA%E5%B1%B1%E6%9D%A5%E4%BF%A1/"/>
    <id>https://dimon94.github.io/2024/12/18/%E9%9B%AA%E5%B1%B1%E6%9D%A5%E4%BF%A1/</id>
    <published>2024-12-18T09:25:16.000Z</published>
    <updated>2025-09-22T09:18:12.571Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;&lt;img src=&quot;https://images.unsplash.com/photo-1735776327649-eeb6b6ed8829?q=80&amp;w=1974&amp;auto=format&amp;fit=crop&amp;ixlib=rb-4.0.3&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA==&quot; alt=&quot;雪山来信&quot;&gt;&lt;/p&gt;
&lt;p&gt;远方的雪山在夕阳下泛着金红色的光芒，我坐在草原上，听着耳机里流淌的钢琴曲。&lt;br&gt;这是第三十七次收到她的来信，信封上依然是那个熟悉的地址：&lt;br&gt;喜马拉雅山脉，海拔6500米，第四号营地。&lt;/p&gt;
&lt;p&gt;没有人知道她为什么会在那里，就像没有人知道为什么高山上会有邮局。&lt;br&gt;但这些信，确实每个月都会准时出现在我的邮箱里，带着雪山的气息和一丝若有若无的檀香。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;第一封信到来的时候，我刚刚结束一段失败的感情。&lt;br&gt;那时我以为，这或许是某个朋友的恶作剧，又或是命运开的一个玩笑。&lt;br&gt;信中只有简单的几句话：”在这里，星星很近，近到伸手就能触碰。有时我会想，如果用力跳起来，是不是就能抓住一颗带回给你。”&lt;/p&gt;
&lt;p&gt;随后的信件里，她告诉我关于雪山的故事。&lt;br&gt;关于凌晨四点出发时踩在积雪上的咯吱声，关于风在氧气面罩上结出的冰晶，关于在极限海拔时每一次呼吸都像是在和死神搏斗。&lt;br&gt;她从不提及为什么去那里，也不说何时会回来。&lt;/p&gt;
&lt;p&gt;“今天看到一只雪豹，”她在第十二封信中写道，”它就站在离我二十米的地方，金色的眼睛里倒映着整个雪山。那一刻我明白了，有些美好，注定是孤独的。”&lt;/p&gt;
&lt;p&gt;我试图回信，但所有的信件都被退回，邮戳上盖着”地址不存在”的印章。&lt;br&gt;第三十七封信里，她写道：”明天将是最后一次攀登。在这里，时间是静止的，而我却在不断地坠落。也许每个人心中都有一座需要攀登的雪山，我的雪山就是你。再见了，我亲爱的陌生人。”&lt;br&gt;这是最后一封信。此后再也没有信件从那个不存在的地址寄来。&lt;/p&gt;
&lt;p&gt;有时我会想，也许她真的存在，也许她真的在那座雪山上。&lt;br&gt;又或者，她只是我在某个寂寞时刻虚构出来的影子，是我内心深处对于未知的渴望和恐惧的投射。&lt;br&gt;但这些都不重要了。重要的是，在那些信件里，在那座可能并不存在的雪山上，有人用她的方式，完成了一场关于爱与孤独的朝圣。&lt;/p&gt;
&lt;p&gt;夕阳最后的余晖也消失了，草原上升起了薄雾。&lt;br&gt;我摘下耳机，远处的雪山在暮色中若隐若现，像是一个模糊的梦境。&lt;br&gt;耳边似乎传来了雪落的声音，那么轻，那么远，仿佛是从另一个世界飘来的絮语。&lt;/p&gt;
&lt;p&gt;《给雪山上的你》&lt;/p&gt;
&lt;p&gt;我站在草原尽头&lt;br&gt;仰望那座金色的雪山&lt;br&gt;风掠过耳际&lt;br&gt;带来你遥远的呼吸声&lt;/p&gt;
&lt;p&gt;星星藏在你的眼睛里&lt;br&gt;每一次眨眼&lt;br&gt;都有流星坠落&lt;br&gt;砸在我心底最柔软的地方&lt;/p&gt;
&lt;p&gt;你说孤独是一种信仰&lt;br&gt;而我的信仰&lt;br&gt;是在每个黄昏&lt;br&gt;数着你寄来的信封&lt;/p&gt;
&lt;p&gt;雪豹走过的路径上&lt;br&gt;落下你的脚印&lt;br&gt;我收集每一片雪花&lt;br&gt;都是你未说完的话语&lt;/p&gt;
&lt;p&gt;六千五百米的距离&lt;br&gt;不及心与心之间&lt;br&gt;那道无法跨越的沟壑&lt;br&gt;深不见底&lt;/p&gt;
&lt;p&gt;但我依然固执地相信&lt;br&gt;在某个清晨&lt;br&gt;推开窗，你会像初雪一样&lt;br&gt;悄无声息地落在我的掌心&lt;/p&gt;
&lt;p&gt;即使知道&lt;br&gt;你可能只是&lt;br&gt;我在寂寞时刻&lt;br&gt;编织的一场大梦&lt;/p&gt;
]]></content>
    
      <category term="短篇小说"/>
    
    
      <category term="AI短篇小说"/>
    
      <category term="AI生成"/>
    
  </entry>
  
  <entry>
    <title>RxAndroid新型异步API（译）</title>
    <link href="https://dimon94.github.io/2018/09/02/RxAndroid%E6%96%B0%E5%9E%8B%E5%BC%82%E6%AD%A5API%EF%BC%88%E8%AF%91%EF%BC%89/"/>
    <id>https://dimon94.github.io/2018/09/02/RxAndroid%E6%96%B0%E5%9E%8B%E5%BC%82%E6%AD%A5API%EF%BC%88%E8%AF%91%EF%BC%89/</id>
    <published>2018-09-02T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.569Z</updated>
    
    <content type="html"><![CDATA[&lt;blockquote&gt;
&lt;p&gt;原文链接：RxAndroid’s New Async API&lt;/p&gt;
&lt;p&gt;原文作者：Zac Sweers&lt;/p&gt;
&lt;p&gt;译文出自：Dimon’s Program Basement&lt;/p&gt;
&lt;p&gt;译者：Dimon&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1000/1*VMZbrxTyr3ga00M0mjNwyw.png&quot; alt=&quot;RxAndroid&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;RxAndroid-2-1-0有一个新的API：&quot;&gt;&lt;a href=&quot;#RxAndroid-2-1-0有一个新的API：&quot; class=&quot;headerlink&quot; title=&quot;RxAndroid 2.1.0有一个新的API：&quot;&gt;&lt;/a&gt;RxAndroid 2.1.0有一个新的API：&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;AndroidSchedulers#from(Looper looper, &lt;strong&gt;boolean async&lt;/strong&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这新的&lt;code&gt;async&lt;/code&gt;参数将影响Android APIs 16 及其以上版本，如果你的APP高度依赖RxJava+RxAndroid，将这个参数设置为&lt;code&gt;true&lt;/code&gt;将能够&lt;strong&gt;显著提升 UI 性能&lt;/strong&gt;表现。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;由于RxAndroid的主要版本与RxJava绑定，我们不希望在次要版本中默默地引入重要的行为更改，所以这个API默认不启用。&lt;/p&gt;
&lt;p&gt;要安装它，你可以使用&lt;code&gt;RxAndroidPlugins&lt;/code&gt;设置此API自定义的&lt;code&gt;scheduler&lt;/code&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;EDIT：由于类加载问题，以下代码在&lt;code&gt;setInitMainThreadSchedulerHandler&lt;/code&gt;上有一些错误。为了避免初始化默认值，你应该在传入的 lambda&amp;#x2F;callable **中(inside)**调用&lt;code&gt;AndroidSchedulers.from(...)&lt;/code&gt;，而不是在之前就调用；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;Kotlin：&quot;&gt;&lt;a href=&quot;#Kotlin：&quot; class=&quot;headerlink&quot; title=&quot;Kotlin：&quot;&gt;&lt;/a&gt;Kotlin：&lt;/h4&gt;&lt;figure class=&quot;highlight kotlin&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Before&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; asyncMainThreadScheduler = AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setInitMainThreadSchedulerHandler &amp;#123; asyncMainThreadScheduler &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Or if the default scheduler is already initialiazed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setMainThreadSchedulerHandler &amp;#123; asyncMainThreadScheduler &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Correct usage&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setInitMainThreadSchedulerHandler &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Or if the default scheduler is already initialiazed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setMainThreadSchedulerHandler &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h4 id=&quot;Java：&quot;&gt;&lt;a href=&quot;#Java：&quot; class=&quot;headerlink&quot; title=&quot;Java：&quot;&gt;&lt;/a&gt;Java：&lt;/h4&gt;&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Before&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;Scheduler&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;asyncMainThreadScheduler&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -&amp;gt; asyncMainThreadScheduler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Or if the default scheduler is already initialiazed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -&amp;gt; asyncMainThreadScheduler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Correct usage&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setInitMainThreadSchedulerHandler(callable -&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;Scheduler&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;asyncMainThreadScheduler&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; asyncMainThreadScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;//Or if the default scheduler is already initialiazed&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;Scheduler&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;asyncMainThreadScheduler&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; AndroidSchedulers.from(Looper.getMainLooper(), &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; asyncMainThreadScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;正文&quot;&gt;&lt;a href=&quot;#正文&quot; class=&quot;headerlink&quot; title=&quot;正文&quot;&gt;&lt;/a&gt;正文&lt;/h3&gt;&lt;p&gt;这是一个很长的时间。RxAndroid中使用主线程历来都是使用&lt;code&gt;Handler#post()&lt;/code&gt;去调度新的&lt;code&gt;Message&lt;/code&gt;们。这通常是需要付出代价：默认情况下这个的遵循VSYNC锁定并且将导致&lt;strong&gt;等待&lt;/strong&gt;直到下一帧运行。对于通过&lt;code&gt;post()&lt;/code&gt;的每一个发射，这都将是一个高达16ms的延迟。如果你已经在主线程上，这更是会加剧这种情况的发生（Ray Ryan曾经在一个出色的演讲上讲到过这个问题)。&lt;/p&gt;
&lt;p&gt;因此，在2015年，在Jake Wharton的RxBinding项目中围绕”快速路径(fastpath)”展开了讨论：主线程调度程序如果已经在主线程上并且避免VSYNC成本，则可以立即运行工作。之后这个讨论转移到RxAndroid的一个提案上，并且得到了很好的社区反馈，但是由于担心可能通过直接运行事件和冒着死锁的风险来竞争系统的事件，导致从未达成过共识；所以它受到了抨击，但对于使用者们仍旧是一个摩擦点，并且在RxAndroid2.x中提交了几个issues；&lt;/p&gt;
&lt;p&gt;很快到了2017年年初，在Uber里我们决定尝试在内部进行re-hash，并且发现它_基本上_是稳定的。我们确实时而会看到死锁，但它们很难被发现，并且看起来很罕见，值得我们换取性能上的提升。上线一年后，我们决定尝试上游化我们的实现，并且重新开启讨论。这一次，Android框架工程师（Adam Powell）看到了这个讨论，并且指出我们使用&lt;code&gt;Message&lt;/code&gt;和&lt;code&gt;Handler&lt;/code&gt;的异步API。这个API允许消息绕过VSYNC锁定，同时仍让框架在其looper中安全地处理所有调度，这正是我们一直想要的！&lt;/p&gt;
&lt;h3 id=&quot;连线API&quot;&gt;&lt;a href=&quot;#连线API&quot; class=&quot;headerlink&quot; title=&quot;连线API&quot;&gt;&lt;/a&gt;连线API&lt;/h3&gt;&lt;p&gt;虽然异步API自API 16以来就已存在，但它们已经通过@hide隐藏在SDK中。在API22中，Message#setAsynchronous()方法变成了public。在API28中，有一个新的&lt;code&gt;Handler.createAsync（）&lt;/code&gt;工厂API，它默认将它处理的所有消息设置为异步。由于这些API为操作系统中一些最关键的部分提供了动力，因此它们不太可能被更改，并且应该可以安全访问。这里有点乐趣。&lt;/p&gt;
&lt;h4 id=&quot;API-22&quot;&gt;&lt;a href=&quot;#API-22&quot; class=&quot;headerlink&quot; title=&quot;API 22+&quot;&gt;&lt;/a&gt;API 22+&lt;/h4&gt;&lt;p&gt;使用上述公共&lt;code&gt;setAsynchronous（）&lt;/code&gt;方法。&lt;/p&gt;
&lt;h4 id=&quot;API-16-21&quot;&gt;&lt;a href=&quot;#API-16-21&quot; class=&quot;headerlink&quot; title=&quot;API[16-21]&quot;&gt;&lt;/a&gt;API[16-21]&lt;/h4&gt;&lt;p&gt;仍然使用&lt;code&gt;setAsynchronous（）&lt;/code&gt;，但我们抑制了lint错误，表明它只有22+。为了避免删除&amp;#x2F;更改内部API的任何（不太可能的）OEM情况，我们 try&amp;#x2F;catch &lt;code&gt;from（）&lt;/code&gt;调度程序工厂中的快速&lt;code&gt;Message＃setAsynchronous（）&lt;/code&gt;方法调用，以确保它在运行时存在，捕获&lt;code&gt;NoSuchMethodError&lt;/code&gt;，如果它丢失并且优雅地回到标准的非异步消息传递。它不是反射，因为我们知道这将在运行时，因为该方法确实存在于运行时。&lt;/p&gt;
&lt;h4 id=&quot;API-16&quot;&gt;&lt;a href=&quot;#API-16&quot; class=&quot;headerlink&quot; title=&quot;API &amp;lt; 16&quot;&gt;&lt;/a&gt;API &amp;lt; 16&lt;/h4&gt;&lt;p&gt;没有行为更改，因为异步API不存在，所以使用标准的非异步消息传递。&lt;/p&gt;
&lt;p&gt;就是这些！我希望在使用主线程调度程序时，这可以让开发人员更加安心。请试一试并报告任何问题。 &lt;/p&gt;
]]></content>
    
      <category term="ReactiveX"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="RxAndroid"/>
    
  </entry>
  
  <entry>
    <title>让你的Daggers保持锋利⚔️（译）</title>
    <link href="https://dimon94.github.io/2018/08/26/%E8%AE%A9%E4%BD%A0%E7%9A%84Daggers%E4%BF%9D%E6%8C%81%E9%94%8B%E5%88%A9%E2%9A%94%EF%B8%8F%EF%BC%88%E8%AF%91%EF%BC%89/"/>
    <id>https://dimon94.github.io/2018/08/26/%E8%AE%A9%E4%BD%A0%E7%9A%84Daggers%E4%BF%9D%E6%8C%81%E9%94%8B%E5%88%A9%E2%9A%94%EF%B8%8F%EF%BC%88%E8%AF%91%EF%BC%89/</id>
    <published>2018-08-26T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.571Z</updated>
    
    <content type="html"><![CDATA[&lt;blockquote&gt;
&lt;p&gt;原文链接：Keeping the Daggers Sharp ⚔️&lt;/p&gt;
&lt;p&gt;原文作者：Py ⚔&lt;/p&gt;
&lt;p&gt;译文出自：Dimon’s Program Basement&lt;/p&gt;
&lt;p&gt;译者：Dimon&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dagger2是一个非常好的依赖注入库，但是其&lt;code&gt;锋利的边缘&lt;/code&gt;处理起来也是比较棘手的。这就让我们来看看Square公司通过遵循哪些最佳事件来防止工程师们&lt;code&gt;伤害自己&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1800/1*5fsp9x_JcuJijFVotF1NQA.png&quot; alt=&quot;Dagger2&quot;&gt;&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;与直接注入成员变量相比推荐通过构造函数注入&quot;&gt;&lt;a href=&quot;#与直接注入成员变量相比推荐通过构造函数注入&quot; class=&quot;headerlink&quot; title=&quot;与直接注入成员变量相比推荐通过构造函数注入&quot;&gt;&lt;/a&gt;与直接注入成员变量相比推荐通过构造函数注入&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;直接注入成员变量要求为非&lt;code&gt;final&lt;/code&gt;字段且非&lt;code&gt;private&lt;/code&gt;字段。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// BAD&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CardConverter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; PublicKeyManager publicKeyManager;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;CardConverter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;忘记加上&lt;code&gt;@Inject&lt;/code&gt;会导致&lt;code&gt;NullPointerException&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// BAD&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CardConverter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; PublicKeyManager publicKeyManager;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Analytics analytics; &lt;span class=&quot;comment&quot;&gt;// Oops, forgot to @Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;CardConverter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;构造函数注入是更好的方式，因为它允许&lt;code&gt;不可变&lt;/code&gt;并且保证&lt;code&gt;线程安全&lt;/code&gt;的对象没有部分构造的状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// GOOD&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CardConverter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; PublicKeyManager publicKeyManager;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;CardConverter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(PublicKeyManager publicKeyManager)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.publicKeyManager = publicKeyManager;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;Kotlin消除了构造函数的注入样板：&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight kotlin&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CardConverter&lt;/span&gt; &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;constructor&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; publicKeyManager: PublicKeyManager)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;我们依旧对系统构造的对象使用成员变量注入，比如Android Activities:&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MainActivity&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Activity&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Component&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(MainActivity activity)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; ToastFactory toastFactory;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onCreate(savedInstanceState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; SquareApplication.component(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    component.inject(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;单例应该非常罕见&quot;&gt;&lt;a href=&quot;#单例应该非常罕见&quot; class=&quot;headerlink&quot; title=&quot;单例应该非常罕见&quot;&gt;&lt;/a&gt;单例应该非常罕见&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;当我们需要&lt;strong&gt;集中访问可变状态&lt;/strong&gt;时，单例很有用。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// GOOD&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;BadgeCounter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Observable&amp;lt;Integer&amp;gt; badgeCount;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;BadgeCounter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(...)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;     badgeCount = ...&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;如果一个对象没有可变状态时，则不需要是单例。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// BAD, should not be a singleton!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;RealToastFactory&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastFactory&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Application context;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;RealToastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Application context)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.context = context;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Toast &lt;span class=&quot;title function_&quot;&gt;makeText&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; resId, &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; duration)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; Toast.makeText(context, resId, duration);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;在极少数情况下，我们使用作用域来&lt;code&gt;缓存&lt;/code&gt;创建成本高昂的实例或者重复创建和丢弃的实例。（译者注：这也是为了让你更少的通过Dagger实现单例，因为实现太简单，可能导致滥用单例。）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;与＠Provides相比更推荐＠Inject&quot;&gt;&lt;a href=&quot;#与＠Provides相比更推荐＠Inject&quot; class=&quot;headerlink&quot; title=&quot;与＠Provides相比更推荐＠Inject&quot;&gt;&lt;/a&gt;与&lt;code&gt;＠Provides&lt;/code&gt;相比更推荐&lt;code&gt;＠Inject&lt;/code&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@Provides&lt;/code&gt;方法不应该复制构造函数样板。&lt;/li&gt;
&lt;li&gt;当所有耦合问题都集中在一个地方的时候，代码更具可读性。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// BAD, remove this binding and add @Inject to RealToastFactory&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Provides&lt;/span&gt; RealToastFactory &lt;span class=&quot;title function_&quot;&gt;realToastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Application context)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;RealToastFactory&lt;/span&gt;(context);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;这对于&lt;code&gt;单例&lt;/code&gt;尤为重要，这是你在阅读这个类的时候需要了解的关键实现细节（&lt;strong&gt;implementation detail&lt;/strong&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// GOOD, I have all the details I need in one place.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;BadgeCounter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;BadgeCounter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(...)&lt;/span&gt; &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;推荐static修饰-Provides方法&quot;&gt;&lt;a href=&quot;#推荐static修饰-Provides方法&quot; class=&quot;headerlink&quot; title=&quot;推荐static修饰@Provides方法&quot;&gt;&lt;/a&gt;推荐&lt;code&gt;static&lt;/code&gt;修饰&lt;code&gt;@Provides&lt;/code&gt;方法&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Dagger&lt;code&gt;@Provides&lt;/code&gt;方法能够&lt;code&gt;static&lt;/code&gt;修饰。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Provides&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(RealToastFactory factory)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; factory;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;生成的代码可以直接调用该方法，而不必创建Module实例。该方法调用可以由编译器内联。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Generated&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;DaggerAppComponent&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppComponent&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; ToastModule.toastFactory(realToastFactoryProvider.get())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;一个静态方法不会有太大变化，但所有绑定都是静态的，会导致相当大的性能提升。&lt;/li&gt;
&lt;li&gt;试你的模块变得抽象并且如果其中一个&lt;code&gt;@Provides&lt;/code&gt;方法不是静态的，那么在编译时&lt;code&gt;Dagger&lt;/code&gt;会失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Provides&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(RealToastFactory factory)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; factory;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;与＠Provides相比更推荐＠Binds&quot;&gt;&lt;a href=&quot;#与＠Provides相比更推荐＠Binds&quot; class=&quot;headerlink&quot; title=&quot;与＠Provides相比更推荐＠Binds&quot;&gt;&lt;/a&gt;与&lt;code&gt;＠Provides&lt;/code&gt;相比更推荐&lt;code&gt;＠Binds&lt;/code&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;当您将一种类型映射到另一种类型时，&lt;code&gt;@Binds&lt;/code&gt;会替换&lt;code&gt;@Provides&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Binds&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(RealToastFactory factory)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;该方法必须是抽象的。它永远不会被调用；生成的代码将知道直接使用该实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Generated&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;DaggerAppComponent&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppComponent&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;DaggerAppComponent&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.toastFactoryProvider = (Provider) realToastFactoryProvider;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; toastFactoryProvider.get();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;避免在接口绑定上使用-Singleton&quot;&gt;&lt;a href=&quot;#避免在接口绑定上使用-Singleton&quot; class=&quot;headerlink&quot; title=&quot;避免在接口绑定上使用@Singleton&quot;&gt;&lt;/a&gt;避免在接口绑定上使用&lt;code&gt;@Singleton&lt;/code&gt;&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;Statefulness is an implementation detail&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;只有实现才知道他们是否需要确保集中访问的可变状态。&lt;/li&gt;
&lt;li&gt;将实现绑定到接口时，不应该有任何作用域注释。&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ToastModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// BAD, remove @Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Binds&lt;/span&gt; &lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; ToastFactory &lt;span class=&quot;title function_&quot;&gt;toastFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(RealToastFactory factory)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;启用error-prone&quot;&gt;&lt;a href=&quot;#启用error-prone&quot; class=&quot;headerlink&quot; title=&quot;启用error-prone&quot;&gt;&lt;/a&gt;启用error-prone&lt;/h3&gt;&lt;p&gt;几个Square团队正在使用它将常见的Dagger错误检查出来。&lt;/p&gt;
&lt;h3 id=&quot;结论&quot;&gt;&lt;a href=&quot;#结论&quot; class=&quot;headerlink&quot; title=&quot;结论&quot;&gt;&lt;/a&gt;结论&lt;/h3&gt;&lt;p&gt;这些指导原则适用于我们：小型异构团队在大型共享Android代码库上工作。由于您的背景可能不同，因此您应该应用对您的团队最有意义的内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;轮到你了！您遵循哪些良好做法来保持Dagger代码的清晰度？&lt;/strong&gt; &lt;/p&gt;
]]></content>
    
      <category term="Dependency Injection"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="Dagger2"/>
    
      <category term="Dependency Injection"/>
    
      <category term="Best Practices"/>
    
  </entry>
  
  <entry>
    <title>LinkedIn的高效代码检视建议(译)</title>
    <link href="https://dimon94.github.io/2018/06/10/LinkedIn%E7%9A%84%E9%AB%98%E6%95%88%E4%BB%A3%E7%A0%81%E6%A3%80%E8%A7%86%E5%BB%BA%E8%AE%AE(%E8%AF%91)/"/>
    <id>https://dimon94.github.io/2018/06/10/LinkedIn%E7%9A%84%E9%AB%98%E6%95%88%E4%BB%A3%E7%A0%81%E6%A3%80%E8%A7%86%E5%BB%BA%E8%AE%AE(%E8%AF%91)/</id>
    <published>2018-06-10T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.568Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文：LinkedIn’s Tips for Highly Effective Code Review&lt;br&gt;原著作者：Szczepan Faber&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;最近LinkedIn里程碑式地完成了他们的第100万次代码检视，这篇文章是LinkedIn社交网络服务工具的负责人Szczepan Faber分享的一些经验与教训。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;阅读和检查代码是每个工程师每天都在做的事情，然而正式的代码检视流程会有点不一样（它要求在代码上线之前），每个代码的更改都要由其他团队成员进行正式检视。在LinkedIn上，自2011年以来，代码检视一直是我们开发流程的强制性部分。我们要求代码检视的目标是尽可能顺利地扩展我们快速发展的工程团队。良好的代码检视与有意义的有用评论确实可以帮助提升整个工程组织。在LinkedIn，这些检视已成为质量保证和知识共享的重要组成部分。拥抱代码检视已经在几个关键方面优化了我们整个工程文化。&lt;/p&gt;
&lt;p&gt;实施全公司代码检视的最大好处之一是增加了我们开发工作流程的标准化。LinkedIn上的每个团队都使用相同的工具和流程来进行代码检视，这意味着任何人都可以帮助审核或为其他团队的项目提供代码。这消除了”我可以修复代码中的错误，但是我将如何构建该代码并提交修复？”这样可以帮助增加工程组织中不同团队之间的协作。&lt;/p&gt;
&lt;p&gt;通过将代码检视制定为强制性流程，我们还帮助培养了公司健康的反馈文化：工程师们对所有工作领域都给予反馈并接受反馈，而不仅仅是编码，因为它已成为工作的常规组成部分。我们的工程师不是将代码检视视为关键或否定，而是将代码审查和代码评论作为专业发展的机会。事实上，高质量的代码审查是LinkedIn推广流程的重要组成部分，因为它们提供了工程技术的客观证据。&lt;/p&gt;
&lt;p&gt;多年来，我们磨练了几个最佳实践和技巧，以便如何提供真正的优秀代码检视。以下是一些以问题形式提供的指南，我们建议您通过这些指南来帮助确保审阅者和被审阅者都从代码检视中获得最大价值。&lt;/p&gt;
&lt;h3 id=&quot;我了解「为什么」吗？&quot;&gt;&lt;a href=&quot;#我了解「为什么」吗？&quot; class=&quot;headerlink&quot; title=&quot;我了解「为什么」吗？&quot;&gt;&lt;/a&gt;我了解「为什么」吗？&lt;/h3&gt;&lt;p&gt;为了尽可能提供最佳评审和帮助您的团队进行扩展，每一次代码更改提交都应包含一个设计概述，以简要说明变更背后的动机。当需要从代码更改本身推断出理由时，提供高质量的代码检视非常困难。提出问题并期望提交者在尝试代码检视之前解释他们的动机是公平的。这也鼓励提交者在他们的提交消息中有一个解释，提高代码文档的质量。&lt;/p&gt;
&lt;h3 id=&quot;我是否给予积极的反馈？&quot;&gt;&lt;a href=&quot;#我是否给予积极的反馈？&quot; class=&quot;headerlink&quot; title=&quot;我是否给予积极的反馈？&quot;&gt;&lt;/a&gt;我是否给予积极的反馈？&lt;/h3&gt;&lt;p&gt;在一个充满聪明人的组织中，干净的代码和整洁的测试覆盖率可以被认为是理所当然的。因此，代码检视反馈倾向于只关注代码中发现的问题与代码本身。这是非常不幸的，因为大多数人需要积极的反馈来感受参与和动力「工程师也不例外」。 当审阅者在代码中看到好东西时，他们应该将其发出并给出正面的反馈。 这有助于改善团队动态，而且这种积极的反馈往往具有传染性。正如所有的代码检视建议（下面的更多内容）一样，任何积极的反馈都应该是具体的，解释为什么那些特定的代码是写得很好的。&lt;/p&gt;
&lt;h3 id=&quot;我的代码检视评论阐述得好吗？&quot;&gt;&lt;a href=&quot;#我的代码检视评论阐述得好吗？&quot; class=&quot;headerlink&quot; title=&quot;我的代码检视评论阐述得好吗？&quot;&gt;&lt;/a&gt;我的代码检视评论阐述得好吗？&lt;/h3&gt;&lt;p&gt;无论反馈是正面还是负面，任何代码检视评论都应该是不言自明的。对于收到解释不好的代码检视评论的工程师而言，对于评审者来说看起来很明显的东西可能并不清楚。如果有疑问，最好是过度解释，而不是提供简洁的反馈，这会产生更多的问题，并需要更多的来回通信。建议可以像”减少重复”，”提高覆盖率”或”使代码更容易测试”一样简单。除了使审阅者的评论更加清晰之外，这些类型的评论还有助于加强团队渴望满足的设计原则。&lt;/p&gt;
&lt;h3 id=&quot;我表扬了提交者的努力吗？&quot;&gt;&lt;a href=&quot;#我表扬了提交者的努力吗？&quot; class=&quot;headerlink&quot; title=&quot;我表扬了提交者的努力吗？&quot;&gt;&lt;/a&gt;我表扬了提交者的努力吗？&lt;/h3&gt;&lt;p&gt;无论结果如何，艰苦的工作总是需要被表扬 - 这培养了强有力的，充满活力的团队。一些代码如果改不到最高质量的话，就需要重新设计。 在这些情况下，即使他们的代码的确需要重新设计，仍然承认作者对变更所做的努力是很重要的。表现欣赏的最佳方式是通过提供高质量的反馈并提供合适的解释，承认好的想法（每次提交代码总是有好的东西！）并使用”谢谢”来投入您的代码检视。&lt;/p&gt;
&lt;h3 id=&quot;这篇检视评论对我有用吗？&quot;&gt;&lt;a href=&quot;#这篇检视评论对我有用吗？&quot; class=&quot;headerlink&quot; title=&quot;这篇检视评论对我有用吗？&quot;&gt;&lt;/a&gt;这篇检视评论对我有用吗？&lt;/h3&gt;&lt;p&gt;提出这个问题是验证代码检视评论是否必要的简单而强大的方法。在一天结束时，工程师应该将代码评论视为有用的开发工具，而不是不重要的繁忙工作的来源。如果您不认为特定的检视评论对您有用，请将其删除。无用的代码检视注释的典型例子是与代码格式相关的代码。代码样式和格式应该由自动化工具验证，而不是由工程师来验证。&lt;/p&gt;
&lt;h3 id=&quot;只要”测试完成”就足够了吗？&quot;&gt;&lt;a href=&quot;#只要”测试完成”就足够了吗？&quot; class=&quot;headerlink&quot; title=&quot;只要”测试完成”就足够了吗？&quot;&gt;&lt;/a&gt;只要”测试完成”就足够了吗？&lt;/h3&gt;&lt;p&gt;在LinkedIn上，每个代码更改提交都有一个必须填写的强制性”已完成测试”部分。 在开源世界中，使用GitHub作为示例，工程师可以在请求描述中提交”测试完成”信息。”测试完成”应该取决于变化的严重程度和当前的测试覆盖水平。如果更改包含新的或更改的条件复杂度，则期望在单元测试中涵盖它是公平的。如果集成测试覆盖率不足，某些更改可能需要运行手动测试。 在这些情况下，”完成测试”应包括有关测试场景和输出的信息。当更换改变程序的输出时，将新输出包含在”测试完成”部分非常有用。&lt;/p&gt;
&lt;h3 id=&quot;我在我的检视中过于迂腐吗？&quot;&gt;&lt;a href=&quot;#我在我的检视中过于迂腐吗？&quot; class=&quot;headerlink&quot; title=&quot;我在我的检视中过于迂腐吗？&quot;&gt;&lt;/a&gt;我在我的检视中过于迂腐吗？&lt;/h3&gt;&lt;p&gt;一些代码检视有很多评论，所以导致重要的问题（真正需要修正的）在重要的建议中丢失了。 对于某个团队的详细信息而言，评论太多可能会减慢审阅周期，并且会导致审阅者和被审阅者都产生摩擦。 具有清晰的审查预期，示例评审以及积极的邀请审阅文化是避免漫长而耗尽审阅周期的好方法。&lt;/p&gt;
&lt;p&gt;总之，通过正式的代码检视流程有助于提高代码质量，团队学习和知识共享。当团队中的每位工程师都意识到两件重要的事情时，工作质量会提高：其他人会阅读我的代码，我必须处理我收到的任何评论意见，所以我应该尝试让我的代码最好是好的，能够一次把事情做好是最好的。当代码检视成为日常习惯时，团队每天都会实践并提供反馈。 这是增长和改善的关键。&lt;/p&gt;
&lt;p&gt;在LinkedIn，我们从过去的一百万次代码检视中学到了很多东西，并且我们渴望从下一个百万人中学到更多。代码检视中付出的努力越多，团队获得优质代码检视的能力越强，检视的代码质量越高，并且所生成产品的质量越高。 高质量的代码检视具有传染性！ &lt;/p&gt;
]]></content>
    
      <category term="团队建设"/>
    
    
      <category term="译文"/>
    
      <category term="CodeReview"/>
    
  </entry>
  
  <entry>
    <title>如何在Android中执行TDD？第4部分 - 使用Espresso进行UI测试(译)</title>
    <link href="https://dimon94.github.io/2018/06/10/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%E7%AC%AC4%E9%83%A8%E5%88%86%20-%20%E4%BD%BF%E7%94%A8Espresso%E8%BF%9B%E8%A1%8CUI%E6%B5%8B%E8%AF%95(%E8%AF%91)/"/>
    <id>https://dimon94.github.io/2018/06/10/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%E7%AC%AC4%E9%83%A8%E5%88%86%20-%20%E4%BD%BF%E7%94%A8Espresso%E8%BF%9B%E8%A1%8CUI%E6%B5%8B%E8%AF%95(%E8%AF%91)/</id>
    <published>2018-06-10T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.570Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文&lt;/p&gt;
&lt;p&gt;在这篇文章中，我们将介绍什么是Espresso，Espresso提供了什么API，如何编写UI测试。&lt;/p&gt;
&lt;h3 id=&quot;什么是Espresso&quot;&gt;&lt;a href=&quot;#什么是Espresso&quot; class=&quot;headerlink&quot; title=&quot;什么是Espresso ?&quot;&gt;&lt;/a&gt;&lt;strong&gt;什么是Espresso ?&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Espresso正在为谷歌测试用于UI测试的框架。 它为单一应用程序中的UI测试提供了API。 用户界面测试可确保用户交互不良或遇到意外行为。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我知道你现在习惯于为你的代码编写测试（至少这是我对你的&lt;strong&gt;期望）。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;Espresso主要的三个类&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ViewMatcher&lt;/strong&gt;：用于使用onView（）在UI上查找视图&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ViewActions&lt;/strong&gt;：用户使用ViewInteraction.perform（）对UI元素执行操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ViewAssertion&lt;/strong&gt;：用于使用ViewInteraction.check（）来断言视图&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;onView(withId(R.id.my_view))      &lt;span class=&quot;comment&quot;&gt;// withId(R.id.my_view) is a ViewMatcher&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .perform(click())             &lt;span class=&quot;comment&quot;&gt;// click() is a ViewAction&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .check(matches(isDisplayed())); &lt;span class=&quot;comment&quot;&gt;// matches(isDisplayed()) is a ViewAssertion&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;在LoginActivity上按SHIFT + CTRL + T并在androidTest文件夹中为UI测试创建测试类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在类名上创建@RunWith（AndroidJUnit4.class）之后&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*nSDNz81yNzMoZv9ATHni6Q.png&quot; alt=&quot;创建测试类&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在我们用@Test注解编写测试&lt;strong&gt;checkUserNameEditTextIsDisplayed（）&lt;/strong&gt;。 我们还需要ActivityTestRule。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*TXmipgWmUgbRjSKIua5qSA.png&quot; alt=&quot;编写测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;在&lt;strong&gt;checkUserNameEditTextIsDisplayed（）&lt;strong&gt;中我们检查的是用户名&lt;/strong&gt;EditText&lt;/strong&gt;显示在UI上。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Espresso 的方法是自我解释的。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;运行它将通过的测试（对于运行测试，我们需要仿真器或实际设备，因为这是UI测试）。&lt;/p&gt;
&lt;p&gt;现在尝试使用户编辑文本的可见性消失，并再次运行测试失败。&lt;/p&gt;
&lt;p&gt;现在我们测试点击空白字段的登录按钮，并检查是否显示错误消息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*dimJrtNfxgNzRWqqY6dHmg.png&quot; alt=&quot;测试空白字段&quot;&gt;&lt;/p&gt;
&lt;p&gt;运行测试正在通过。&lt;/p&gt;
&lt;p&gt;接下来我们将为登录成功编写测试。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*7utZHMwuzuac8WaoDLr0jA.png&quot; alt=&quot;测试登录成功&quot;&gt;&lt;/p&gt;
&lt;p&gt;干杯！🍺🍺🍺。 我们已经完成了基本的测试。 测试中还有很多需要探索的地方。&lt;/p&gt;
&lt;p&gt;希望听到你的建议，对这个系列或帖子的投入&lt;br&gt;如果你喜欢关于TDD的系列，请将其推荐给其他人。&lt;br&gt;欲了解更多信息，请在Medium上关注我。 &lt;/p&gt;
]]></content>
    
      <category term="Android测试"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="TDD"/>
    
      <category term="测试"/>
    
  </entry>
  
  <entry>
    <title>如何在Android中执行TDD？第3部分 - 模拟和集成测试(译)</title>
    <link href="https://dimon94.github.io/2018/06/09/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%E7%AC%AC3%E9%83%A8%E5%88%86%20-%20%E6%A8%A1%E6%8B%9F%E5%92%8C%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95(%E8%AF%91)/"/>
    <id>https://dimon94.github.io/2018/06/09/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%E7%AC%AC3%E9%83%A8%E5%88%86%20-%20%E6%A8%A1%E6%8B%9F%E5%92%8C%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95(%E8%AF%91)/</id>
    <published>2018-06-09T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.570Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文&lt;/p&gt;
&lt;p&gt;很久以后，这是系列的第三篇教程。 我希望你们都做得很好，享受教程系列（阅读，编码和改进）。&lt;/p&gt;
&lt;p&gt;到目前为止，在本系列的第1部分和第2部分，为什么开发人员害怕重构代码，我们获得了为什么TDD至高无上，Android测试的类型，测试的位置，如何编写和运行测试等。&lt;/p&gt;
&lt;p&gt;我们覆盖了单元测试到现在，接下来我们将转向集成测试，以验证演示者是否应对UI。&lt;/p&gt;
&lt;p&gt;如图所示，我们可以创建用于登录的用户界面（我知道你会比这更好的用户界面）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/600/1*pFyDEdZTg8DFVbsIml9OIQ.jpeg&quot; alt=&quot;登录界面&quot;&gt;&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;p&gt;所以我们将编写一些与UI相关的代码。 我们在View和Presenter之间签有合同，即LoginView，如下所示。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LoginView&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginSuccessMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginFailedMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginAttemptExceededMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;现在我们将LoginView实现为我们的Activity，如下所示。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LoginActivity&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppCompatActivity&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LoginView&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; EditText usernameEditText;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; EditText passwordEditText;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; Button loginButton;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; LoginPresenter loginPresenter;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onCreate(savedInstanceState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        setContentView(R.layout.activity_login);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        loginPresenter = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LoginPresenter&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        initView();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;initView&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        usernameEditText = (EditText) findViewById(R.id.username_edit_text);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        passwordEditText = (EditText) findViewById(R.id.password_edit_text);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        loginButton = (Button) findViewById(R.id.login_button);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        loginButton.setOnClickListener(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;View&lt;/span&gt;.OnClickListener() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(View v)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                loginPresenter.validateCredentialsAndPerformLogin(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        usernameEditText.getText().toString(),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        passwordEditText.getText().toString());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginSuccessMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Toast.makeText(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;Login Success&amp;quot;&lt;/span&gt;, Toast.LENGTH_SHORT).show();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginFailedMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Toast.makeText(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;Login Failed&amp;quot;&lt;/span&gt;, Toast.LENGTH_SHORT).show();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;showLoginAttemptExceededMessage&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Toast.makeText(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;Login Attempt Exceeded&amp;quot;&lt;/span&gt;, Toast.LENGTH_SHORT).show();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;我们的Presenter实现如下所示&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LoginPresenter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; LoginView loginView;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; loginAttempt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;LoginPresenter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(LoginView loginView)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.loginView = loginView;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;validateCredentialsAndPerformLogin&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String username, String password)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (isLoginAttemptExceeded()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            loginView.showLoginAttemptExceededMessage();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        incrementLoginAttempt();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (username.equals(&lt;span class=&quot;string&quot;&gt;&amp;quot;admin&amp;quot;&lt;/span&gt;) &amp;amp;&amp;amp; password.equals(&lt;span class=&quot;string&quot;&gt;&amp;quot;admin&amp;quot;&lt;/span&gt;)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            loginView.showLoginSuccessMessage();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125; &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            loginView.showLoginFailedMessage();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;incrementLoginAttempt&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        loginAttempt++;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;isLoginAttemptExceeded&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; loginAttempt &amp;gt;= &lt;span class=&quot;number&quot;&gt;3&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;您将在LoginPresenterTest中发生错误，因为我们有构建使用LoginView对象的Presenter的构造函数。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*MCKwoXItQiLJzYc9_z3qjQ.png&quot; alt=&quot;错误&quot;&gt;&lt;br&gt;为了摆脱这个错误，我们必须在创建演示者对象时将LoginView的对象传递给Presenter。 所以我们必须模拟LoginView.Here来模仿😍。&lt;/p&gt;
&lt;h3 id=&quot;什么是模仿？&quot;&gt;&lt;a href=&quot;#什么是模仿？&quot; class=&quot;headerlink&quot; title=&quot;什么是模仿？&quot;&gt;&lt;/a&gt;什么是模仿？&lt;/h3&gt;&lt;p&gt;你有一个对象，你想要测试的方法，这些方法都依赖于其他对象。 您创建依赖对象的模拟，而不是创建该依赖关系的实际实例。 模拟对象用于单元测试。&lt;/p&gt;
&lt;p&gt;为了模仿，我们将使用Mockito。&lt;br&gt;将以下testCompile依赖项添加到应用程序的build.gradle&lt;/p&gt;
&lt;figure class=&quot;highlight gradle&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;testCompile &lt;span class=&quot;string&quot;&gt;&amp;#x27;org.mockito:mockito-core:1.10.19&amp;#x27;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;现在我们模仿我们的LoginView，如下所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*CqN7YriY3D7NVkkXV2TZ-g.png&quot; alt=&quot;模仿&quot;&gt;&lt;/p&gt;
&lt;p&gt;Ooho😳，😎我们有我们的模拟（神奇）登录对象，可以传递给Presenter。 所以我们的实现就像下图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*JO-0qh-ZMd8XONeOBtYZmg.png&quot; alt=&quot;通过测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;运行测试显示绿色信号继续。&lt;/p&gt;
&lt;h3 id=&quot;集成测试（Presenter和View之间）&quot;&gt;&lt;a href=&quot;#集成测试（Presenter和View之间）&quot; class=&quot;headerlink&quot; title=&quot;集成测试（Presenter和View之间）&quot;&gt;&lt;/a&gt;集成测试（Presenter和View之间）&lt;/h3&gt;&lt;p&gt;要验证Presenter调用正确的View方法后，我们使用verify（）。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*O5FxqfH3kFGp9qVtq3U_2g.png&quot; alt=&quot;验证&quot;&gt;&lt;br&gt;在上面的代码图中，验证方法确保在视图对象上调用showLoginSuccessMessage（），这确保我们的Presenter正确地与View集成。&lt;/p&gt;
&lt;p&gt;其他测试如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*3xwuV6prCaZ2kImWZzdLpg.png&quot; alt=&quot;验证&quot;&gt;&lt;/p&gt;
&lt;p&gt;End&lt;/p&gt;
&lt;p&gt;同时，希望听到有关这方面的建议和意见。&lt;/p&gt;
&lt;p&gt;干杯🍻!!! &lt;/p&gt;
]]></content>
    
      <category term="Android测试"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="TDD"/>
    
      <category term="测试"/>
    
  </entry>
  
  <entry>
    <title>如何在Android中执行TDD？ 第2部分 - 项目体系结构，设置和单元测试(译)</title>
    <link href="https://dimon94.github.io/2018/06/03/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%20%E7%AC%AC2%E9%83%A8%E5%88%86%20-%20%E9%A1%B9%E7%9B%AE%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84%EF%BC%8C%E8%AE%BE%E7%BD%AE%E5%92%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95(%E8%AF%91)/"/>
    <id>https://dimon94.github.io/2018/06/03/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%20%E7%AC%AC2%E9%83%A8%E5%88%86%20-%20%E9%A1%B9%E7%9B%AE%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84%EF%BC%8C%E8%AE%BE%E7%BD%AE%E5%92%8C%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95(%E8%AF%91)/</id>
    <published>2018-06-03T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.570Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文&lt;/p&gt;
&lt;p&gt;本教程将通过项目体系结构，从头开始设置，最后我们将编写一些单元测试。&lt;/p&gt;
&lt;p&gt;而本系列的第1部分，我们介绍了什么是自动化测试的重要性，什么是测试金字塔，什么是测试类型，可以使用什么工具在Android中执行TDD以及测试文件夹的位置。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;项目架构&quot;&gt;&lt;a href=&quot;#项目架构&quot; class=&quot;headerlink&quot; title=&quot;项目架构&quot;&gt;&lt;/a&gt;项目架构&lt;/h3&gt;&lt;p&gt;根据自动化测试，我们必须遵循某种架构模式，这将有助于我们以清晰，可测试的方式测试和构建应用程序。&lt;/p&gt;
&lt;p&gt;有许多架构模式可轻松支持TDD Model - View - Presenter（MVP），Model - View - Viewmodel（MVVM）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们正在为我们的教程系列选择MVP。&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;通常是由Presenter和View用来传达信息或采取行动的POJO。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt;（Activity或Fragment）是显示数据的被动界面。这是我们设置数据并进行UI相关更改（如隐藏，移动视图）的地方。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presenter&lt;/strong&gt;决定演示逻辑，我们的业务逻辑驻留在这里。 它与存储库交谈获取数据，并将其格式化以显示在视图中。 它决定在View.Try上显示什么，尽量避免在演示者中使用Android特定的代码（我知道有时更难）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;项目设置&quot;&gt;&lt;a href=&quot;#项目设置&quot; class=&quot;headerlink&quot; title=&quot;项目设置&quot;&gt;&lt;/a&gt;项目设置&lt;/h3&gt;&lt;p&gt;在这篇文章中，我们将从头开始创建简单的Android应用程序。 我们将创建的应用程序是简单的应用程序，登录并采取学生出席。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开Android Studio选择”&lt;strong&gt;Start a new Android Project&lt;/strong&gt;“&lt;/li&gt;
&lt;li&gt;输入应用程序名称为”&lt;strong&gt;StudentAttendance&lt;/strong&gt;“，公司域名为”&lt;strong&gt;nilesh.tdd.com&lt;/strong&gt;“。 点击下一步&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*dpnE8qzqmPToc7-nwYTQXA.png&quot; alt=&quot;创建项目&quot;&gt;&lt;/li&gt;
&lt;li&gt;选择你想要支持的Android版本并点击”&lt;strong&gt;Next&lt;/strong&gt;“&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*OAM95sofW0ggruqfKFteJA.png&quot; alt=&quot;选择Android版本&quot;&gt;&lt;/li&gt;
&lt;li&gt;选择”&lt;strong&gt;Empty Activity&lt;/strong&gt;“并点击”&lt;strong&gt;Next&lt;/strong&gt;“&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*OAM95sofW0ggruqfKFteJA.png&quot; alt=&quot;选择Empty Activity&quot;&gt;&lt;/li&gt;
&lt;li&gt;输入活动名称，然后单击”&lt;strong&gt;Finish&lt;/strong&gt;“&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*OAM95sofW0ggruqfKFteJA.png&quot; alt=&quot;输入活动名称&quot;&gt;&lt;/li&gt;
&lt;li&gt;最终的项目结构将如下图所示&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*OAM95sofW0ggruqfKFteJA.png&quot; alt=&quot;项目结构&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;单元测试&quot;&gt;&lt;a href=&quot;#单元测试&quot; class=&quot;headerlink&quot; title=&quot;单元测试&quot;&gt;&lt;/a&gt;单元测试&lt;/h3&gt;&lt;p&gt;在对包结构进行一些重构之后，签出提交(&lt;strong&gt;81d1668667bcb5eacf4cf94a8dffe738ad206d67&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用下面的命令&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git &lt;span class=&quot;built_in&quot;&gt;clone&lt;/span&gt; https://github.com/NileshJarad/TDD_Demo.git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;git checkout 81d1668667bcb5eacf4cf94a8dffe738ad206d67&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如果有人在结帐后出现错误&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*lfKyDU02BXRo1B-7Ul7BnQ.png&quot; alt=&quot;结帐错误&quot;&gt;&lt;br&gt;然后在build.gradle中将build.gradle版本更改为2.1.2&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*kja_Shq18OXShIX2xPkBXw.png&quot; alt=&quot;build.gradle&quot;&gt;&lt;br&gt;所以最后我们在这里编程了。&lt;/p&gt;
&lt;p&gt;现在我们将编写我们的第一个测试用例来检查”&lt;strong&gt;如果用户在三次尝试后尝试登录，我们会向他&amp;#x2F;她显示错误消息&lt;/strong&gt;“&lt;/p&gt;
&lt;p&gt;转到&lt;strong&gt;com.tdd.nilesh.studentattendance.login.LoginPresenter&lt;/strong&gt;类。 在类名上使用光标，按&lt;strong&gt;SHIFT + CTRL + T&lt;/strong&gt;（对于mac &lt;strong&gt;SHIFT + CMD + T&lt;/strong&gt;），这将弹出带有Create New Test选项的窗口。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*UMhQkMxos0hS06TBYGw7Xg.png&quot; alt=&quot;创建测试&quot;&gt;&lt;br&gt;它将自动为Test选择类名，并在我们的例子&lt;strong&gt;LoginPresenterTest&lt;/strong&gt;的类名末尾追加Test。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*mRhJiRo7wye4X0Ojj2qixw.png&quot; alt=&quot;创建测试&quot;&gt;&lt;br&gt;点击确定。 它会再次弹出窗口的测试文件夹。 在我们编写JUnit时，请选择..&amp;#x2F;app&amp;#x2F;src&amp;#x2F;test&amp;#x2F;…它将创建具有相同包结构的测试类。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*j5pa8aebCAGyLKWLpxbwdg.png&quot; alt=&quot;创建测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;我正在发布代码的快照，因为我希望您编写代码。😊&lt;br&gt;现在我们将写下如下屏幕截图中的测试。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*-wz1hC51x6wRkqA7OJAcTA.png&quot; alt=&quot;创建测试&quot;&gt;&lt;br&gt;&lt;strong&gt;@Test&lt;/strong&gt;注释表明它是测试。 由于&lt;strong&gt;incrementLoginAttempt&lt;/strong&gt;＆&lt;strong&gt;isLoginAttemptExceeded&lt;/strong&gt;以红色显示。 我们必须在&lt;strong&gt;LoginPresenter&lt;/strong&gt;中创建方法。 按&lt;strong&gt;ALT + ENTER&lt;/strong&gt;，将在演示者类中创建方法，如下所示。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*3sOPDcwW7eXa6yv2RDFDMw.png&quot; alt=&quot;创建测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们写了失败的测试。 逐渐我们会为它编写它的实现。 要运行测试，请按测试名称左侧的小绿色播放图像。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*VlQnTHDvgvRdt3wFl2LeWg.png&quot; alt=&quot;运行测试&quot;&gt;&lt;br&gt;由于我们编写的测试失败测试输出应如下所示。&lt;br&gt;我们期待1作为输出，但我们得到0。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*ugRLt-KJZu9NogmwYGgGNQ.png&quot; alt=&quot;运行测试&quot;&gt;&lt;br&gt;现在我们将编写通过测试的测试实现。 以下是通过测试的实施。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*yq0xP22HkiBto1ZK4edQCw.png&quot; alt=&quot;通过测试&quot;&gt;&lt;/p&gt;
&lt;p&gt;再次运行测试它应该传递😍。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*wCriHjd_c72e7I3Xv54Y4A.png&quot; alt=&quot;通过测试&quot;&gt;&lt;br&gt;直到现在，使用以下结帐命令进行回购。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git checkout 302962ea6f63aa3afba003efa043ebacbaf02345&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;现在您必须编写&lt;strong&gt;checkIfLoginAttemptIsNotExceeded&lt;/strong&gt;测试，并检查您的实现是否正确。 如果你不是，不要担心它会在下一次提交。&lt;/p&gt;
&lt;p&gt;现在我们要检查”用户名和密码是否正确”&lt;/p&gt;
&lt;p&gt;所以我们会写测试，下面是它的样子（失败测试😉）&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*JaeEY0mdhu4ytUzi9_nD-Q.png&quot; alt=&quot;失败测试&quot;&gt;&lt;br&gt;再次重复&lt;strong&gt;ALT + ENTER&lt;/strong&gt;和运行测试的过程。 由于测试失败，它会显示红色警报失败。&lt;/p&gt;
&lt;p&gt;现在我们将编写它的实现如下。&lt;br&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*xAkaUJiXA_At67-R0092Xg.png&quot; alt=&quot;通过测试&quot;&gt;&lt;br&gt;再次运行测试它现在会通过。&lt;/p&gt;
&lt;p&gt;现在尝试使用&lt;strong&gt;checkUsernameAndPasswordIsInCorrect&lt;/strong&gt;测试用例。&lt;/p&gt;
&lt;p&gt;直到现在，使用以下结帐命令进行回购。&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git checkout 4771fbfb8c6cbda98d9f01ac5e035ea02f0f5da6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;同时，希望听到有关这方面的建议和意见。&lt;/p&gt;
&lt;p&gt;干杯🍻!!! &lt;/p&gt;
]]></content>
    
      <category term="Android测试"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="TDD"/>
    
      <category term="测试"/>
    
  </entry>
  
  <entry>
    <title>如何在Android中执行TDD？ 第1部分 - 概述(译)</title>
    <link href="https://dimon94.github.io/2018/05/27/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%20%E7%AC%AC1%E9%83%A8%E5%88%86%20-%20%E6%A6%82%E8%BF%B0(%E8%AF%91)/"/>
    <id>https://dimon94.github.io/2018/05/27/%E5%A6%82%E4%BD%95%E5%9C%A8Android%E4%B8%AD%E6%89%A7%E8%A1%8CTDD%EF%BC%9F%20%E7%AC%AC1%E9%83%A8%E5%88%86%20-%20%E6%A6%82%E8%BF%B0(%E8%AF%91)/</id>
    <published>2018-05-27T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.569Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;Android自动化测试教程系列,使用JUnit＆Espresso&lt;/p&gt;
&lt;p&gt;自动化测试非常重要，因为它在开发应用程序时确保了质量.TDD在编写实现之前执行写入测试。&lt;/p&gt;
&lt;p&gt;有关TDD的更多信息，请参阅此前的教程。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;测试金字塔：&quot;&gt;&lt;a href=&quot;#测试金字塔：&quot; class=&quot;headerlink&quot; title=&quot;测试金字塔：&quot;&gt;&lt;/a&gt;测试金字塔：&lt;/h3&gt;&lt;p&gt;正如我们可以看到下面的金字塔：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/600/1*9LV9happxsb-UqmmnDk-Qw.png&quot; alt=&quot;测试金字塔&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;单元测试&lt;/strong&gt;涵盖了制作坚实基础的大部分金字塔。 单元测试很容易编写。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集成测试&lt;/strong&gt;确保模块的集成是正确的，并覆盖第二大块金字塔。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;功能测试&lt;/strong&gt;通常描述系统在三部分中所做的并占据最小的部分。&lt;/p&gt;
&lt;p&gt;有许多测试工具和框架可以在开发应用程序时使用。 其中一些是由Google提供和支持的，其中一些是第三方。&lt;/p&gt;
&lt;h3 id=&quot;Android中的测试类型：&quot;&gt;&lt;a href=&quot;#Android中的测试类型：&quot; class=&quot;headerlink&quot; title=&quot;Android中的测试类型：&quot;&gt;&lt;/a&gt;Android中的测试类型：&lt;/h3&gt;&lt;h4 id=&quot;单元测试&quot;&gt;&lt;a href=&quot;#单元测试&quot; class=&quot;headerlink&quot; title=&quot;单元测试&quot;&gt;&lt;/a&gt;单元测试&lt;/h4&gt;&lt;p&gt;单元测试主要针对与其他组件隔离的最小功能（如方法，类，组件或小模块）。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;用于单元测试的工具：&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JUnit是编写可重复测试的简单框架。 它是单元测试框架的xUnit体系结构的一个实例。&lt;/li&gt;
&lt;li&gt;Robolectric是流行的Android单元测试框架，通过在JVM上运行测试（不需要任何设备或仿真器），可以加快测试的执行速度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;UI测试&quot;&gt;&lt;a href=&quot;#UI测试&quot; class=&quot;headerlink&quot; title=&quot;UI测试&quot;&gt;&lt;/a&gt;UI测试&lt;/h4&gt;&lt;p&gt;UI测试是模拟用户交互的测试，如单击按钮，在EditText中输入文本。 Android Instrumentation是Android系统中的一组”挂钩”，它允许您控制Android组件的生命周期（即自己驱动活动生命周期，而不是由系统驱动这些生命周期）。 这些测试需要运行实际的设备或仿真器。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;用于UI测试的工具：&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Espresso - Google提供的Android UI测试框架，可以很好地处理测试同步。&lt;/li&gt;
&lt;li&gt;UIAutomator - Google提供的Android UI测试框架，用于同时测试多个应用程序。&lt;/li&gt;
&lt;li&gt;Robotium - 第三方Android UI测试框架（Robotium vs Espresso）&lt;/li&gt;
&lt;li&gt;Selendroid - Selenium for Android&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;项目测试目录结构&quot;&gt;&lt;a href=&quot;#项目测试目录结构&quot; class=&quot;headerlink&quot; title=&quot;项目测试目录结构&quot;&gt;&lt;/a&gt;项目测试目录结构&lt;/h4&gt;&lt;p&gt;在我们的项目中，两个文件夹托管我们的测试，这些测试是test和androidTest。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*64ZYn-MpgqAMAwXaWNHdFA.png&quot; alt=&quot;测试目录结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;test&lt;/strong&gt;：单元测试托管在此文件夹中。 这些测试在JVM上运行，不需要Android设备或模拟器。 这种类型的测试无法访问任何Android框架特定的组件，如上下文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;androidTest&lt;/strong&gt;： - 所有Instrumentation（Espresso）测试托管在此文件夹中。这些测试需要物理Android设备或模拟器才能运行。&lt;/p&gt;
&lt;p&gt;在下一篇文章中，我们将深入探讨遵循和执行单元测试的体系结构。&lt;/p&gt;
&lt;p&gt;同时，希望听到有关这方面的建议和意见。&lt;/p&gt;
&lt;p&gt;干杯🍻!!! &lt;/p&gt;
]]></content>
    
      <category term="Android测试"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="TDD"/>
    
      <category term="测试"/>
    
  </entry>
  
  <entry>
    <title>MVVM 架构，ViewModel 和LiveData 第二部分(译)</title>
    <link href="https://dimon94.github.io/2018/05/20/MVVM%20%E6%9E%B6%E6%9E%84%EF%BC%8CViewModel%20%E5%92%8CLiveData%20%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86/"/>
    <id>https://dimon94.github.io/2018/05/20/MVVM%20%E6%9E%B6%E6%9E%84%EF%BC%8CViewModel%20%E5%92%8CLiveData%20%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86/</id>
    <published>2018-05-20T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.569Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文：MVVM architecture, ViewModel and LiveData (Part ２)&lt;/p&gt;
&lt;p&gt;在Google I &amp;#x2F; O期间，Google推出了包含LiveData 和ViewModel 的architecture components ，这有助于使用MVVM模式开发Android应用程序。 本文讨论这些组件如何为遵循MVVM的Android应用程序提供服务。&lt;/p&gt;
&lt;p&gt;在本系列的第一篇文章中，我们讨论了这些组件如何为遵循MVVM的Android应用程序提供服务。 在第二篇文章中，我们将回答在依赖注入的第一篇文章结尾处提出的其中一个问题。&lt;/p&gt;
&lt;p&gt;本文假定您具有Dagger的基本知识，因为我们将专注于在MVVM示例中设置最新的Dagger版本（版本2.11）以实现依赖注入。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果您需要关于Dagger 2.11的基本信息，请查看Dagger用户指南。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;配置Dagger-2-11&quot;&gt;&lt;a href=&quot;#配置Dagger-2-11&quot; class=&quot;headerlink&quot; title=&quot;配置Dagger 2.11&quot;&gt;&lt;/a&gt;配置Dagger 2.11&lt;/h3&gt;&lt;p&gt;首先，让我们将Dagger 2.11依赖添加到我们的MVVM Sample。&lt;/p&gt;
&lt;h4 id=&quot;指定Dagger版本2-11&quot;&gt;&lt;a href=&quot;#指定Dagger版本2-11&quot; class=&quot;headerlink&quot; title=&quot;指定Dagger版本2.11&quot;&gt;&lt;/a&gt;指定Dagger版本2.11&lt;/h4&gt;&lt;figure class=&quot;highlight gradle&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;project&lt;/span&gt;.ext &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// ... &lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    dagger_version = &lt;span class=&quot;string&quot;&gt;&amp;quot;2.11&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight gradle&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;annotationProcessor &lt;span class=&quot;string&quot;&gt;&amp;quot;com.google.dagger:dagger-compiler:$dagger_version&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;compile&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;com.google.dagger:dagger:$project.dagger_version&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight gradle&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;compile&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;com.google.dagger:dagger-android:$project.dagger_version&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;compile&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;com.google.dagger:dagger-android-support:$project.dagger_version&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;annotationProcessor &lt;span class=&quot;string&quot;&gt;&amp;quot;com.google.dagger:dagger-android-processor:$dagger_version&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;Dagger-2-11项目设置&quot;&gt;&lt;a href=&quot;#Dagger-2-11项目设置&quot; class=&quot;headerlink&quot; title=&quot;Dagger 2.11项目设置&quot;&gt;&lt;/a&gt;Dagger 2.11项目设置&lt;/h3&gt;&lt;p&gt;下图显示了本示例中的主Dagger 2.11设置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*ued7xwa6lGHiGrvGO-8PvA.png&quot; alt=&quot;Dagger 2.11 setup in MVVM Sample&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们主要有以下Dagger App类&amp;#x2F;接口：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AppModule&lt;/strong&gt;是一个Dagger模块，负责在应用程序级别提供单例服务，例如&lt;strong&gt;GitHubService&lt;/strong&gt;和&lt;strong&gt;ProjectViewModelFactory&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AppComponent&lt;/strong&gt;负责注入&lt;strong&gt;AppModule&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ViewModelSubComponent&lt;/strong&gt;是创建View Model实例的子组件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MainActivityModule&lt;/strong&gt;和&lt;strong&gt;FragmentBuildersModule&lt;/strong&gt;是Activity和Fragment实例提供程序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Injectable&lt;/strong&gt;只是可注射Fragment的标记接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AppInjector&lt;/strong&gt;是一个辅助类，用于在实现&lt;strong&gt;Injectable&lt;/strong&gt;接口时自动注入Fragments。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在，让我们进入这个设置中每个Dagger项目的细节。&lt;/p&gt;
&lt;h3 id=&quot;创建-View-Model-SubComponent&quot;&gt;&lt;a href=&quot;#创建-View-Model-SubComponent&quot; class=&quot;headerlink&quot; title=&quot;创建 View Model SubComponent&quot;&gt;&lt;/a&gt;创建 View Model SubComponent&lt;/h3&gt;&lt;p&gt;以下代码片断显示了&lt;strong&gt;ViewModelSubComponent&lt;/strong&gt;接口，该接口负责创建ViewModel实例：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Subcomponent&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModelSubComponent&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Subcomponent&lt;/span&gt;.Builder&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Builder&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ViewModelSubComponent &lt;span class=&quot;title function_&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ProjectListViewModel &lt;span class=&quot;title function_&quot;&gt;projectListViewModel&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ProjectViewModel &lt;span class=&quot;title function_&quot;&gt;projectViewModel&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;请注意，&lt;strong&gt;ViewModelSubComponent&lt;/strong&gt;将被&lt;strong&gt;ProjectViewModelFactory&lt;/strong&gt;调用以获取ViewModel实例。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;但什么是_ProjectViewModelFactory_？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下一节回答这个问题。&lt;/p&gt;
&lt;h3 id=&quot;创建自定义View-Model-Factory&quot;&gt;&lt;a href=&quot;#创建自定义View-Model-Factory&quot; class=&quot;headerlink&quot; title=&quot;创建自定义View Model Factory&quot;&gt;&lt;/a&gt;创建自定义View Model Factory&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;ProjectViewModelFactory&lt;/strong&gt;是一个扩展&lt;strong&gt;ViewModelProvider.Factory&lt;/strong&gt;以便将ViewModel实例提供给使用者Fragment类的工厂。&lt;/p&gt;
&lt;p&gt;以下代码片段显示了&lt;strong&gt;ProjectViewModelFactory&lt;/strong&gt;，它是一个扩展&lt;strong&gt;ViewModelProvider.Factory&lt;/strong&gt;的自解释Factory实现：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectViewModelFactory&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModelProvider&lt;/span&gt;.Factory &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; ArrayMap&amp;lt;Class, Callable&amp;lt;? &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModel&lt;/span&gt;&amp;gt;&amp;gt; creators;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ProjectViewModelFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ViewModelSubComponent viewModelSubComponent)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        creators = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArrayMap&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// View models cannot be injected directly because they won&amp;#x27;t be bound to the owner&amp;#x27;s&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// view model scope.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        creators.put(ProjectViewModel.class, () -&amp;gt; viewModelSubComponent.projectViewModel());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        creators.put(ProjectListViewModel.class, () -&amp;gt; viewModelSubComponent.projectListViewModel());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &amp;lt;T &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModel&lt;/span&gt;&amp;gt; T &lt;span class=&quot;title function_&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Class&amp;lt;T&amp;gt; modelClass)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        Callable&amp;lt;? &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModel&lt;/span&gt;&amp;gt; creator = creators.get(modelClass);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (creator == &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; (Map.Entry&amp;lt;Class, Callable&amp;lt;? &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ViewModel&lt;/span&gt;&amp;gt;&amp;gt; entry : creators.entrySet()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (modelClass.isAssignableFrom(entry.getKey())) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    creator = entry.getValue();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (creator == &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;IllegalArgumentException&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Unknown model class &amp;quot;&lt;/span&gt; + modelClass);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; (T) creator.call();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125; &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception e) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;RuntimeException&lt;/span&gt;(e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;现在，让我们看下一节中的主要应用程序模块。&lt;/p&gt;
&lt;h3 id=&quot;创建App模块&quot;&gt;&lt;a href=&quot;#创建App模块&quot; class=&quot;headerlink&quot; title=&quot;创建App模块&quot;&gt;&lt;/a&gt;创建App模块&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;AppModule&lt;/strong&gt;是一个Dagger模块，负责在应用程序级别为消费者提供单例服务，例如&lt;strong&gt;GitHubService&lt;/strong&gt;和&lt;strong&gt;ProjectViewModelFactory&lt;/strong&gt;。 以下代码片段显示了&lt;strong&gt;AppModule&lt;/strong&gt;类：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module(subcomponents = ViewModelSubComponent.class)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt; &lt;span class=&quot;meta&quot;&gt;@Provides&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    GitHubService &lt;span class=&quot;title function_&quot;&gt;provideGithubService&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Retrofit&lt;/span&gt;.Builder()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .baseUrl(GitHubService.HTTPS_API_GITHUB_URL)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .addConverterFactory(GsonConverterFactory.create())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .build()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .create(GitHubService.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Provides&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ViewModelProvider.Factory &lt;span class=&quot;title function_&quot;&gt;provideViewModelFactory&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;            ViewModelSubComponent.Builder viewModelSubComponent)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectViewModelFactory&lt;/span&gt;(viewModelSubComponent.build());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这里需要注意的一点是，不要忘记通过在**@Module&lt;strong&gt;注解的&lt;/strong&gt;subcomponents&lt;strong&gt;参数中指定&lt;/strong&gt;ViewModelSubComponent&lt;strong&gt;到&lt;/strong&gt;AppModule**。&lt;/p&gt;
&lt;h3 id=&quot;创建-Injectable和-AppInjector&quot;&gt;&lt;a href=&quot;#创建-Injectable和-AppInjector&quot; class=&quot;headerlink&quot; title=&quot;创建 Injectable和 AppInjector&quot;&gt;&lt;/a&gt;创建 Injectable和 AppInjector&lt;/h3&gt;&lt;p&gt;可注入接口只是一个普通的空白标记接口，如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Injectable&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Injectable&lt;/strong&gt;将由可注射的Fragment实施。&lt;/p&gt;
&lt;p&gt;为了在实现&lt;strong&gt;Injectable&lt;/strong&gt;接口时自动注入片段，将创建以下&lt;strong&gt;AppInjector&lt;/strong&gt;助手类，以在**onFragmentCreated（）**上注入片段实例，如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppInjector&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;AppInjector&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(MVVMApplication mvvmApplication)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        DaggerAppComponent.builder().application(mvvmApplication)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .build().inject(mvvmApplication);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        mvvmApplication&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .registerActivityLifecycleCallbacks(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Application&lt;/span&gt;.ActivityLifecycleCallbacks() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onActivityCreated&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Activity activity, Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        handleActivity(activity);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;comment&quot;&gt;// Other methods are omitted for simplification ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;handleActivity&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Activity activity)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (activity &lt;span class=&quot;keyword&quot;&gt;instanceof&lt;/span&gt; HasSupportFragmentInjector) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            AndroidInjection.inject(activity);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (activity &lt;span class=&quot;keyword&quot;&gt;instanceof&lt;/span&gt; FragmentActivity) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            ((FragmentActivity) activity).getSupportFragmentManager()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    .registerFragmentLifecycleCallbacks(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                            &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FragmentManager&lt;/span&gt;.FragmentLifecycleCallbacks() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onFragmentCreated&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(FragmentManager fm, Fragment fragment,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                                                              Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (fragment &lt;span class=&quot;keyword&quot;&gt;instanceof&lt;/span&gt; Injectable) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                        AndroidSupportInjection.inject(fragment);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                                &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                            &amp;#125;, &lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;有一点需要注意，**AppInjector.init（）**将在应用程序启动时调用（正如我们将在自定义应用程序类部分中展示的那样）。&lt;/p&gt;
&lt;h3 id=&quot;创建-Activity-和-Fragment-Modules&quot;&gt;&lt;a href=&quot;#创建-Activity-和-Fragment-Modules&quot; class=&quot;headerlink&quot; title=&quot;创建 Activity 和 Fragment Modules&quot;&gt;&lt;/a&gt;创建 Activity 和 Fragment Modules&lt;/h3&gt;&lt;p&gt;以下代码片段显示了Fragments Dagger模块：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FragmentBuildersModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@ContributesAndroidInjector&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; ProjectFragment &lt;span class=&quot;title function_&quot;&gt;contributeProjectFragment&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@ContributesAndroidInjector&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; ProjectListFragment &lt;span class=&quot;title function_&quot;&gt;contributeProjectListFragment&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;从Dagger 2.10开始，&lt;strong&gt;@ContributesAndroidInjector&lt;/strong&gt;轻松将活动和片段附加到匕首图上。 以下代码片断显示了&lt;strong&gt;MainActivityModule&lt;/strong&gt;：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Module&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MainActivityModule&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@ContributesAndroidInjector(modules = FragmentBuildersModule.class)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;abstract&lt;/span&gt; MainActivity &lt;span class=&quot;title function_&quot;&gt;contributeMainActivity&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;现在，我们来看看Dagger 2.11设置中的最后一项，即&lt;strong&gt;AppComponent&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;创建AppComponent&quot;&gt;&lt;a href=&quot;#创建AppComponent&quot; class=&quot;headerlink&quot; title=&quot;创建AppComponent&quot;&gt;&lt;/a&gt;创建AppComponent&lt;/h3&gt;&lt;p&gt;下一个代码片段显示了&lt;strong&gt;AppComponent&lt;/strong&gt;接口。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Component(modules = &amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;        AndroidInjectionModule.class,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;        AppModule.class,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;        MainActivityModule.class&amp;#125;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AppComponent&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Component&lt;/span&gt;.Builder&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Builder&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;meta&quot;&gt;@BindsInstance&lt;/span&gt; Builder &lt;span class=&quot;title function_&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Application application)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        AppComponent &lt;span class=&quot;title function_&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(MVVMApplication mvvmApplication)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;有一件重要的事情要注意，除了包括&lt;strong&gt;AppModule&lt;/strong&gt;和&lt;strong&gt;MainActivityModule&lt;/strong&gt;之外，我们还根据官方文档向&lt;strong&gt;AppComponent&lt;/strong&gt;添加了&lt;strong&gt;AndroidSupportInjectionModule&lt;/strong&gt;，该文档声明有必要确保所有必要的绑定都可用。 &lt;strong&gt;AndroidSupportInjectionModule&lt;/strong&gt;是dagger-android中的一个内置模块：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2dvb2dsZS9kYWdnZXIvYmxvYi9tYXN0ZXIvamF2YS9kYWdnZXIvYW5kcm9pZC9zdXBwb3J0L0FuZHJvaWRTdXBwb3J0SW5qZWN0aW9uTW9kdWxlLmphdmE=&quot;&gt;https://github.com/google/dagger/blob/master/java/dagger/android/support/AndroidSupportInjectionModule.java&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;更新存储库层实现&quot;&gt;&lt;a href=&quot;#更新存储库层实现&quot; class=&quot;headerlink&quot; title=&quot;更新存储库层实现&quot;&gt;&lt;/a&gt;更新存储库层实现&lt;/h3&gt;&lt;p&gt;现在，我们完成了设置Dagger 2.11，让我们更新我们现有的应用程序代码，以便利用Dagger依赖注入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ProjectRepository&lt;/strong&gt;不再需要手动创建&lt;strong&gt;GitHubService&lt;/strong&gt;服务实例，它所需要做的就是在它的&lt;strong&gt;GitHubService&lt;/strong&gt;实例的构造函数中使用**@Inject**，如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Singleton&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectRepository&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; GitHubService gitHubService;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ProjectRepository&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(GitHubService gitHubService)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.gitHubService = gitHubService;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// Other methods here are omitted for simplicity ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;更新ViewModel层实现&quot;&gt;&lt;a href=&quot;#更新ViewModel层实现&quot; class=&quot;headerlink&quot; title=&quot;更新ViewModel层实现&quot;&gt;&lt;/a&gt;更新ViewModel层实现&lt;/h3&gt;&lt;p&gt;更新ViewModel图层也是必要的，以避免在此图层内手动从&lt;strong&gt;ProjectRepository&lt;/strong&gt;创建实例。&lt;/p&gt;
&lt;p&gt;以下代码片段显示了&lt;strong&gt;ProjectViewModel&lt;/strong&gt;的一个示例，该示例使用@inject注释来注入&lt;strong&gt;Application&lt;/strong&gt;和&lt;strong&gt;ProjectRepository&lt;/strong&gt;实例：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectViewModel&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AndroidViewModel&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;TAG&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; ProjectViewModel.class.getName();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;MutableLiveData&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;ABSENT&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MutableLiveData&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;//noinspection unchecked&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        ABSENT.setValue(&lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; LiveData&amp;lt;Project&amp;gt; projectObservable;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; MutableLiveData&amp;lt;String&amp;gt; projectID;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; ObservableField&amp;lt;Project&amp;gt; project = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ObservableField&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ProjectViewModel&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@NonNull&lt;/span&gt; ProjectRepository projectRepository, &lt;span class=&quot;meta&quot;&gt;@NonNull&lt;/span&gt; Application application)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;(application);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.projectID = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MutableLiveData&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        projectObservable = Transformations.switchMap(projectID, input -&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (input.isEmpty()) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; ABSENT;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; projectRepository.getProjectDetails(&lt;span class=&quot;string&quot;&gt;&amp;quot;Google&amp;quot;&lt;/span&gt;, projectID.getValue());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// Code is omitted for simplicity ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;更新视图实现（Fragments和主要的Activity）&quot;&gt;&lt;a href=&quot;#更新视图实现（Fragments和主要的Activity）&quot; class=&quot;headerlink&quot; title=&quot;更新视图实现（Fragments和主要的Activity）&quot;&gt;&lt;/a&gt;更新视图实现（Fragments和主要的Activity）&lt;/h3&gt;&lt;p&gt;更新视图层也是必要的，以避免在该图层内手动创建ViewModel类的实例。&lt;/p&gt;
&lt;p&gt;以下代码片段显示了&lt;strong&gt;ProjectFragment&lt;/strong&gt;的一个示例：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectFragment&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LifecycleFragment&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Injectable&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ViewModelProvider.Factory viewModelFactory;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onActivityCreated&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt; Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onActivityCreated(savedInstanceState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ProjectViewModel&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;viewModel&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; ViewModelProviders.of(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;, viewModelFactory)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .get(ProjectViewModel.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这里需要注意的一些重点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;现在每个Fragment都必须实现可注入接口。&lt;/li&gt;
&lt;li&gt;Fragment应该引用&lt;strong&gt;ViewModelProvider.Factory&lt;/strong&gt;以获取ViewModel实例。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;创建定制-Application类&quot;&gt;&lt;a href=&quot;#创建定制-Application类&quot; class=&quot;headerlink&quot; title=&quot;创建定制 Application类&quot;&gt;&lt;/a&gt;创建定制 Application类&lt;/h3&gt;&lt;p&gt;最后，我们的自定义application类代码如下所示：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MVVMApplication&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;HasActivityInjector&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    DispatchingAndroidInjector&amp;lt;Activity&amp;gt; dispatchingAndroidInjector;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onCreate();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        AppInjector.init(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; DispatchingAndroidInjector&amp;lt;Activity&amp;gt; &lt;span class=&quot;title function_&quot;&gt;activityInjector&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; dispatchingAndroidInjector;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这里需要注意两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Application必须实现&lt;strong&gt;HasActivityInjector&lt;/strong&gt;，并**@Inject DispatchingAndroidInjector** 才能从**activityInjector（）**方法返回。&lt;/li&gt;
&lt;li&gt;在Application类的&lt;strong&gt;onCreate（）&lt;strong&gt;中，我们初始化&lt;/strong&gt;AppInjector&lt;/strong&gt;以便在实现&lt;strong&gt;Injectable&lt;/strong&gt;接口时自动注入Fragments。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;源代码&quot;&gt;&lt;a href=&quot;#源代码&quot; class=&quot;headerlink&quot; title=&quot;源代码&quot;&gt;&lt;/a&gt;源代码&lt;/h3&gt;&lt;p&gt;在GitHub中检查更新的应用程序的源代码，随意随意分叉和更新，如你所愿：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2hhemVtcy9tdnZtLXNhbXBsZS1hcHAvdHJlZS9wYXJ0Mg==&quot;&gt;https://github.com/hazems/mvvm-sample-app/tree/part2&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;下一步是什么&quot;&gt;&lt;a href=&quot;#下一步是什么&quot; class=&quot;headerlink&quot; title=&quot;下一步是什么&quot;&gt;&lt;/a&gt;下一步是什么&lt;/h3&gt;&lt;p&gt;在本文后，您现在有足够的信息来使用Google Architectural components创建自己的MVVM应用程序。 希望我能为本系列的下一篇文章留有余地，内容包括以下主题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;错误处理。&lt;/li&gt;
&lt;li&gt;在此演示中添加更多功能，以了解Room如何促进SQLite数据操作，以及如何实现有效的缓存。&lt;/li&gt;
&lt;li&gt;单元测试。&lt;/li&gt;
&lt;li&gt;我们在哪里可以将Rx用于此架构？&lt;/li&gt;
&lt;li&gt;我们如何使用Kotlin简化这个实现？ （旅程真的有很多有趣的东西 : )）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你喜欢这篇文章，我感谢你的支持与他人分享这篇文章，让伟大的Android社区知道。 &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="MVVM"/>
    
      <category term="Dagger2"/>
    
  </entry>
  
  <entry>
    <title>MVVM 架构，ViewModel 和LiveData 第一部分(译)</title>
    <link href="https://dimon94.github.io/2018/05/13/MVVM%20%E6%9E%B6%E6%9E%84%EF%BC%8CViewModel%20%E5%92%8CLiveData%20%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86/"/>
    <id>https://dimon94.github.io/2018/05/13/MVVM%20%E6%9E%B6%E6%9E%84%EF%BC%8CViewModel%20%E5%92%8CLiveData%20%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86/</id>
    <published>2018-05-13T14:36:16.000Z</published>
    <updated>2025-09-22T09:18:12.568Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;原文：MVVM architecture, ViewModel and LiveData (Part 1)&lt;/p&gt;
&lt;p&gt;在Google I &amp;#x2F; O期间，Google推出了包含LiveData 和ViewModel 的architecture components ，这有助于使用MVVM模式开发Android应用程序。 本文讨论这些组件如何为遵循MVVM的Android应用程序提供服务。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;快速定义MVVM&quot;&gt;&lt;a href=&quot;#快速定义MVVM&quot; class=&quot;headerlink&quot; title=&quot;快速定义MVVM&quot;&gt;&lt;/a&gt;快速定义MVVM&lt;/h3&gt;&lt;p&gt;如果您熟悉MVVM，则可以完全跳过本节。&lt;/p&gt;
&lt;p&gt;MVVM是增强关注点分离的体系结构模式之一，它允许将用户界面逻辑从业务（或后端）逻辑中分离出来。 它的目标（与其他MVC模式目标一致）是为了实现以下原则：”使UI代码简单且不含应用程序逻辑，以便更易于管理”。&lt;/p&gt;
&lt;p&gt;MVVM主要有以下几个层次：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Model&lt;br&gt;Model表示应用程序的数据和业务逻辑。 这一层的推荐实施策略之一是通过观测数据并且公开其数据，从而完全从ViewModel或任何其他观察者&amp;#x2F;消费者（这将在我们的MVVM示例应用程序中进行说明）进行解耦。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ViewModel&lt;br&gt;ViewModel与Model交互，并且还准备可以被View观察的observable（s）。 ViewModel可以选择性地为View提供钩子（hooks）以将事件传递给Model。&lt;br&gt;该层的一个重要实现策略是将其与View分离，即ViewModel不应该意识到与之交互的View。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View&lt;br&gt;最后，此模式中的View是观察（或订阅）ViewModel，可观察数据以获取数据并且相应地更新UI元素。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下图显示了MVVM组件和基本交互。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*BpxMFh7DdX0_hqX6ABkDgw.png&quot; alt=&quot;MVVM组件和基本交互&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;LiveData&quot;&gt;&lt;a href=&quot;#LiveData&quot; class=&quot;headerlink&quot; title=&quot;LiveData&quot;&gt;&lt;/a&gt;LiveData&lt;/h3&gt;&lt;p&gt;如上所述，LiveData是新引入的体系结构组件之一。 LiveData是一个可观察的数据持有者。 这允许应用程序中的组件能够观察LiveData对象的更改，而无需在它们之间创建明确的和严格的依赖关系路径。 这将完全分离LiveData对象使用者的LiveData对象生产者。&lt;/p&gt;
&lt;p&gt;除此之外，LiveData也有很大的好处，LiveData尊重应用程序组件（活动，片段，服务）的生命周期状态，并处理对象生命周期管理，确保LiveData对象不泄漏。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据Google文档，如果您已经在使用Rx或Agera等Library，则可以继续使用它们而不是LiveData。 但在这种情况下，您有责任处理每个Android组件生命周期的对象分配和解除分配。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于LiveData尊重Android生命周期，这意味着除非LiveData主机（activity或fragment）处于活动状态（接收onStart()但未收到onStop()），否则它将不会调用其观察者回调。 除此之外，当主机收到onDestroy()时，LiveData也会自动删除观察者。&lt;/p&gt;
&lt;p&gt;LiveData将在下面的MVVM示例应用程序中进行说明。&lt;/p&gt;
&lt;h3 id=&quot;ViewModel&quot;&gt;&lt;a href=&quot;#ViewModel&quot; class=&quot;headerlink&quot; title=&quot;ViewModel&quot;&gt;&lt;/a&gt;ViewModel&lt;/h3&gt;&lt;p&gt;ViewModel也是新引入的体系结构组件之一。 architecture components 提供了一个名为&lt;strong&gt;ViewModel&lt;/strong&gt;的新类，它负责为UI &amp;#x2F; View准备数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ViewModel&lt;/strong&gt;为您的MVVM ViewModel层提供了一个很好的基类，因为&lt;strong&gt;ViewModel&lt;/strong&gt;（及其子类&lt;strong&gt;AndroidViewModel&lt;/strong&gt;）的扩展类在配置更改期间自动保留其保留数据。 这意味着，在配置更改后，此&lt;strong&gt;ViewModel&lt;/strong&gt;所有数据立即可用于下一个Activity或Fragment实例。&lt;/p&gt;
&lt;p&gt;下图显示了ViewModel组件的生命周期。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*uWXunt0A6fKUFU8PsTLkfA.png&quot; alt=&quot;ViewModel组件的生命周期&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ViewModel&lt;/strong&gt;也将在我们的MVVM示例应用程序中进行说明。&lt;/p&gt;
&lt;h3 id=&quot;示例应用程序&quot;&gt;&lt;a href=&quot;#示例应用程序&quot; class=&quot;headerlink&quot; title=&quot;示例应用程序&quot;&gt;&lt;/a&gt;示例应用程序&lt;/h3&gt;&lt;p&gt;现在，让我们来看看最有趣的部分，让我们把所有这些东西放在一个示例应用程序中。 此MVVM示例应用程序主要包含两个屏幕。 下面显示的第一个屏幕显示了Google GitHub项目列表，其中包含一些简要信息，例如标题，编程语言和最终观察者数量。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*vt5V03Pe1Va7B0vGfceRpQ.png&quot; alt=&quot;Project List&quot;&gt;&lt;/p&gt;
&lt;p&gt;一旦应用程序的最终用户触摸任何列表项、GitHub项目的详细信息，屏幕将显示项目描述、编程语言、观察者数量、公开问题、创建和上次更新日期，最后显示克隆URL。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*7dR3uzZsANvvjJp3twYINg.png&quot; alt=&quot;Project Details&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;示例应用程序交互图&quot;&gt;&lt;a href=&quot;#示例应用程序交互图&quot; class=&quot;headerlink&quot; title=&quot;示例应用程序交互图&quot;&gt;&lt;/a&gt;示例应用程序交互图&lt;/h3&gt;&lt;p&gt;下图显示了示例应用程序的包结构&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*2AIrQEjTkInY_BjUW9slSw.png&quot; alt=&quot;示例应用程序的包结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;以下交互图显示了检索Google GitHub项目的应用场景之一的示例交互图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/800/1*uCmP51XYJCuffo4fXe2KFg.png&quot; alt=&quot;Google GitHub项目的应用场景&quot;&gt;&lt;/p&gt;
&lt;p&gt;如图所示，每个图层都从其后续图层（Fragment（View） - &amp;gt; ViewModel - &amp;gt; Repository）观察LiveData，最后一旦检索到项目列表，就会使用RecyclerView适配器绑定显示项目列表。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Repository模块负责处理数据操作。 通过确保这一点，Repository模块可以为应用程序的其余部分提供干净的API，并简化使用者ViewModel的工作。 如果需要更新数据，系统信息库模块应该知道从哪里获取数据以及进行哪些API调用。 它们可以被视为不同数据源（REST服务，数据库，XML文件等）之间的中介。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在，让我们从下往上解释这些图层，从Model，ViewModel开始，最后用View来检索GitHub项目场景。&lt;/p&gt;
&lt;h3 id=&quot;示例应用程序模型层&quot;&gt;&lt;a href=&quot;#示例应用程序模型层&quot; class=&quot;headerlink&quot; title=&quot;示例应用程序模型层&quot;&gt;&lt;/a&gt;示例应用程序模型层&lt;/h3&gt;&lt;p&gt;让我们从业务逻辑层开始，我们有两个模型对象&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project&lt;/strong&gt;，包含GitHub项目的信息，如id，名称，描述，创建日期等等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户&lt;/strong&gt;，包含GitHub项目所有者的用户信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了与GitHub RESTful API进行交互，我使用了我喜欢的Retrofit 2来定义存储库包中的以下简单接口。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;GithubService&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@GET(&amp;quot;users/&amp;#123;user&amp;#125;/repos&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Single&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getProjectList&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@Path(&amp;quot;user&amp;quot;)&lt;/span&gt; String userId)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@GET(&amp;quot;repos/&amp;#123;user&amp;#125;/&amp;#123;repoName&amp;#125;&amp;quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Single&amp;lt;Project&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getProjectDetails&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@Path(&amp;quot;user&amp;quot;)&lt;/span&gt; String userId, &lt;span class=&quot;meta&quot;&gt;@Path(&amp;quot;repoName&amp;quot;)&lt;/span&gt; String projectName)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;为了便于ViewModel的Job，创建一个&lt;strong&gt;ProjectRepository&lt;/strong&gt;类来与GitHub服务交互，并最终为ViewModel提供一个&lt;strong&gt;LiveData&lt;/strong&gt;对象。 它也将在以后用于编排服务呼叫。 以下代码片段显示了**getProjectList()**API实现。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectRepository&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; GithubService githubService;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// constructor and helper methods&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; LiveData&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getProjectList&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String userId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; MutableLiveData&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt; data = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MutableLiveData&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        githubService.getProjectList(userId)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .subscribeOn(Schedulers.io())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .observeOn(AndroidSchedulers.mainThread())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                .subscribe(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;SingleObserver&lt;/span&gt;&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSubscribe&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Disposable d)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(List&amp;lt;Project&amp;gt; projects)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        data.setValue(projects);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Throwable e)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        &lt;span class=&quot;comment&quot;&gt;// handle the error case&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        &lt;span class=&quot;comment&quot;&gt;//Yet, do not do nothing in this app. In a real world, this should be handled&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                        System.out.println(e);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; data;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;ProjectRepository&lt;/strong&gt;是ViewModel的数据提供者，它具有&lt;strong&gt;getProjectList()&lt;/strong&gt;，它将响应简单地包装到LiveData Object中。&lt;/p&gt;
&lt;p&gt;为了简化本文的目的，错误处理被省略，并且将在下一篇文章中进行说明。&lt;/p&gt;
&lt;h3 id=&quot;示例应用程序ViewModel图层&quot;&gt;&lt;a href=&quot;#示例应用程序ViewModel图层&quot; class=&quot;headerlink&quot; title=&quot;示例应用程序ViewModel图层&quot;&gt;&lt;/a&gt;示例应用程序ViewModel图层&lt;/h3&gt;&lt;p&gt;为了消费&lt;strong&gt;getProjectList（）&lt;strong&gt;API，创建了ViewModel类（调用Repository API并可以为LiveData执行任何所需的转换）。 以下代码片段显示了&lt;/strong&gt;ProjectListViewModel&lt;/strong&gt;类。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectListViewModel&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AndroidViewModel&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; LiveData&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt; projectListObservable;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; ProjectRepository projectRepository;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ProjectListViewModel&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@NonNull&lt;/span&gt; Application application)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;(application);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// If any transformation is needed, this can be simply done by Transformations class ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        projectRepository = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectRepository&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        projectListObservable = projectRepository.getProjectList(&lt;span class=&quot;string&quot;&gt;&amp;quot;Google&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;     * Expose the LiveData Projects query so the UI can observe it.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;     */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; LiveData&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getProjectListObservable&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; projectListObservable;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如上所示，我们的&lt;strong&gt;ProjectListViewModel&lt;/strong&gt;类扩展了&lt;strong&gt;AndroidViewModel&lt;/strong&gt;，并在构造函数中调用**getProjectList（”Google”）**来检索Google GitHub项目。&lt;/p&gt;
&lt;p&gt;在现实世界的情况下，在将结果数据传递给观察视图之前可能需要进行转换，为了进行转换，可以使用Transformation类，如下面的文档所示：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9kZXZlbG9wZXIuYW5kcm9pZC5jb20vdG9waWMvbGlicmFyaWVzL2FyY2hpdGVjdHVyZS9saXZlZGF0YS5odG1sI3RyYW5zZm9ybWF0aW9uc19vZl9saXZlZGF0YQ==&quot;&gt;https://developer.android.com/topic/libraries/architecture/livedata.html#transformations_of_livedata&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;示例应用视图图层&quot;&gt;&lt;a href=&quot;#示例应用视图图层&quot; class=&quot;headerlink&quot; title=&quot;示例应用视图图层&quot;&gt;&lt;/a&gt;示例应用视图图层&lt;/h3&gt;&lt;p&gt;最后，让我们快速浏览一下这个应用程序的视图层，我们主要有一个名为&lt;strong&gt;MainActivity&lt;/strong&gt;的Activity，它负责处理代表应用程序视图的两个片段的导航：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ProjectListFragment&lt;/strong&gt;，它显示Google GitHub项目的列表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ProjectFragment&lt;/strong&gt;，它显示所选的GitHub项目细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;由于Activities和Fragments被视为生命周期所有者，活动需要扩展LifecycleActivity，片段需要扩展LifecycleFragment。 但是，请务必记住LifecycleActivity和LifecycleFragment类都是临时实现，直到Lifecycle与支持库集成为止：&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9kZXZlbG9wZXIuYW5kcm9pZC5jb20vcmVmZXJlbmNlL2FuZHJvaWQvYXJjaC9saWZlY3ljbGUvTGlmZWN5Y2xlQWN0aXZpdHkuaHRtbA==&quot;&gt;https://developer.android.com/reference/android/arch/lifecycle/LifecycleActivity.html&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在，让我们继续我们的项目检索方案，查看&lt;strong&gt;ProjectListFragment&lt;/strong&gt;，下面的代码片段显示了最重要的集成部分。&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;39&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;40&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;41&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;42&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;43&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;44&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;45&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;46&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;47&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;48&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectListFragment&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;LifecycleFragment&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; ProjectAdapter projectAdapter;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; FragmentProjectListBinding binding;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; View &lt;span class=&quot;title function_&quot;&gt;onCreateView&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(LayoutInflater inflater, &lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt; ViewGroup container,&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;params&quot;&gt;                           &lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt; Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_project_list, container, &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        projectAdapter = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectAdapter&lt;/span&gt;(projectClickCallback);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        binding.projectList.setAdapter(projectAdapter);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        binding.setIsLoading(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; binding.getRoot();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onActivityCreated&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt; Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onActivityCreated(savedInstanceState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ProjectListViewModel&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;viewModel&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; ViewModelProviders.of(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;).get(ProjectListViewModel.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        observeViewModel(viewModel);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;observeViewModel&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(ProjectListViewModel viewModel)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;comment&quot;&gt;// Update the list when the data changes&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        viewModel.getProjectListObservable().observe(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;, &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Observer&lt;/span&gt;&amp;lt;List&amp;lt;Project&amp;gt;&amp;gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onChanged&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;meta&quot;&gt;@Nullable&lt;/span&gt; List&amp;lt;Project&amp;gt; projects)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (projects != &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    binding.setIsLoading(&lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                    projectAdapter.setProjectList(projects);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ProjectClickCallback&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;projectClickCallback&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ProjectClickCallback&lt;/span&gt;() &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Project project)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                ((MainActivity) getActivity()).show(project);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;如上所示，&lt;strong&gt;ProjectListFragment&lt;/strong&gt;获取&lt;strong&gt;ProjectListViewModel&lt;/strong&gt;，然后监听其&lt;strong&gt;getProjectListObservable（）&lt;strong&gt;方法，以便在准备好时获取Github项目列表。 最后，一旦检索到项目列表，它将被传递给&lt;/strong&gt;projectAdapter&lt;/strong&gt;（RecyclerView适配器），以显示RecyclerView组件中的项目列表。&lt;/p&gt;
&lt;p&gt;这是对项目的一个端到端场景的解释，您可以在这里找到GitHub中提供的完整项目：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2hhemVtcy9tdnZtLXNhbXBsZS1hcHAvdHJlZS9wYXJ0MQ==&quot;&gt;https://github.com/hazems/mvvm-sample-app/tree/part1&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;MVVM实施的重要指导原则&quot;&gt;&lt;a href=&quot;#MVVM实施的重要指导原则&quot; class=&quot;headerlink&quot; title=&quot;MVVM实施的重要指导原则&quot;&gt;&lt;/a&gt;MVVM实施的重要指导原则&lt;/h3&gt;&lt;p&gt;现在，重点介绍MVVM实现的一些重要指导原则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如示例中所示，ViewModels不会直接引用Views，因为如果这样做，ViewModels可能会超出View的生命周期，并且可能会发生内存泄漏。&lt;/li&gt;
&lt;li&gt;建议使用Model和ViewModel使用LiveData公开其数据，因为LiveData尊重应用程序组件（活动，片段，服务）的生命周期状态，并处理确保LiveData对象不泄漏的对象生命周期管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;下一篇文章&quot;&gt;&lt;a href=&quot;#下一篇文章&quot; class=&quot;headerlink&quot; title=&quot;下一篇文章&quot;&gt;&lt;/a&gt;下一篇文章&lt;/h3&gt;&lt;p&gt;故事尚未完成，因为有些事情需要处理，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依赖注入&lt;/li&gt;
&lt;li&gt;错误处理&lt;/li&gt;
&lt;li&gt;缓存&lt;/li&gt;
&lt;li&gt;在此演示中添加更多功能以查看Room如何促进SQLite数据操作&lt;/li&gt;
&lt;li&gt;单元测试&lt;/li&gt;
&lt;li&gt;其他…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这将在MVVM的下一系列文章中进行说明，敬请关注。 &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="译文"/>
    
      <category term="MVVM"/>
    
  </entry>
  
  <entry>
    <title>Android架构：第五部分-Clean架构是如何测试的 (译)</title>
    <link href="https://dimon94.github.io/2018/05/13/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%94%E9%83%A8%E5%88%86-Clean%E6%9E%B6%E6%9E%84%E6%98%AF%E5%A6%82%E4%BD%95%E6%B5%8B%E8%AF%95%E7%9A%84/"/>
    <id>https://dimon94.github.io/2018/05/13/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%94%E9%83%A8%E5%88%86-Clean%E6%9E%B6%E6%9E%84%E6%98%AF%E5%A6%82%E4%BD%95%E6%B5%8B%E8%AF%95%E7%9A%84/</id>
    <published>2018-05-13T14:36:15.000Z</published>
    <updated>2025-09-22T09:18:12.568Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;你为什么要关心测试？ 像任何人一样，程序员犯错误。 我们可能会忘记我们上个月实现的边缘案例，或者我们传递一个空字符串时某些方法的行为方式。&lt;/p&gt;
&lt;p&gt;在每次更改后都可以使用APP，并尝试每次可能的点击，点按，手势和方向更改，以确保一切正常。 在旋转设备时，您可能会忘记右上角的三次敲击，因此当用户执行此操作时，所有内容都会崩溃，并引发空指针异常。 用户做的很愚蠢，我们需要确保每个类都能够做到应有的功能，并且APP的每个部分都可以处理我们抛出的所有内容。&lt;/p&gt;
&lt;p&gt;这就是我们编写自动化测试的原因。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h3 id=&quot;1-测试-Clean-架构&quot;&gt;&lt;a href=&quot;#1-测试-Clean-架构&quot; class=&quot;headerlink&quot; title=&quot;1. 测试 Clean 架构&quot;&gt;&lt;/a&gt;1. 测试 Clean 架构&lt;/h3&gt;&lt;p&gt;Clean架构完全关于可维护性和可测试性。 架构的每个部分都有一个目的。 我们只需要指定它并检查它实际上是否每次都做它的工作。&lt;/p&gt;
&lt;p&gt;现在，让我们现实一点。 我们可以测试什么？ 一切。 诚然，如果你正确地构建你的代码，你可以测试一切。 这取决于你要测试什么。 不幸的是，通常没有时间来测试一切。&lt;/p&gt;
&lt;p&gt;可测性。 这是第一步。 第二步是测试正确的方法。 让我们提醒一下&lt;strong&gt;FIRST&lt;/strong&gt;的旧规则：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fast&lt;/strong&gt; – 测试应该非常快。如果需要几分钟或几小时来执行测试，写测试是没有意义的。 没有人会检查测试，如果是这样的话！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolated&lt;/strong&gt; – 一次测试APP的一个单元。 安排在该单位的一切行为完全按照你想要的方式，然后执行测试单位并且断言它的行为是正确的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repeatable&lt;/strong&gt; – 每次执行测试时都应该有相同的结果。 它不应该依赖于一些不确定的数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Self-validating&lt;/strong&gt; – 框架应该知道测试是否通过。 不应该有任何手动检查测试。 只要检查一切是否是绿色，就是这样:)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timely&lt;/strong&gt; – 测试应该和代码一样写，或者甚至在代码之前写！&lt;/p&gt;
&lt;p&gt;所以，我们制作了一个可测试的APP，我们知道如何测试。 那如何命名单元测试的名字呢？&lt;/p&gt;
&lt;h3 id=&quot;2-命名测试&quot;&gt;&lt;a href=&quot;#2-命名测试&quot; class=&quot;headerlink&quot; title=&quot;2. 命名测试&quot;&gt;&lt;/a&gt;2. 命名测试&lt;/h3&gt;&lt;p&gt;说实话，我们如何命名测试很重要。它直接反映了你对测试的态度，以及你想要测试什么的方式。&lt;/p&gt;
&lt;p&gt;让我们认识我们的受害者：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;DeleteFeedUseCase&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CompletableUseCaseWithParameter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Completable &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Integer feedId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;//implementation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;首先，幼稚的方法是编写像这样的测试：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;executeWhenDatabaseReturnsTrue&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;executeWithErrorInDatabase&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这被称为实现式命名。 它与类实现紧密结合。 当我们改变实施时，我们需要改变我们对类的期望。 这些通常是在代码之后编写的，关于它们唯一的好处是它们可以很快写入。&lt;/p&gt;
&lt;p&gt;第二种方式是示例式命名：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;doSomethingWithIdsSmallerThanZero&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ignoreWhenNullIsPassed&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;示例式测试是系统使用的示例。 它们在测试边缘案例时很好，但不要将它们用于所有事情，它们应该与实现相关联。&lt;/p&gt;
&lt;p&gt;现在，让我们尝试抽象我们对这个类的看法，并从实现中移开。 那这个呢：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;shouldDeleteExistingFeed&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;shouldIgnoreDeletingNonExistingFeed&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;我们确切地知道我们对这个类的期望。 这个测试类可以用作类的规范，因此可以使用名称规范式的命名。 名称没有说明实现的任何内容，并且从测试的名称 - 规范 - 我们可以编写实际的具体类。 规范样式的名称通常是最好的选择，但如果您认为您无法测试某些特定于实现的边缘案例，则可以随时抛出几个示例样式的测试。&lt;/p&gt;
&lt;p&gt;理论到此为止，我们准备好让我们的手变dirty！&lt;/p&gt;
&lt;h3 id=&quot;3-测试Domain&quot;&gt;&lt;a href=&quot;#3-测试Domain&quot; class=&quot;headerlink&quot; title=&quot;3. 测试Domain&quot;&gt;&lt;/a&gt;3. 测试Domain&lt;/h3&gt;&lt;p&gt;让我们看看我们如何测试用例。 我们的Reedley应用程序中的用例结构如下所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-6.png&quot; alt=&quot;Reedley App 3.1&quot;&gt;&lt;/p&gt;
&lt;p&gt;问题是EnableBackgroundFeedUpdatesUseCase是最终的，如果它是一些其他用例测试所需的模拟，则无法完成。 Mockito不允许嘲笑最终课程。&lt;/p&gt;
&lt;p&gt;用例被其实现引用，所以让我们添加另一层接口：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-7.png&quot; alt=&quot;Reedley App 3.2&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在我们可以模拟EnableBackgroundFeedUpdatesUseCase接口。 但在我们的日常实践中，我们得出结论，这在开发时非常混乱，中间层接口是空的，用例实际上并不需要接口。 用例只做一项工作，它在名称中说得很对 - “启用后台供稿更新用例”，没有什么可以抽象的！&lt;/p&gt;
&lt;p&gt;好的，让我们试试这个 - 我们不需要做最终用例。&lt;/p&gt;
&lt;p&gt;我们尽可能做最后的决定，它使得更多结构化和更优化的代码。 我们可以忍受用例不是最终的，但必须有更好的方法。&lt;/p&gt;
&lt;p&gt;我们找到了使用mockito-inline的解决方案。 它使得unmockable，mockable。 随着Mockito的新版本，可以启用最终classes的模拟。&lt;/p&gt;
&lt;p&gt;以下是用例实现的示例：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;EnableBackgroundFeedUpdatesUseCase&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CompletableUseCase&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedsUpdateScheduler feedsUpdateScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;//constructor&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Completable &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; setShouldUpdateFeedsInBackgroundUseCase.execute(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .concatWith(Completable.fromAction(feedsUpdateScheduler::scheduleBackgroundFeedUpdates));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;在测试用例时，我们应该测试该用例调用Repositories中的正确方法或执行其他用例。 我们还应该测试该用例返回适当的回调：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; EnableBackgroundFeedUpdatesUseCase enableBackgroundFeedUpdatesUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; FeedsUpdateScheduler feedUpdateScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; TestSubscriber testSubscriber;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Before&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  setShouldUpdateFeedsInBackgroundUseCase = Mockito.mock(SetShouldUpdateFeedsInBackgroundUseCase.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  feedUpdateScheduler = Mockito.mock(FeedsUpdateScheduler.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testSubscriber = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;TestSubscriber&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  enableBackgroundFeedUpdatesUseCase = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;EnableBackgroundFeedUpdatesUseCase&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    setShouldUpdateFeedsInBackgroundUseCase, &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    feedUpdateScheduler&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;shouldEnableBackgroundFeedUpdates&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(setShouldUpdateFeedsInBackgroundUseCase.execute(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(Completable.complete());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  enableBackgroundFeedUpdatesUseCase.execute()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .subscribe(testSubscriber);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verify(setShouldUpdateFeedsInBackgroundUseCase, Mockito.times(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .execute(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verifyNoMoreInteractions(setShouldUpdateFeedsInBackgroundUseCase);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verify(feedUpdateScheduler, Mockito.times(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .scheduleBackgroundFeedUpdates();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verifyNoMoreInteractions(feedUpdateScheduler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testSubscriber.assertCompleted();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;这里使用了来自Rx的 TestSubscriber ，因此可以测试适当的回调。 它可以断言完成，发射值，数值等。&lt;/p&gt;
&lt;h3 id=&quot;4-测试Data&quot;&gt;&lt;a href=&quot;#4-测试Data&quot; class=&quot;headerlink&quot; title=&quot;4. 测试Data&quot;&gt;&lt;/a&gt;4. 测试Data&lt;/h3&gt;&lt;p&gt;这里是非常简单的Repository方法，它只使用一个DAO方法：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedRepositoryImpl&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedRepository&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedDao feedDao;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Scheduler backgroundScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;//constructor&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Single &lt;span class=&quot;title function_&quot;&gt;feedExists&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; String feedUrl)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; Single.defer(() -&amp;gt; feedDao.doesFeedExist(feedUrl))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .subscribeOn(backgroundScheduler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;//more methods&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;在测试Repository时，我们应该测试它是否正确地使用了DAO方法，并且它是否在正确的线程上执行：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; FeedRepositoryImpl feedRepository;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; FeedDao feedDao;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; TestScheduler testScheduler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Before&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  feedDao = Mockito.mock(FeedDao.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testScheduler = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;TestScheduler&lt;/span&gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  feedRepository = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedRepositoryImpl&lt;/span&gt;(feedDao, testScheduler);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;shouldCheckIfFeedExists&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;feedUrl&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;http://example.com/feed&amp;quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(feedDao.doesFeedExist(feedUrl))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(Single.just(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; TestSubscriber&amp;lt;Boolean&amp;gt; testSubscriber = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;TestSubscriber&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  feedRepository.feedExists(feedUrl)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .subscribe(testSubscriber);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testSubscriber.assertNoValues();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testScheduler.triggerActions();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testSubscriber.assertValue(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  testSubscriber.assertCompleted();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verify(feedDao, Mockito.times(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .doesFeedExist(feedUrl);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verifyNoMoreInteractions(feedDao);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;5-测试-App-module&quot;&gt;&lt;a href=&quot;#5-测试-App-module&quot; class=&quot;headerlink&quot; title=&quot;5. 测试 App module&quot;&gt;&lt;/a&gt;5. 测试 App module&lt;/h3&gt;&lt;p&gt;最后，让我们看看如何测试Presenter。 这是一个简单的Presenter，它从用例获取文章并将它们传递给视图：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArticlesPresenter&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;BasePresenter&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArticlesContract&lt;/span&gt;.Presenter &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  GetArticlesUseCase getArticlesUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Inject&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  FeedViewModeMapper feedViewModeMapper;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// (...) more fields&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;ArticlesPresenter&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; ArticlesContract.View view)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;(view);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;fetchArticles&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; feedId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    viewActionQueue.subscribeTo(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      getArticlesUseCase.execute(feedId)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        .map(feedViewModeMapper::mapArticlesToViewModels)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        .map(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;::toViewAction),&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      Throwable::printStackTrace&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;comment&quot;&gt;// (...) more methods&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Presenter通常有很多依赖关系。 我们通过@Inject注释将依赖关系注入Presenter，而不是通过构造函数。 所以在下面的测试中，我们需要使用@Mock和@Spy注释：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Mock&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;GetArticlesUseCase getArticlesUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Mock&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;FeedViewModeMapper feedViewModeMapper;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Mock&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ConnectivityReceiver connectivityReceiver;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Mock&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ViewActionQueueProvider viewActionQueueProvider;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Spy&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;Scheduler&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;mainThreadScheduler&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; Schedulers.immediate();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Spy&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;MockViewActionQueue mockViewActionHandler;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@InjectMocks&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;ArticlesPresenter articlesPresenter;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;然后一些设置是必需的。 视图是手动模拟的，因为它是通过构造函数注入的，我们调用presenter.start()和presenter.activate()，因此演示程序已准备好并启动：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Before&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  view = Mockito.mock(ArticlesContract.View.class);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articlesPresenter = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArticlesPresenter&lt;/span&gt;(view);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  MockitoAnnotations.initMocks(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(connectivityReceiver.getConnectivityStatus())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(Observable.just(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(viewActionQueueProvider.queueFor(Mockito.any()))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;MockViewActionQueue&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articlesPresenter.start();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articlesPresenter.activate();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;一切准备就绪后，我们可以开始编写测试。 准备好所有内容并确保Presenter在需要时调用视图：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;28&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;29&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;30&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;31&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;32&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;33&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;34&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;35&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;36&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;37&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;38&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@Test&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;shouldFetchArticlesAndPassThemToView&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;throws&lt;/span&gt; Exception &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;feedId&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; AppTestData.TEST_FEED_ID;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; List&amp;lt;Article&amp;gt; articles = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArrayList&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;Article&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Article&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_ARTICLE_ID,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    feedId,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_STRING,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_LINK,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_LONG_DATE,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articles.add(article);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; List&amp;lt;ArticleViewModel&amp;gt; articleViewModels = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArrayList&lt;/span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;ArticleViewModel&lt;/span&gt; &lt;span class=&quot;variable&quot;&gt;articleViewModel&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ArticleViewModel&lt;/span&gt;(&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_ARTICLE_ID,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_STRING,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_LINK,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    AppTestData.TEST_STRING,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  );&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articleViewModels.add(articleViewModel);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(getArticlesUseCase.execute(feedId))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(Single.just(articles));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.&lt;span class=&quot;keyword&quot;&gt;when&lt;/span&gt;(feedViewModeMapper.mapArticlesToViewModels(Mockito.anyList()))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .thenReturn(articleViewModels);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  articlesPresenter.fetchArticles(feedId);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verify(getArticlesUseCase, Mockito.times(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .execute(feedId);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Mockito.verify(view, Mockito.times(&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;))&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    .showArticles(articleViewModels);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;总结&quot;&gt;&lt;a href=&quot;#总结&quot; class=&quot;headerlink&quot; title=&quot;总结&quot;&gt;&lt;/a&gt;总结&lt;/h3&gt;&lt;p&gt;在编码之前和期间考虑测试，这样你就可以编写可测试和解耦的代码。 使用你的测试作为类的规范，如果可能的话在代码之前写下它们。 不要让你的自我妨碍，我们都会犯错误。 因此，我们需要有一个流程来保护我们自己的应用程序！&lt;/p&gt;
&lt;p&gt;这是Android Architecture系列的一部分。 想查看我们的其他部分可以：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84%EF%BC%8C%E5%AE%9E%E8%B7%B5%EF%BC%88%E5%8C%85%E5%90%AB%E6%BA%90%E4%BB%A3%E7%A0%81%EF%BC%89-%E8%AF%91/&quot;&gt;Part 4: Applying Clean Architecture on Android (Hands-on)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84-%E8%AF%91/&quot;&gt;Part 3: Applying Clean Architecture on Android&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/&quot;&gt;Part 2: The Clean Architecture&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/&quot;&gt;Part 1: every new beginning is hard&lt;/a&gt; &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="Clean架构"/>
    
      <category term="译文"/>
    
      <category term="单元测试"/>
    
  </entry>
  
  <entry>
    <title>Android架构：第四部分-在Android上应用Clean架构，实践（包含源代码）(译)</title>
    <link href="https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84%EF%BC%8C%E5%AE%9E%E8%B7%B5%EF%BC%88%E5%8C%85%E5%90%AB%E6%BA%90%E4%BB%A3%E7%A0%81%EF%BC%89/"/>
    <id>https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84%EF%BC%8C%E5%AE%9E%E8%B7%B5%EF%BC%88%E5%8C%85%E5%90%AB%E6%BA%90%E4%BB%A3%E7%A0%81%EF%BC%89/</id>
    <published>2018-05-07T14:36:14.000Z</published>
    <updated>2025-09-22T09:18:12.568Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-1.png&quot; alt=&quot;clean architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;在Android Architecture系列的最后一部分，我们将Clean Architecture稍微调整到了Android平台。 我们将Android和现实世界从业务逻辑中分离出来，让满意的利益相关者满意，并让所有事情都可以轻松测试。&lt;/p&gt;
&lt;p&gt;这个理论很好，但是当我们创建一个新的Android项目时，我们从哪里开始？ 让我们用干净的代码弄脏我们的手，并将空白的画布变成一个架构。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h2 id=&quot;基础&quot;&gt;&lt;a href=&quot;#基础&quot; class=&quot;headerlink&quot; title=&quot;基础&quot;&gt;&lt;/a&gt;基础&lt;/h2&gt;&lt;p&gt;我们将首先奠定基础 - 创建模块并建立它们之间的依赖关系，以便与依赖规则保持一致。&lt;/p&gt;
&lt;p&gt;这些将是我们的模块，从最抽象的一个到具体的实现：&lt;/p&gt;
&lt;h3 id=&quot;1-domain&quot;&gt;&lt;a href=&quot;#1-domain&quot; class=&quot;headerlink&quot; title=&quot;1. domain&quot;&gt;&lt;/a&gt;1. domain&lt;/h3&gt;&lt;p&gt;Entities, use cases, repositories interfaces, 和 device interfaces 进入 domain module。&lt;/p&gt;
&lt;p&gt;理想情况下，实体和业务逻辑应该是平台不可知的。 为了安全起见，为了防止我们在这里放置一些Android的东西，我们将使它成为一个纯粹的Java模块。&lt;/p&gt;
&lt;h3 id=&quot;2-data&quot;&gt;&lt;a href=&quot;#2-data&quot; class=&quot;headerlink&quot; title=&quot;2. data&quot;&gt;&lt;/a&gt;2. data&lt;/h3&gt;&lt;p&gt;数据模块应包含与数据持久性和操作相关的所有内容。 在这里，我们将找到DAO，ORM，SharedPreferences，网络相关的东西，例如Retrofit服务和类似的东西。&lt;/p&gt;
&lt;h3 id=&quot;3-device&quot;&gt;&lt;a href=&quot;#3-device&quot; class=&quot;headerlink&quot; title=&quot;3. device&quot;&gt;&lt;/a&gt;3. device&lt;/h3&gt;&lt;p&gt;设备模块应该包含与Android相关的所有内容，而不是数据持久性和UI。 例如，ConnectivityManager，NotificationManager和misc传感器的包装类。&lt;/p&gt;
&lt;p&gt;我们将使数据和设备模块都是Android模块，因为他们必须了解Android并且不能是纯Java。&lt;/p&gt;
&lt;h3 id=&quot;4-The-easiest-part-app-module-UI-module&quot;&gt;&lt;a href=&quot;#4-The-easiest-part-app-module-UI-module&quot; class=&quot;headerlink&quot; title=&quot;4. The easiest part, app module (UI module)&quot;&gt;&lt;/a&gt;4. The easiest part, app module (UI module)&lt;/h3&gt;&lt;p&gt;创建项目时，Android模块已经为您创建了该模块。&lt;/p&gt;
&lt;p&gt;在这里，您可以放置与Android UI相关的所有类，例如presenters，controllers，view models，adapters和views。&lt;/p&gt;
&lt;h2 id=&quot;依赖&quot;&gt;&lt;a href=&quot;#依赖&quot; class=&quot;headerlink&quot; title=&quot;依赖&quot;&gt;&lt;/a&gt;依赖&lt;/h2&gt;&lt;p&gt;依赖规则定义具体模块依赖于更抽象的模块。&lt;/p&gt;
&lt;p&gt;您可能还记得，从本系列的第三部分可以看出，UI（应用程序），DB-API（数据）和Device（设备）等东西都在外环中。 这意味着它们处于相同的抽象层次。 我们如何将它们连接在一起呢？&lt;/p&gt;
&lt;p&gt;理想情况下，这些模块仅取决于域模块。 在这种情况下，依赖关系看起来有点像明星：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-2.png&quot; alt=&quot;clean architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;但是，我们在这里与Android打交道，事情并不完美。 因为我们需要创建对象图并初始化事物，所以模块有时依赖于domain以外的其他模块。&lt;/p&gt;
&lt;p&gt;例如，我们正在app模块中创建用于依赖注入的对象图。 这迫使APP模块了解所有其他模块。&lt;/p&gt;
&lt;p&gt;我们调整后的依赖关系图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-3.png&quot; alt=&quot;clean architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后，是时候编写一些代码。 为了更容易，我们将以RSS Reader APP为例。 我们的用户应该能够管理他们的RSS提要订阅，从提要中获取文章并阅读它们。&lt;/p&gt;
&lt;h2 id=&quot;Domain&quot;&gt;&lt;a href=&quot;#Domain&quot; class=&quot;headerlink&quot; title=&quot;Domain&quot;&gt;&lt;/a&gt;Domain&lt;/h2&gt;&lt;p&gt;让我们从domain层开始，创建我们的核心业务模型和逻辑。&lt;/p&gt;
&lt;p&gt;我们的商业模式非常简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Feed&lt;/em&gt; - 持有RSS提要相关数据，如网址，缩略图网址，标题和说明&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Article&lt;/em&gt; - 保存文章相关数据，如文章标题，网址和发布日期&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而对于我们的逻辑，我们将使用UseCases。 他们在简洁的类中封装了小部分业务逻辑。 他们都将实施通用的UseCase 契约类：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;P&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Callback&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Throwable throwable)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(P parameter, Callback callback)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;我们的用户在打开我们的应用时首先要做的就是添加一个新的RSS订阅。 因此，要开始使用我们的Use Case，我们将创建_AddNewFeedUseCase_及其助手来处理Feed的添加和验证逻辑。&lt;/p&gt;
&lt;p&gt;_AddNewFeedUseCase_将使用_FeedValidator_来检查Feed URL的有效性，并且我们还将创建FeedRepository 契约类，这将为我们的业务逻辑提供一些基本的CRUD功能来管理供稿数据：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedRepository&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;createNewFeed&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String feedUrl)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  List&amp;lt;Feed&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getUserFeeds&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  List&amp;lt;Article&amp;gt; &lt;span class=&quot;title function_&quot;&gt;getFeedArticles&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; feedId)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;type&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;deleteFeed&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; feedId)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;请注意我们在Domain层的命名是如何清楚地传递我们的APP正在做什么的想法。&lt;/p&gt;
&lt;p&gt;把所有东西放在一起，我们的_AddNewFeedUseCase_看起来像这样：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AddNewFeedUseCase&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;String&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedValidator feedValidator;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedRepository feedRepository;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; String feedUrl, &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Callback callback)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (feedValidator.isValid(feedUrl)) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      onValidFeedUrl(feedUrl, callback);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125; &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      callback.onError(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;InvalidFeedUrlException&lt;/span&gt;());&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onValidFeedUrl&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; String feedUrl, &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Callback callback)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      feedRepository.createNewFeed(feedUrl);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      callback.onSuccess();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125; &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Throwable throwable) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      callback.onError(throwable);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;ps:为简洁起见，省略构造函数。&lt;/p&gt;
&lt;p&gt;现在，您可能想知道，为什么我们的use case以及我们的回调是一个接口？&lt;/p&gt;
&lt;p&gt;为了更好地展示我们的下一个问题，让我们来研究_GetFeedArticlesUseCase_。&lt;/p&gt;
&lt;p&gt;它需要一个feedId -&amp;gt; 通过_FeedRespository_获取提要文章 -&amp;gt; 返回提要文章&lt;/p&gt;
&lt;p&gt;这里是数据流问题，用例介于表示层和数据层之间。 我们如何建立层之间的沟通？ 记住那些输入和输出端口？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-4.png&quot; alt=&quot;clean architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们的 Use Case必须实现输入端口（接口）。 Presenter在 Use Case上调用方法，数据流向 Use Case（feedId）。 Use Case映射feedId提供文章并希望将它们发送回表示层。 它有一个对输出端口（回调）的引用，因为输出端口是在同一层定义的，因此它调用了一个方法。 因此，数据发送到输出端口 - Presenter。&lt;/p&gt;
&lt;p&gt;我们将稍微调整我们的UseCase契约类：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;P, R&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Callback&lt;/span&gt;&amp;lt;R&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(R return)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Throwable throwable)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(P parameter, Callback&amp;lt;R&amp;gt; callback)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CompletableUseCase&lt;/span&gt;&amp;lt;P&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Callback&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Throwable throwable)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(P parameter, Callback callback)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;_UseCase_接口是输入端口，_Callback_接口是输出端口。&lt;/p&gt;
&lt;p&gt;现在我们可以实现我们的_GetFeedArticlesUseCase_：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;GetFeedArticlesUseCase&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;Integer, List&amp;lt;Article&amp;gt;&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedRepository feedRepository;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Integer feedId, &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Callback&amp;lt;List&amp;lt;Article&amp;gt;&amp;gt; callback)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      callback.onSuccess(feedRepository.getFeedArticles(feedId));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125; &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Throwable throwable) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      callback.onError(throwable);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;UI&quot;&gt;&lt;a href=&quot;#UI&quot; class=&quot;headerlink&quot; title=&quot;UI&quot;&gt;&lt;/a&gt;UI&lt;/h2&gt;&lt;p&gt;让我们看看表示层如何使用我们的用例。 我们将创建一个_FeedArticlesPresenter_，它将使用我们的用例来获取文章并将它们显示在视图中：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedArticlesPresenter&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;.Callback&amp;lt;List&amp;lt;Article&amp;gt;&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; GetFeedArticlesUseCase getFeedArticlesUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; ViewModelMapper viewModelMapper;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;fetchFeedItems&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; feedId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getFeedArticlesUseCase.execute(feedId, &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; List&amp;lt;Article&amp;gt; articles)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getView().showArticles(viewModelMapper.mapArticlesToViewModels(articles));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Throwable throwable)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getView().showErrorMessage();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;请注意，_FeedArticlesPresenter_实现了Callback接口，并将其自身传递给use case，它实际上是use case的输出端口，并以这种方式关闭了数据流。 这是我们前面提到的数据流的具体示例，我们可以在流程图上调整标签以匹配此示例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-5.png&quot; alt=&quot;clean architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们的参数P是feedId，返回类型R是文章列表。&lt;/p&gt;
&lt;p&gt;您不必使用Presenter来处理显示逻辑，我们可以说Clean架构是”前端”不可知的 - 这意味着您可以使用MVP，MVC，MVVM或其他任何东西。&lt;/p&gt;
&lt;h2 id=&quot;让我们在混合中抛出一些Rx&quot;&gt;&lt;a href=&quot;#让我们在混合中抛出一些Rx&quot; class=&quot;headerlink&quot; title=&quot;让我们在混合中抛出一些Rx&quot;&gt;&lt;/a&gt;让我们在混合中抛出一些Rx&lt;/h2&gt;&lt;p&gt;现在，如果你想知道为什么有这样的RxJava，我们将看看我们UseCase的反应式实现：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;P, R&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Single&amp;lt;R&amp;gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(P parameter)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;CompletableUseCase&lt;/span&gt;&amp;lt;P&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  Completable &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(P parameter)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;回调接口现在不见了，我们使用RxJava Single &amp;#x2F; Completable接口作为我们的输出端口。&lt;/p&gt;
&lt;p&gt;Reactive &lt;em&gt;GetFeedArticlesUseCase&lt;/em&gt;：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;GetFeedArticlesUseCase&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UseCase&lt;/span&gt;&amp;lt;Integer, List&amp;lt;Article&amp;gt;&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; FeedRepository feedRepository;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; Single&amp;lt;List&amp;lt;Article&amp;gt;&amp;gt; &lt;span class=&quot;title function_&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Integer feedId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; feedRepository.getFeedArticles(feedId);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;Reactive &lt;em&gt;FeedArticlePresenter&lt;/em&gt; 如下：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;FeedArticlesPresenter&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; GetFeedArticlesUseCase getFeedArticlesUseCase;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; ViewModeMapper viewModelMapper;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;fetchFeedItems&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; feedId)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getFeedItemsUseCase.execute(feedId)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .map(feedViewModeMapper::mapFeedItemsToViewModels)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .subscribeOn(Schedulers.io())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .observeOn(AndroidSchedulers.mainThread())&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;      .subscribe(&lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;::onSuccess, &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;::onError);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onSuccess&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; List articleViewModels)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getView().showArticles(articleViewModels);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; Throwable throwable)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    getView().showErrorMessage();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;  &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;虽然它有点隐藏，但同样的数据流反演原理仍然存在，因为没有RxJava Presenters实现回调，并且RxJava订阅者也包含在外层 - 在Presenter的某处。&lt;/p&gt;
&lt;h2 id=&quot;Data-和-Device&quot;&gt;&lt;a href=&quot;#Data-和-Device&quot; class=&quot;headerlink&quot; title=&quot;Data 和 Device&quot;&gt;&lt;/a&gt;Data 和 Device&lt;/h2&gt;&lt;p&gt;Data和Device包含业务逻辑不关心的所有实现细节。 它只关心契约类，允许您轻松测试它并在不触及业务逻辑的情况下交换实施。&lt;/p&gt;
&lt;p&gt;在这里，您可以使用自己喜欢的ORM或DAO在本地存储数据，并使用网络服务从网络获取数据。 我们将实现_FeedService_来获取文章，并使用_FeedDao_将文章数据存储在设备上。&lt;/p&gt;
&lt;p&gt;每个数据源（网络和本地存储）都将有自己的模型可供使用。&lt;/p&gt;
&lt;p&gt;在我们的例子中，它们是_ApiFeed_ - &lt;em&gt;ApiArticle_和_DbFeed&lt;/em&gt; - &lt;em&gt;DbArticle&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;_FeedRepository_的具体实现也可以在Data模块中找到。&lt;/p&gt;
&lt;p&gt;Device模块将持有作为_NotificationManager_类的包装的通知合同的实现。 我们也许可以使用业务逻辑中的通知来在用户可能感兴趣并推动参与的新文章发布时向用户显示通知。&lt;/p&gt;
&lt;h2 id=&quot;Models-models-everywhere&quot;&gt;&lt;a href=&quot;#Models-models-everywhere&quot; class=&quot;headerlink&quot; title=&quot;Models, models everywhere.&quot;&gt;&lt;/a&gt;Models, models everywhere.&lt;/h2&gt;&lt;p&gt;您可能已经注意到我们提到的不仅仅是实体或业务模型，还有更多的模型。&lt;/p&gt;
&lt;p&gt;实际上，我们也有db模型，API模型，View模型，当然还有业务模型。&lt;/p&gt;
&lt;p&gt;对于每个图层来说，都有一个很好的实践，可以使用它自己的模型，因此具体的细节（如View）不依赖于较低层实现的具体细节。 这样，例如，如果您决定从一个ORM更改为另一个，则不必分解不相关的代码。&lt;/p&gt;
&lt;p&gt;为了实现这一点，有必要在每个图层中使用对象映射器。 在示例中，我们使用_ViewModelMapper_将Domain 里的_Article_模型映射到_ArticleViewModel_。&lt;/p&gt;
&lt;h2 id=&quot;总结&quot;&gt;&lt;a href=&quot;#总结&quot; class=&quot;headerlink&quot; title=&quot;总结&quot;&gt;&lt;/a&gt;总结&lt;/h2&gt;&lt;p&gt;遵循这些准则，我们创建了一个强大且多功能的架构。 起初，它可能看起来像很多代码，它有点像，但请记住，我们正在构建我们的架构以适应未来的变化和功能。 如果你做得对，未来你会感恩。&lt;/p&gt;
&lt;p&gt;在下一部分中，我们将会介绍这个架构中最重要的部分，可测试性以及如何测试它。&lt;/p&gt;
&lt;p&gt;那么，在此期间，您最感兴趣的是架构实现的哪一部分？&lt;/p&gt;
&lt;p&gt;这是Android Architecture系列的一部分。 检查我们的其他部分：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNS10ZXN0LWNsZWFuLWFyY2hpdGVjdHVyZS8=&quot;&gt;Part 5: How to Test Clean Architecture&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84-%E8%AF%91/&quot;&gt;Part 3: Applying Clean Architecture on Android&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/&quot;&gt;Part 2: The Clean Architecture&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/&quot;&gt;Part 1: every new beginning is hard&lt;/a&gt; &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="Clean架构"/>
    
      <category term="译文"/>
    
  </entry>
  
  <entry>
    <title>Android架构：第三部分-在Android上应用Clean架构 (译)</title>
    <link href="https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84/"/>
    <id>https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86-%E5%9C%A8Android%E4%B8%8A%E5%BA%94%E7%94%A8Clean%E6%9E%B6%E6%9E%84/</id>
    <published>2018-05-07T14:36:13.000Z</published>
    <updated>2025-09-22T09:18:12.567Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;到目前为止，在这个系列中，我们已经介绍了一些初学者的错误，并通过了Clean架构。 在最后一部分中，我们将介绍拼图的最后一部分：标签，或者更确切地说：组件。&lt;/p&gt;
&lt;p&gt;首先，我将删除我们在Android项目中不使用的东西，然后添加一些我们使用的东西，但在原始的Bob叔叔图中找不到。 它看起来像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-1.png&quot; alt=&quot;clean architecture - 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;我会从最抽象的中心走到边缘。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h2 id=&quot;Entities&quot;&gt;&lt;a href=&quot;#Entities&quot; class=&quot;headerlink&quot; title=&quot;Entities&quot;&gt;&lt;/a&gt;Entities&lt;/h2&gt;&lt;p&gt;Entities，即&lt;strong&gt;Domain 对象&lt;/strong&gt;或业务对象，它们是App的核心。 它们代表了APP的主要功能，您应该能够通过查看它们来了解APP的功能。 它们包含业务逻辑，但仅限于它们 - 验证和类似的东西。 他们不会与外在世界的细节进行互动，也不会处理持久性。 例如，如果您有新闻APP，则这些实体将是类别，文章和商业广告。&lt;/p&gt;
&lt;h2 id=&quot;Use-cases&quot;&gt;&lt;a href=&quot;#Use-cases&quot; class=&quot;headerlink&quot; title=&quot;Use cases&quot;&gt;&lt;/a&gt;Use cases&lt;/h2&gt;&lt;p&gt;Use case，又名interactors，又名business services，是Entities的扩展，是业务逻辑的延伸，也就是说。 它们包含的逻辑不仅限于一个实体，而是处理更多的实体。 一个好Use case的一个指标是，你可以用一种通用的语言描述它在一个简单的句子中的作用 - 例如，”从一个账户转移到另一个账户”。你甚至可以用这样的命名来命名该类，例如，TransferMoneyUseCase。&lt;/p&gt;
&lt;h2 id=&quot;Repositories&quot;&gt;&lt;a href=&quot;#Repositories&quot; class=&quot;headerlink&quot; title=&quot;Repositories&quot;&gt;&lt;/a&gt;Repositories&lt;/h2&gt;&lt;p&gt;Repositories用于坚持Entities。 就这么简单。 它们被定义为接口并用作想要对Entities执行CRUD操作的用例的输出端口。 另外，它们可以公开一些与持久性相关的更复杂的操作，例如过滤，聚合等。 具体的持久性策略，例如数据库或因特网，是在外层实现的。 例如，您可以命名接口AccountRepository。&lt;/p&gt;
&lt;h2 id=&quot;Presenters&quot;&gt;&lt;a href=&quot;#Presenters&quot; class=&quot;headerlink&quot; title=&quot;Presenters&quot;&gt;&lt;/a&gt;Presenters&lt;/h2&gt;&lt;p&gt;如果你熟悉MVP模式，Presenters就会做你期望他们做的事情。 他们处理用户交互，调用适当的业务逻辑，并将数据发送到UI进行渲染。 这里通常有不同类型的模型之间的映射。 有些人会在这里使用Controller，这很好。 我们使用的Presenter正式被称为监督Controller。 我们通常在每个屏幕上定义一个或两个Presenters，具体取决于屏幕，Presenter的生命周期与视图的生命周期相关联。 一条建议：尝试在Presenter上将您的方法命名为技术不可知论者（technology agnostic）。 假设您不知道视图实现的技术。因此，如果您在视图中具有名为onSubmitOrderButtonClicked和onUserListItemSelected的方法，则处理这些事件的相应演示者方法可以命名为submitOrder和selectUser。&lt;/p&gt;
&lt;h2 id=&quot;Device&quot;&gt;&lt;a href=&quot;#Device&quot; class=&quot;headerlink&quot; title=&quot;Device&quot;&gt;&lt;/a&gt;Device&lt;/h2&gt;&lt;p&gt;这个组件在通知（再次）例子中已经被取笑了。它包含了诸如传感器，警报，通知，播放器，各种Managers等Android提供东西的实现。它是一个由两部分组成的部分。第一部分是业务逻辑用作与外部世界进行通信的输出端口的内部圈定义的接口。第二部分，这是在图中绘制的，是这些接口的实现。因此，您可以定义例如名为Gyroscope，Alarm，Notifications和Player的接口。注意名称是抽象的和技术不可知（technology agnostic）的。业务逻辑不关心如何显示通知，玩家如何播放声音，或陀螺仪数据来自何处。您可以制作将通知写入终端，将声音数据写入日志或从预定义文件收集陀螺仪数据的实现。这样的实现对于调试或创建确定性环境是非常有用的。但是，您当然必须制作诸如AndroidAlarm，NativePlayer等实现。在大多数情况下，这些实现只是围绕Android的Manager 类的包装。&lt;/p&gt;
&lt;h2 id=&quot;DB-API&quot;&gt;&lt;a href=&quot;#DB-API&quot; class=&quot;headerlink&quot; title=&quot;DB &amp;amp; API&quot;&gt;&lt;/a&gt;DB &amp;amp; API&lt;/h2&gt;&lt;p&gt;将存储库的实现放在这个组件中。所有这些底层持久化的东西应该在这里：DAO，ORM的东西，Retrofit（或别的东西）的东西，JSON解析等。你也可以在这里实现缓存策略，或者只是使用内存中的持久性，直到完成与应用程序的其余部分。我们最近在团队中进行了一次有趣的讨论。问题是这样的：资源库是否应公开诸如fetchUsersOffline（fetchUsersFromCache）和fetchUsersOnline（fetchUsersFromInternet）之类的方法？换句话说，业务逻辑应该知道数据来自哪里？看过这篇文章的所有内容后，答案很简单：不。但是有一个问题！如果关于数据源的决定是业务逻辑的一部分 - 例如，如果用户可以选择它，或者如果您有明确的离线模式的应用程序，那么您可以添加这样的区别。但是我不会为每个抓取定义两种方法。我可能会在资源库中公开方法，如enterOfflineMode和exitOfflineMode。或者，如果它适用于所有存储库，则可以使用输入和退出方法定义OfflineMode接口，并在业务逻辑端使用它，让存储库查询该模式并在内部进行确定。&lt;/p&gt;
&lt;h2 id=&quot;UI&quot;&gt;&lt;a href=&quot;#UI&quot; class=&quot;headerlink&quot; title=&quot;UI&quot;&gt;&lt;/a&gt;UI&lt;/h2&gt;&lt;p&gt;将与Android用户界面相关的所有内容放在这里 Activities，fragments，views，adapters等完成。&lt;/p&gt;
&lt;h2 id=&quot;Modules&quot;&gt;&lt;a href=&quot;#Modules&quot; class=&quot;headerlink&quot; title=&quot;Modules&quot;&gt;&lt;/a&gt;Modules&lt;/h2&gt;&lt;p&gt;下图显示了我们如何将所有这些组件分成Android Studio模块。 你可能会发现另一个更合适的部门。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-2.png&quot; alt=&quot;clean architecture - 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们将entities，use cases，repositories和device接口分组到domain模块中。 如果您想获得额外的挑战，并获得永恒的荣耀和完全干净的设计，您可以使该模块成为纯Java模块。 它会阻止你在这里使用快捷方式并将相关的东西放在Android上。&lt;/p&gt;
&lt;p&gt;device模块应该包含与Android相关的所有内容，而不是数据持久性和UI。 正如我们已经说过的，data模块应该包含与数据持久性相关的所有内容。 你不能让这两个人进入Java模块，因为他们需要访问各种Android的东西。 你可以将它们变成Android库。&lt;/p&gt;
&lt;p&gt;最后，我们将与UI相关的所有内容（包括presenters）分组到UI模块中。 您可以明确地命名为UI，但由于Android中的所有东西，我们将其命名为”app”，就像Android Studio在创建项目时命名的那样。&lt;/p&gt;
&lt;h2 id=&quot;这样更好些了吗？&quot;&gt;&lt;a href=&quot;#这样更好些了吗？&quot; class=&quot;headerlink&quot; title=&quot;这样更好些了吗？&quot;&gt;&lt;/a&gt;这样更好些了吗？&lt;/h2&gt;&lt;p&gt;为了回答这个问题，我会抛弃Bob叔叔的图表，并将之前描述的组件扩展到像我们用来评估以前类型的体系结构的图表。 这样做后，我们得到这个：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-3.png&quot; alt=&quot;clean architecture - 3&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在让我们应用我们在以前的体系结构中使用的相同条件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-4.png&quot; alt=&quot;clean architecture - 4&quot;&gt;&lt;/p&gt;
&lt;p&gt;它完全由模块级别，封装级别和类级别分开。 所以SRP应该得到满足。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-5.png&quot; alt=&quot;clean architecture - 5&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们尽可能将Android和现实世界推到了最外面。 业务逻辑不再直接接触Android。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-6.png&quot; alt=&quot;clean architecture - 6&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们有很好的分离类，很容易测试。 接触世界的类可以使用Android测试用例进行测试; 可以使用JUnit测试它。 有人恶意可能会称这类爆炸。 我称之为可测试的。:)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-7.png&quot; alt=&quot;clean architecture - 7&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;Clean架构：它可能很复杂-但它是值得的&quot;&gt;&lt;a href=&quot;#Clean架构：它可能很复杂-但它是值得的&quot; class=&quot;headerlink&quot; title=&quot;Clean架构：它可能很复杂 - 但它是值得的&quot;&gt;&lt;/a&gt;Clean架构：它可能很复杂 - 但它是值得的&lt;/h2&gt;&lt;p&gt;我希望我精心挑选的标准并不是为了支持Clean架构而编造的，它会让你尝试一下。 这看起来很复杂，而且这里有很多细节，&lt;strong&gt;但它是值得的。&lt;/strong&gt; 一旦你将所有东西连接起来，测试就容易得多，错误更容易隔离，新特性易于添加，代码更具可读性和可维护性，一切正常，世界满意。&lt;/p&gt;
&lt;p&gt;所以，就是这样。 如果您还没有这样做，请查看以前的系列文章：&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/&quot;&gt;Mistakes&lt;/a&gt; 和 &lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/&quot;&gt;Clean Architecture&lt;/a&gt;。 如果您有任何意见或问题，请在下面留言。 我们总是有兴趣听到你的想法。&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNC1hcHBseWluZy1jbGVhbi1hcmNoaXRlY3R1cmUtb24tYW5kcm9pZC1oYW5kcy1vbi8=&quot;&gt;阅读我们的Android Architecture系列的第四部分&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;您还可以查看其他部分：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNS10ZXN0LWNsZWFuLWFyY2hpdGVjdHVyZS8=&quot;&gt;Part 5: How to Test Clean Architecture&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/&quot;&gt;Part 2: The Clean Architecture&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/&quot;&gt;Part 1: every new beginning is hard&lt;/a&gt; &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="Clean架构"/>
    
      <category term="译文"/>
    
  </entry>
  
  <entry>
    <title>Android架构：第二部分-The clean architecture (译)</title>
    <link href="https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/"/>
    <id>https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86-The-clean-architecture-%E8%AF%91/</id>
    <published>2018-05-07T14:36:12.000Z</published>
    <updated>2025-09-22T09:18:12.567Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;在系列的第一部分中，我们介绍了我们在寻找可行的体系结构方面所犯的错误。 在这一部分中，我们将介绍所谓的&lt;strong&gt;Clean Architecture&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;你在google”clean architecture”中遇到的第一个图像是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-1.png&quot; alt=&quot;the clean architecture &quot;&gt;&lt;/p&gt;
&lt;p&gt;它也被称为&lt;strong&gt;洋葱架构&lt;/strong&gt;，因为图表看起来像一个洋葱（当你意识到你需要写多少样板时，它会让你哭泣）; 或端口和适配器，因为您可以看到右下角有一些端口。 &lt;strong&gt;六角形架构&lt;/strong&gt;是另一种类似的架构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean architecture&lt;/strong&gt;是前面提到的Bob叔叔的心血结晶，他也写了关于Clean Code和Clean Coder的书籍。 这种方法的主要观点是业务逻辑（也称为domain）处于宇宙的中心。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h2 id=&quot;Domain&quot;&gt;&lt;a href=&quot;#Domain&quot; class=&quot;headerlink&quot; title=&quot;Domain&quot;&gt;&lt;/a&gt;Domain&lt;/h2&gt;&lt;p&gt;当你打开你的项目，你应该已经知道这个APP是什么。 其他一切都是实现细节。 例如：持久性 - 这是一个细节。 定义一个接口，使内存实现一个快速而肮脏的实现，在业务完成之前不要考虑它。 然后，您可以决定如何确实坚持数据。 数据库，互联网，组合，文件系统 - 也许把它们留在内存中，也许事实证明你根本不必坚持它们。 在一句话中：内层包含业务逻辑，外层包含实现细节。&lt;/p&gt;
&lt;p&gt;这就是说，有一些Clean Architecture可以实现这些功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Dependency rule（依赖规则）&lt;/li&gt;
&lt;li&gt;Abstraction（抽象）&lt;/li&gt;
&lt;li&gt;Communication between layers（层之间的通信）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;I-Dependency-rule（依赖规则）&quot;&gt;&lt;a href=&quot;#I-Dependency-rule（依赖规则）&quot; class=&quot;headerlink&quot; title=&quot;I. Dependency rule（依赖规则）&quot;&gt;&lt;/a&gt;I. Dependency rule（依赖规则）&lt;/h2&gt;&lt;p&gt;依赖关系规则可以用下图来解释：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-2.png&quot; alt=&quot;graph-2&quot;&gt;&lt;/p&gt;
&lt;p&gt;外层应该取决于内层。 红色方块中的这三个箭头表示相关性。 “依赖”也许这是最好用的术语，如”看”，”知道”，或者”知道的。”没那么好。在这方面，外层看到并了解内层，但内层既不看也不知道外层。 如前所述，内层包含业务逻辑，外层包含实现细节。 结合依赖关系规则，业务逻辑既看不到，也不知道实现细节。 这正是我们正在努力完成的。&lt;/p&gt;
&lt;p&gt;您如何实现依赖关系规则取决于您。 您可以将它放在不同的包中，但要小心不要使用”内部”包装中的”外层”包装。 但是，如果有人没有意识到依赖性原则，没有什么能阻止他们打破它。 例如，更好的方法是将图层分成不同的Android模块，并调整构建文件中的依赖关系，以便内层无法使用外层。 在Five，我们使用了两者之间的东西。&lt;/p&gt;
&lt;p&gt;值得一提的是，尽管没有人可以阻止你跳过图层 ，例如，在蓝色图层组件中使用红色图层组件。我强烈建议您仅访问图层旁边的组件。&lt;/p&gt;
&lt;h2 id=&quot;II-Abstraction（抽象）&quot;&gt;&lt;a href=&quot;#II-Abstraction（抽象）&quot; class=&quot;headerlink&quot; title=&quot;II. Abstraction（抽象）&quot;&gt;&lt;/a&gt;II. Abstraction（抽象）&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;抽象原则&lt;/strong&gt;已经暗示过。 当你走向图的中间时，东西变得更加抽象。 这是有道理的：正如我们所说的内圈包含业务逻辑，外圈包含实现细节。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-3.png&quot; alt=&quot;clean android architecture &quot;&gt;&lt;/p&gt;
&lt;p&gt;您甚至可以在多个图层之间划分相同的逻辑组件，如上图所示： 在内层中可以定义更抽象的部分，外层中更具体的部分。&lt;/p&gt;
&lt;p&gt;一个例子会说清楚：我们可以将抽象接口定义为”通知”并将其放入内层，这样，您的业务逻辑就可以使用它来向用户显示通知。 另一方面，我们可以通过实现使用Android通知管理器显示通知的方式来实现该接口，然后将该实现放入外层。&lt;/p&gt;
&lt;p&gt;通过这种方式，业务逻辑可以在我们的示例中使用功能通知 - 但它不知道实现细节的任何内容：实际通知是如何实现的。 而且，业务逻辑甚至不知道实现细节存在。 看看下面的图片：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-4.png&quot; alt=&quot;clean android architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;将抽象与依赖规则结合起来时，事实证明，使用通知的抽象业务逻辑既没有看到也没有意识到使用Android通知管理器的具体实现。 这很好，因为我们可以切换具体实现，业务逻辑甚至不会注意到它。&lt;/p&gt;
&lt;p&gt;让我们简单地比较一下使用标准三层体系结构时抽象和依赖关系的外观和工作方式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-5.png&quot; alt=&quot;graph-5&quot;&gt;&lt;/p&gt;
&lt;p&gt;您可以在图中看到标准三层体系结构中的所有依赖关系都转到数据库。 这意味着抽象和依赖不匹配。 从逻辑上讲，业务层应该是应用程序的中心，但不是，因为依赖关系会转向数据库。&lt;/p&gt;
&lt;p&gt;业务层不应该知道数据库。 而在清洁架构中，依赖关系转到业务层（内层），而抽象层也向业务层上升，因此它们匹配得很好。&lt;/p&gt;
&lt;p&gt;这很重要，因为抽象是理论，依赖是实践。 抽象是应用程序的逻辑布局，依赖关系是它如何组合在一起。 在 clean architecture中，这两者匹配，而在标准的三层架构中则没有; 如果你不小心，这可能会很快导致各种逻辑不一致和混乱。&lt;/p&gt;
&lt;h2 id=&quot;III-Communication-between-layers（层之间的通信）&quot;&gt;&lt;a href=&quot;#III-Communication-between-layers（层之间的通信）&quot; class=&quot;headerlink&quot; title=&quot;III. Communication between layers（层之间的通信）&quot;&gt;&lt;/a&gt;III. Communication between layers（层之间的通信）&lt;/h2&gt;&lt;p&gt;现在我们已经将应用程序划分为模块，很好地分离了所有内容，将业务逻辑放在应用程序的中心和郊区的实现细节中，一切看起来都很棒。 但是你可能很快遇到了一个有趣的问题。&lt;/p&gt;
&lt;p&gt;如果你的UI是一个实现细节，那么internet就是一个实现细节，业务逻辑介于两者之间，&lt;strong&gt;我们如何从internet获取数据，通过业务逻辑传递它，然后将其发送到屏幕？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;业务逻辑处于中间，应该在互联网和用户界面之间进行调解，但它甚至不知道这两个人存在。 这是一个沟通和数据流的问题。&lt;/p&gt;
&lt;p&gt;我们希望数据能够从外层流向内层，反之亦然，但依赖性规则不允许这样做。 让我们将其简化为最简单的例子:&lt;/p&gt;
&lt;p&gt;我们只有两层，绿色和红色。 绿色的是外在的，知道红色的，红色的是内在的，只知道自己。 我们希望数据从绿色流向红色流，然后返回绿色流。 该解决方案之前已经被暗示过，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-6.png&quot; alt=&quot;clean android architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;右下角的图部分显示了数据流。 数据从Controller通过Use Case（或用用户选择的组件替换用例）输入端口，然后通过Use Case本身，然后通过Use Case输出端口返回给Presenter。&lt;/p&gt;
&lt;p&gt;图的主要部分上的箭头表示组成和继承 - 组成和继承 - 组成由一个实心箭头表示，继承由一个一个空箭头表示。 组成也被称为&lt;strong&gt;有关系&lt;/strong&gt;，而继承是&lt;strong&gt;一种关系&lt;/strong&gt;。 圆圈中的”I”和”O”表示输入和输出端口。 可以看出，在绿色层中定义的控制器具有在红色层中定义的输入端口。 Use Case（齿轮，商业逻辑，现在不重要）是（或实现）输入端口并具有输出端口。 最后，在绿色层中定义的Presenter实际上是在红色层中定义的输出端口。&lt;/p&gt;
&lt;p&gt;我们现在可以将其与数据流相匹配。 Controller有一个输入端口 - 它实际上有一个参考。 它调用一个方法，以便数据从Controller传输到输入端口。 但是输入端口是一个接口，实际的实现就是Use Case：所以它在一个Use Case上调用一个方法，数据流向Use Case。 Use Case做了一些事情，并希望将数据返回。 它有一个对输出端口的引用 - 因为输出端口是在同一层定义的 - 所以它可以调用它的方法。 因此，数据进入输出端口。 最后，Presenter是或实现输出端口; 这是神奇的一部分。 由于它实现了输出端口，数据实际上流入了它。&lt;/p&gt;
&lt;p&gt;诀窍是Use Case只知道它的输出端口; 世界在这个输出端口结束。 实施它取决于Presenter， 它可能已经被任何东西实现了，因为用例不知道或关心，并且只知道其层中的小世界。 我们可以看到，通过组合和继承的组合，我们可以使数据在两个方向上流动，尽管内层不知道他们正在与外部世界进行通信。 快速浏览下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Graph-7.png&quot; alt=&quot;clean android architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;你可以看到两个箭头都指向中间，就像依赖箭头一样。 那么，这是合乎逻辑的。 根据依赖规则，这是唯一可行的方法。 外层可以看到内层，但不是其他方式。 唯一棘手的部分是，这是一种关系，虽然它指向中间，但会颠倒数据流。&lt;/p&gt;
&lt;p&gt;请注意，定义其输入和输出端口是内层的责任，以便外层可以使用它们与之建立通信。 我已经说过这个解决方案已经在之前被暗示了，而且已经有了。 说明抽象的通知示例也是这种通信的一个例子。 我们在内层中定义了一个通知接口，业务逻辑可以使用它向用户显示通知，但我们也有一个在外层中定义的实现。 在这种情况下，通知接口是业务逻辑的输出端口，它用于与外部世界进行通信 - 本例中的具体实现。 您不必为您的类命名FooOutputPort或BarInputPort; 我们命名只是为了解释这个理论。&lt;/p&gt;
&lt;h2 id=&quot;总结&quot;&gt;&lt;a href=&quot;#总结&quot; class=&quot;headerlink&quot; title=&quot;总结&quot;&gt;&lt;/a&gt;总结&lt;/h2&gt;&lt;p&gt;那么，这是过于复杂，过度模糊的过度工程？ 好吧，当你习惯它时很简单。 这是必要的。 它使我们能够在现实世界中实现良好的抽象&amp;#x2F;依赖匹配实际交流和工作。 也许这一切都让人联想到弦理论：理论上美观，但是过于复杂，我们仍然不知道它是否有效，但在我们的案例中 - 它确实有效。:)&lt;/p&gt;
&lt;p&gt;这就是这个系列的第二部分。 最后，第三部分，毕竟我们已经了解了理论和体系结构，将涵盖所有你需要了解的关于这些图表上的标签，或者换句话说 - 单独的组件。 我们将向您展示在Android上应用的真实生活Clean Architecture。&lt;/p&gt;
&lt;p&gt;这是Android体系结构博客文章系列的一部分。您还可以查看其他部分：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNS10ZXN0LWNsZWFuLWFyY2hpdGVjdHVyZS8=&quot;&gt;Part 5: How to Test Clean Architecture&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNC1hcHBseWluZy1jbGVhbi1hcmNoaXRlY3R1cmUtb24tYW5kcm9pZC1oYW5kcy1vbi8=&quot;&gt;Part 4: Applying Clean Architecture on Android (Hands-on)&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtMy1hcHBseWluZy1jbGVhbi1hcmNoaXRlY3R1cmUtYW5kcm9pZC8=&quot;&gt;Part 3: Applying Clean Architecture on Android&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtMS1ldmVyeS1uZXctYmVnaW5uaW5nLWlzLWhhcmQv&quot;&gt;Part 1: every new beginning is hard&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="Clean架构"/>
    
      <category term="译文"/>
    
  </entry>
  
  <entry>
    <title>Android架构：第一部分-每个新的开始都很艰难 (译)</title>
    <link href="https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/"/>
    <id>https://dimon94.github.io/2018/05/07/Android%E6%9E%B6%E6%9E%84%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86-%E6%AF%8F%E4%B8%AA%E6%96%B0%E7%9A%84%E5%BC%80%E5%A7%8B%E9%83%BD%E5%BE%88%E8%89%B0%E9%9A%BE-%E8%AF%91/</id>
    <published>2018-05-07T14:36:11.000Z</published>
    <updated>2025-09-22T09:18:12.567Z</updated>
    
    <content type="html"><![CDATA[&lt;p&gt;本系列文章的目标是概述我们与Android应用程序体系结构（Android体系结构）的斗争。我意识到，无论Android应用程序架构的实施可能会如此痛苦，事实证明，这是我一直在努力的每一个优秀应用程序的基础。&lt;/p&gt;
&lt;p&gt;每项技术都有其自然演变。或者更准确地说，它的社区经历了进化过程。早期采用新计算机语言或框架的用户只是希望掌握技术并尽快完成一些工作。通常情况下，新社区很小，开发人员之间知识转移的潜力有限，也就是说，由于没有可用的架构指导原则，每个人都从自己的错误中学习。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Illustration_1.png&quot; alt=&quot;Android architecture part 1&quot;&gt;&lt;/p&gt;
&lt;p&gt;早期的Android并不例外。这对开发人员来说既酷又有吸引力，它为早期采用者成为围绕这一快速发展技术的社区的一部分提供了机会。&lt;/p&gt;
&lt;span id=&quot;more&quot;&gt;&lt;/span&gt;

&lt;h2 id=&quot;早期Android时代的痛点：谷歌是否关心？&quot;&gt;&lt;a href=&quot;#早期Android时代的痛点：谷歌是否关心？&quot; class=&quot;headerlink&quot; title=&quot;早期Android时代的痛点：谷歌是否关心？&quot;&gt;&lt;/a&gt;早期Android时代的痛点：谷歌是否关心？&lt;/h2&gt;&lt;p&gt;你可以争辩说，那里有许多资深人士，他们拥有很多不同技术的经验，并且很快就会提出标准。嗯，不一定。如果这个技术背后有一个强大的公司可以赚钱，他们肯定会有自己的技术传播者，他们会传播关于这种新语言如何酷的文字，并且可以用它做很多事情，这很容易学习，扩展并满足数百万客户的需求。&lt;/p&gt;
&lt;p&gt;微软经常用它的技术做到这一点。另一方面，当谷歌购买Android时，我诚实地认为他们将其视为一些其他方面的项目。如果您从一开始就一直处于Android世界，那么您必须记住Google根本不关心您的挫折感和感受。那些拥有更多经验，能力和帮助社区的意愿的家伙现在已经成为Android超级巨星或者小半神 - 例如Jake Wharton。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“When Google bought Android, I honestly think they treated it just as some other side project.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/xAmvxi.gif&quot; alt=&quot;xamvxi&quot;&gt;&lt;/p&gt;
&lt;p&gt;你可以说的另一件事是你不必考虑很多关于代码的架构和组织，因为框架为你做了。Android强制你将你的屏幕显示放入Activities中，将可重复使用的屏幕材料分解成Fragments，并将后台的东西放入Services中，并使用Broadcast Receivers与其他人交谈，因此它使您的生活变得更加美好。对？&lt;strong&gt;没有&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;首先，不管技术如何，都有一些很好的做法和原则。例如单一责任原则或依赖倒置原则，面向接口编程，杀死全局状态，试图谋杀所有状态等等。&lt;/p&gt;
&lt;p&gt;**框架很少迫使你遵循原则。**相反，他们以最糟糕的方式违反了原则。想想Context，你必须在任何地方使用的上帝对象，各种单例，Fragments的生命周期（那是噩梦般的生活），AsyncTasks通常被错误地实现，所以他们吸食记忆，食物，水，并从您的APP中散发出来。&lt;/p&gt;
&lt;p&gt;对于没有指南的年轻开发人员来说，制造怪物比制造APP简单。把它想象成一个意大利面般黑洞般的怪物-它作为一个Pastafarian神只是一个很好的选择，但作为APP并不好。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Illustration_2.png&quot; alt=&quot;android architecture spaghetti monster&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后，**技术和框架隐藏了APP的目的。**好的，这是一个Android应用程序，但是什么样的Android应用程序？新闻阅读器？音乐播放器？语言学习应用程序？天气应用？也许这是另一个待办事项清单应用程序。如果所有的东西都捆绑在框架提供的类中，那么你会说不清楚。&lt;/p&gt;
&lt;p&gt;正如Robert Martin, aka Uncle Bob所说：”Your architecture should scream the purpose of the app.”更技术性地说，&lt;strong&gt;业务逻辑应该明确分开，并且独立于框架。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;Android架构的四大黄金规则（或任何架构）&quot;&gt;&lt;a href=&quot;#Android架构的四大黄金规则（或任何架构）&quot; class=&quot;headerlink&quot; title=&quot;Android架构的四大黄金规则（或任何架构）&quot;&gt;&lt;/a&gt;Android架构的四大黄金规则（或任何架构）&lt;/h2&gt;&lt;p&gt;我希望现在很清楚，**您不能依赖框架来使您的代码整齐有序，特别是在Android里。**在FIve里，我们很早就意识到这一点，但缺乏立即提出核心的经验。失败真正显示出来需要很长时间，并且在项目中间无法更改整个架构。你也无法花时间将旧项目重构为新的酷炫的梦寐以求的最佳架构。所以，我们采取了渐进式的方法，**逐渐从项目到项目逐步构建我们的架构，并从错误中学习。**我们认为我们的架构应该满足几个目标，我们可以将我们的方法与这些目标进行比较。&lt;strong&gt;良好的架构应该做到以下几点：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Satisfy a multitude of stakeholders.（满足各方利益者）&lt;/li&gt;
&lt;li&gt;Encourage separation of concerns.（鼓励的关注点分离）&lt;/li&gt;
&lt;li&gt;Run away from the real world (Android, DB, Internet…).&lt;/li&gt;
&lt;li&gt;Enable your components to be testable.（使您的组件成为可测试的）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;I-Satisfy-a-multitude-of-stakeholders-满足各方利益者&quot;&gt;&lt;a href=&quot;#I-Satisfy-a-multitude-of-stakeholders-满足各方利益者&quot; class=&quot;headerlink&quot; title=&quot;I. Satisfy a multitude of stakeholders.(满足各方利益者)&quot;&gt;&lt;/a&gt;I. Satisfy a multitude of stakeholders.(满足各方利益者)&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Stakeholder&lt;/strong&gt;（在这篇文章中）是任何对你的APP感兴趣的人。除开发者外，还有UI设计师，UX设计师，PM，DB管理员，QA等。&lt;/p&gt;
&lt;p&gt;当然，你不能以某种方式组织代码，例如，UX设计人员可以立即打开项目并理解所有内容，甚至可以进行一些更改。这是一个独角兽。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Illustration_3.png&quot; alt=&quot;Android Architecture stakeholders&quot;&gt;&lt;/p&gt;
&lt;p&gt;我的意思是，你可以组织你的代码，使得当前与UX设计师一起工作的开发人员只能管理与UX相关的东西。所以，&lt;strong&gt;所有的交互都在classes &amp;#x2F; modules &amp;#x2F; components &amp;#x2F;中分开&lt;/strong&gt;，就算他们的工作是交互作用。而且特定的开发人员只在应用的UX部分工作时在这些组件上工作。&lt;/p&gt;
&lt;h3 id=&quot;II-Encourage-separation-of-concerns-（鼓励的关注点分离）&quot;&gt;&lt;a href=&quot;#II-Encourage-separation-of-concerns-（鼓励的关注点分离）&quot; class=&quot;headerlink&quot; title=&quot;II. Encourage separation of concerns.（鼓励的关注点分离）&quot;&gt;&lt;/a&gt;II. Encourage separation of concerns.（鼓励的关注点分离）&lt;/h3&gt;&lt;p&gt;我之前所说的是一个&lt;strong&gt;关注点分离&lt;/strong&gt;的例子。我们鼓励这种特殊的方法，因为它可以很好地映射到团队和项目阶段的组织，但是您的架构也应该鼓励&lt;strong&gt;常规分离关注点&lt;/strong&gt;。分离问题或单一职责原则说，&lt;strong&gt;每个组件都应该只有一个理由需要改变。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;III-Run-away-from-the-real-world-Android-DB-Internet…&quot;&gt;&lt;a href=&quot;#III-Run-away-from-the-real-world-Android-DB-Internet…&quot; class=&quot;headerlink&quot; title=&quot;III. Run away from the real world (Android, DB, Internet…).&quot;&gt;&lt;/a&gt;III. Run away from the real world (Android, DB, Internet…).&lt;/h3&gt;&lt;p&gt;从现实世界中逃脱出来的这个特殊点已经在之前提到过了。我们已经说过，我们想要表现APP真正的功能而已。我们想要强调业务逻辑，并在框架下留下框架细节。这一点应该更加强烈：我们不仅要隐藏框架细节，还要隐藏与外部世界相关的所有细节。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Illustration_4.png&quot; alt=&quot;Android Architecture under the hood&quot;&gt;&lt;/p&gt;
&lt;p&gt;基本所有的Android的东西如传感器，通知机制，屏幕细节，数据库访问，Internet访问等等都是&lt;strong&gt;nitty gritty dirty&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;IV-Enable-your-components-to-be-testable&quot;&gt;&lt;a href=&quot;#IV-Enable-your-components-to-be-testable&quot; class=&quot;headerlink&quot; title=&quot;IV. Enable your components to be testable.&quot;&gt;&lt;/a&gt;IV. Enable your components to be testable.&lt;/h3&gt;&lt;p&gt;您应该尽可能单元测试您的APP，并且您的架构应该允许您执行此操作。如果你不能单元测试一切，你至少应该用测试来覆盖你的业务逻辑。与现实世界的分离与此相得益彰。如果它明显与应用程序的其他部分分离，则测试业务逻辑会更容易。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/giphy-5.gif&quot; alt=&quot;giphy-5&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;第一次迭代-上帝的活动&quot;&gt;&lt;a href=&quot;#第一次迭代-上帝的活动&quot; class=&quot;headerlink&quot; title=&quot;第一次迭代 - 上帝的活动&quot;&gt;&lt;/a&gt;第一次迭代 - 上帝的活动&lt;/h2&gt;&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;UsersActivity&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ListActivity&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &lt;span class=&quot;keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;onCreate&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Bundle savedInstanceState)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;built_in&quot;&gt;super&lt;/span&gt;.onCreate(savedInstanceState);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;comment&quot;&gt;//...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ListUsers&lt;/span&gt;().execute();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;ListUsers&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;AsyncTask&lt;/span&gt;&amp;lt;Void, Void, Void&amp;gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;meta&quot;&gt;@Override&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &lt;span class=&quot;keyword&quot;&gt;protected&lt;/span&gt; Void &lt;span class=&quot;title function_&quot;&gt;doInBackground&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Void... voids)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;comment&quot;&gt;// final SQLiteOpenHelper sqLiteOpenHelper = ...&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;comment&quot;&gt;// JsonObjectRequest jsObjRequest = new JsonObjectRequest&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;comment&quot;&gt;// (Request.Method.GET, url, null, new Response.Listener&amp;lt;JSONObject&amp;gt;() &amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;comment&quot;&gt;// MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;comment&quot;&gt;// showData(user);&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;           &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;literal&quot;&gt;null&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;       &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;   &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;你可能在”古代时代”看到过类似的代码。如果你还没有，那么你还很年轻。这有什么问题？&lt;strong&gt;Everything!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/no.gif&quot; alt=&quot;no&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们有一个涉及数据库，进入互联网，解析，产生线程和呈现数据的Activity。所以，&lt;strong&gt;所有的利益相关者都在关注单个类，没有任何问题是分离的，它不是可测试的，业务逻辑与Android的东西混杂在一起。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/2017/wp-content/uploads/2016/11/Diagram_1-1.png&quot; alt=&quot;android architecture part 1 - 1&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;第二次迭代-MVP&quot;&gt;&lt;a href=&quot;#第二次迭代-MVP&quot; class=&quot;headerlink&quot; title=&quot;第二次迭代 - MVP&quot;&gt;&lt;/a&gt;第二次迭代 - MVP&lt;/h2&gt;&lt;p&gt;第一种方法显然不起作用。我们尝试的第一件事情之一是MVP，或&lt;strong&gt;model-view-presenter&lt;/strong&gt;。每个人都熟悉MVP。它是最受欢迎的架构之一。它看起来像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Diagram_1-1.png&quot; alt=&quot;android architecture part 2 - 2&quot;&gt;&lt;/p&gt;
&lt;p&gt;在这里，我们分离了实际上是我们的Android Fragments的视图，我们有代表我们业务的（domain）模型，最后我们有协调所有这些的presenters。肯定会更好。关注点有些分离，利益相关者不再困惑，你可以写一些测试。然而，我们仍然与现实世界混淆，因为presenter直接触及数据库和所有内容。这是一个神的对象。它处理模型，它将数据发送到视图，它拥有业务逻辑（业务逻辑是那些齿轮:)），并且它传递到数据库和Internet，获取传感器数据等等。所以，更好，但是它可以变得更好。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Diagram_2-1.png&quot; alt=&quot;android architecture part 1 - 3&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;第三次迭代-MVP-managers&quot;&gt;&lt;a href=&quot;#第三次迭代-MVP-managers&quot; class=&quot;headerlink&quot; title=&quot;第三次迭代 - MVP + managers&quot;&gt;&lt;/a&gt;第三次迭代 - MVP + managers&lt;/h2&gt;&lt;p&gt;当政府不知道该做什么时，他们做了什么？它设置一个机构。当开发者不知道该怎么做时，他们会做什么？他们采用一些managers。您不必将其命名为”manager”。这些类有很多名称：utils，helper，fooBarBuzz-ator等等。我们统称为managers。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Diagram_3-3.png&quot; alt=&quot;android architecture part 1 - 4&quot;&gt;&lt;/p&gt;
&lt;p&gt;说实话，**这甚至有点奏效。**业务逻辑包含在manager类中。利益相关者知道在哪里看，关注是分开的，但他们可能更多，你可以编写更多的测试，但你仍然直接触摸Android，所以你必须编写Android测试用例和预填充数据库来测试业务逻辑，这很慢。是的，managers往往是巨大的野兽，**很快就很难维持。**你可以争辩说，它不需要比现在更复杂，你可以通过使用更简单的架构来更快地传递代码，但是这种方法会出现更多的错误，并且可维护性会受到影响。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://five.agency/wp-content/uploads/2016/11/Diagram_4-1.png&quot; alt=&quot;android architecture part 1 - 5&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;总结&quot;&gt;&lt;a href=&quot;#总结&quot; class=&quot;headerlink&quot; title=&quot;总结&quot;&gt;&lt;/a&gt;总结&lt;/h2&gt;&lt;p&gt;在本系列的第一部分中，我们遇到了创建实际可行的Android体系结构的挑战。良好的Android架构应该满足众多利益相关者的需求，鼓励分离问题，强调业务逻辑，并将框架细节置于隐患之下，并使所有组件都可测试。在系列的第二部分中，我们将向您展示我们如何管理那些对我们有用的东西。在此之前，&lt;strong&gt;您对如何创建适当的Android工作流程有任何建议吗？你遇到了什么问题？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtMi1jbGVhbi1hcmNoaXRlY3R1cmUv&quot;&gt;Continue to part 2&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;您还可以查看其他部分：&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNS10ZXN0LWNsZWFuLWFyY2hpdGVjdHVyZS8=&quot;&gt;Part 5: How to Test Clean Architecture&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtNC1hcHBseWluZy1jbGVhbi1hcmNoaXRlY3R1cmUtb24tYW5kcm9pZC1oYW5kcy1vbi8=&quot;&gt;Part 4: Applying Clean Architecture on Android (Hands-on)&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL2ZpdmUuYWdlbmN5L2FuZHJvaWQtYXJjaGl0ZWN0dXJlLXBhcnQtMy1hcHBseWluZy1jbGVhbi1hcmNoaXRlY3R1cmUtYW5kcm9pZC8=&quot;&gt;Part 3: Applying Clean Architecture on Android&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; &lt;/p&gt;
]]></content>
    
      <category term="Android架构"/>
    
    
      <category term="Android"/>
    
      <category term="Clean架构"/>
    
      <category term="译文"/>
    
  </entry>
  
</feed> 