Внедрение зависимостей (DI) — это метод, широко используемый в программировании и хорошо подходящий для разработки под Android. Следуя принципам DI, вы закладываете основу для хорошей архитектуры приложения.
Реализация внедрения зависимостей дает вам следующие преимущества:
- Возможность повторного использования кода
- Простота рефакторинга
- Простота тестирования
Основы внедрения зависимостей
Прежде чем рассматривать внедрение зависимостей конкретно в Android, на этой странице представлен более общий обзор того, как работает внедрение зависимостей.
Что такое внедрение зависимостей?
Классы часто требуют ссылок на другие классы. Например, классу Car
может потребоваться ссылка на класс Engine
. Эти обязательные классы называются зависимостями , и в этом примере класс Car
зависит от наличия экземпляра класса Engine
для запуска.
У класса есть три способа получить необходимый ему объект:
- Класс создает необходимую ему зависимость. В приведенном выше примере
Car
создаст и инициализирует собственный экземплярEngine
. - Возьмите его откуда-нибудь еще. Некоторые API-интерфейсы Android, такие как методы получения
Context
иgetSystemService()
, работают таким образом. - Поставьте его в качестве параметра. Приложение может предоставить эти зависимости при создании класса или передать их функциям, которым нужна каждая зависимость. В приведенном выше примере конструктор
Car
получит в качестве параметраEngine
.
Третий вариант — внедрение зависимостей! При таком подходе вы берете зависимости класса и предоставляете их вместо того, чтобы экземпляр класса получал их сам.
Вот пример. Без внедрения зависимостей представление Car
, который создает в коде собственную зависимость Engine
, выглядит следующим образом:
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}
fun main(args: Array
class Car {
private Engine engine = new Engine();
public void start() {
engine.start();
}
}
class MyApp {
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}

Это не пример внедрения зависимостей, поскольку класс Car
создает свой собственный Engine
. Это может быть проблематично, потому что:
Car
иEngine
тесно связаны — экземплярCar
использует один типEngine
, и никакие подклассы или альтернативные реализации не могут быть легко использованы. Если быCar
собирал свой собственныйEngine
, вам пришлось бы создать два типаCar
вместо того, чтобы просто повторно использовать один и тот жеCar
для двигателей типаGas
иElectric
.Жесткая зависимость от
Engine
усложняет тестирование.Car
использует реальный экземплярEngine
, что не позволяет вам использовать тестовый дубль для модификацииEngine
для разных тестовых случаев.
Как выглядит код с внедрением зависимостей? Вместо того, чтобы каждый экземпляр Car
создавал свой собственный объект Engine
при инициализации, он получает объект Engine
в качестве параметра в своем конструкторе:
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main(args: Array
class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
}
class MyApp {
public static void main(String[] args) {
Engine engine = new Engine();
Car car = new Car(engine);
car.start();
}
}

main
функция использует Car
. Поскольку Car
зависит от Engine
, приложение создает экземпляр Engine
, а затем использует его для создания экземпляра Car
. Преимущества этого подхода на основе DI:
Многоразовость
Car
. Вы можете передать различные реализацииEngine
вCar
. Например, вы можете определить новый подклассEngine
под названиемElectricEngine
, который вы хотите, чтобыCar
использовал. Если вы используете DI, все, что вам нужно сделать, это передать экземпляр обновленного подклассаElectricEngine
, иCar
по-прежнему будет работать без каких-либо дальнейших изменений.Легкое тестирование
Car
. Вы можете передать тестовые дубли для проверки различных сценариев. Например, вы можете создать тестовый двойникEngine
под названиемFakeEngine
и настроить его для различных тестов.
Существует два основных способа внедрения зависимостей в Android:
Внедрение конструктора . Это способ, описанный выше. Вы передаете зависимости класса его конструктору.
Внедрение поля (или внедрение сеттера) . Определенные классы платформы Android, такие как действия и фрагменты, создаются системой, поэтому внедрение конструктора невозможно. При внедрении полей экземпляры зависимостей создаются после создания класса. Код будет выглядеть так:
class Car {
lateinit var engine: Engine
fun start() {
engine.start()
}
}
fun main(args: Array
class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
}
class MyApp {
public static void main(String[] args) {
Car car = new Car();
car.setEngine(new Engine());
car.start();
}
}
Автоматическое внедрение зависимостей
В предыдущем примере вы сами создавали, предоставляли и управляли зависимостями различных классов, не полагаясь на библиотеку. Это называется внедрением зависимостей вручную или ручным внедрением зависимостей . В примере Car
была только одна зависимость, но большее количество зависимостей и классов может сделать ручное внедрение зависимостей более утомительным. Ручное внедрение зависимостей также создает несколько проблем:
В больших приложениях для принятия всех зависимостей и их правильного подключения может потребоваться большой объем стандартного кода. В многоуровневой архитектуре, чтобы создать объект для верхнего уровня, необходимо предоставить все зависимости нижних слоев. Конкретный пример: чтобы построить настоящий автомобиль, вам могут понадобиться двигатель, трансмиссия, шасси и другие детали; а двигателю, в свою очередь, нужны цилиндры и свечи зажигания.
Если вы не можете создавать зависимости перед их передачей — например, при использовании ленивой инициализации или привязке объектов к потокам вашего приложения — вам необходимо написать и поддерживать собственный контейнер (или граф зависимостей), который управляет временем жизни вашего приложения. зависимости в памяти.
Существуют библиотеки, которые решают эту проблему, автоматизируя процесс создания и предоставления зависимостей. Они делятся на две категории:
Решения на основе отражения, которые подключают зависимости во время выполнения.
Статические решения, генерирующие код для подключения зависимостей во время компиляции.
Dagger — это популярная библиотека внедрения зависимостей для Java, Kotlin и Android, поддерживаемая Google. Dagger облегчает использование DI в вашем приложении, создавая за вас граф зависимостей и управляя им. Он предоставляет полностью статические зависимости и зависимости времени компиляции, решающие многие проблемы разработки и производительности решений на основе отражения, таких как Guice .
Альтернативы внедрению зависимостей
Альтернативой внедрению зависимостей является использование локатора сервисов . Шаблон проектирования локатора сервисов также улучшает отделение классов от конкретных зависимостей. Вы создаете класс, известный как локатор служб , который создает и хранит зависимости, а затем предоставляет эти зависимости по требованию.
object ServiceLocator {
fun getEngine(): Engine = Engine()
}
class Car {
private val engine = ServiceLocator.getEngine()
fun start() {
engine.start()
}
}
fun main(args: Array
class ServiceLocator {
private static ServiceLocator instance = null;
private ServiceLocator() {}
public static ServiceLocator getInstance() {
if (instance == null) {
synchronized(ServiceLocator.class) {
instance = new ServiceLocator();
}
}
return instance;
}
public Engine getEngine() {
return new Engine();
}
}
class Car {
private Engine engine = ServiceLocator.getInstance().getEngine();
public void start() {
engine.start();
}
}
class MyApp {
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}
Шаблон локатора сервисов отличается от внедрения зависимостей способом использования элементов. С помощью шаблона локатора сервисов классы имеют контроль и запрашивают внедрение объектов; при внедрении зависимостей приложение получает контроль и заранее внедряет необходимые объекты.
По сравнению с внедрением зависимостей:
Коллекция зависимостей, требуемых локатором сервисов, усложняет тестирование кода, поскольку все тесты должны взаимодействовать с одним и тем же глобальным локатором сервисов.
Зависимости кодируются в реализации класса, а не в поверхности API. В результате становится сложнее узнать, что нужно классу извне. В результате изменения в
Car
или зависимостях, доступных в локаторе служб, могут привести к сбоям во время выполнения или теста из-за сбоя ссылок.Управлять временем жизни объектов сложнее, если вы хотите ограничиться чем-то другим, кроме времени жизни всего приложения.
Используйте Hilt в своем приложении для Android
Hilt — рекомендуемая Jetpack библиотека для внедрения зависимостей в Android. Hilt определяет стандартный способ реализации внедрения внедрения в вашем приложении, предоставляя контейнеры для каждого класса Android в вашем проекте и автоматически управляя их жизненными циклами.
Hilt построен на основе популярной библиотеки DI Dagger и обеспечивает корректность времени компиляции, производительность во время выполнения, масштабируемость и поддержку Android Studio, которую обеспечивает Dagger.
Чтобы узнать больше о Hilt, см. «Внедрение зависимостей с помощью Hilt» .
Заключение
Внедрение зависимостей дает вашему приложению следующие преимущества:
Возможность повторного использования классов и разделение зависимостей: проще заменять реализации зависимости. Повторное использование кода улучшено благодаря инверсии управления, и классы больше не контролируют создание своих зависимостей, а вместо этого работают с любой конфигурацией.
Простота рефакторинга: зависимости становятся проверяемой частью поверхности API, поэтому их можно проверять во время создания объекта или во время компиляции, а не скрывать как детали реализации.
Простота тестирования: класс не управляет своими зависимостями, поэтому при его тестировании вы можете передавать разные реализации для проверки всех ваших разных случаев.
Чтобы полностью понять преимущества внедрения зависимостей, вам следует попробовать его вручную в своем приложении, как показано в разделе «Внедрение зависимостей вручную» .
Дополнительные ресурсы
Чтобы узнать больше о внедрении зависимостей, см. следующие дополнительные ресурсы.
Образцы
Mir 2: Return of the King 是 Actoz Soft 授权的优质《传奇》IP 移动游戏,由 HK ZHILI YAOAN LIMITED 使用 Unity 游戏引擎开发。 这款游戏不仅完美再现了韩国奇幻类 MMORPG 的代表作 Mir 2 的游戏氛围,还提供了许多最受欢迎的游戏内容,例如装备收集、大规模沙漠攻击和其他核心玩法。 该游戏使用了 Android Frame Pacing 库 (Swappy) 来提高帧速率的稳定性、实现流畅的渲染,并显著提升了 Android 鸣潮 是一款由 Kuro Games 开发的高保真动作角色扮演游戏。为了持续为长时间的游戏会话提供卓越的用户体验,优化功耗非常重要。 Android Studio 从 Hedgehog (2023.1.1) 开始引入了 功耗性能分析器 ,可帮助开发者根据设备端电源轨监视器 (ODPM) 了解功耗数据。 借助 Android Studio 中的功耗性能分析功能,您还可以 有效地对 Android 应用功能的功耗进行 A/B 测试 (如下所示)。 Kuro Games 首先使用 Android Godot Engine 是一个广受欢迎的多平台开源游戏引擎,对 Android 提供强大的支持。Godot 可用于制作几乎任何类型的游戏,并且支持 2D 和 3D 图形。Godot 4 版引入了新的渲染系统,该系统具有用于高保真图形的高级功能。Godot 4 渲染程序专为 Vulkan 等现代图形 API 而设计。 Godot Foundation 聘请了 The Forge Interactive 的图形优化专家,并与 Google 合作分析和进一步改进了 Godot 4 Vulkan Android 动态性能框架 (ADPF) 是 Google 推出的一款强大工具,适用于希望优化应用性能的开发者。ADPF 通过其热管理 API 提供有关设备热状态的实时信息,这些信息随后用于调整应用中的图形设置。 出于研究目的,Arm 使用 Unreal Engine 和 ADPF 开发了一个演示版,以研究如何使用 ADPF 优化游戏性能。 ADPF 会监控热状态,并相应地在游戏引擎中调整图形质量。 NCSoft《天堂 W》是由 NCSoft 开发的大型多人在线角色扮演游戏 (MMORPG)。这款游戏继承了原始 Lineage W 游戏的传统,为世界各地的玩家提供了一个环境,让他们可以通过全球服务器进行合作和竞争。《Lineage W》以独特的中世纪奇幻世界为背景,通过各种职业、技能和战斗系统为玩家提供深层次的游戏体验。 NCSoft 使用 Android 动态性能框架最大限度地提高了图形质量,同时避免了由温控调频导致的性能问题。 Android 动态性能框架 (ADPF) 改进性能和散热管理对于开发成功的 Android 游戏至关重要。传统上,开发者必须通过降低游戏保真度或进一步优化渲染程序来管理这些问题。这些更改往往针对特定游戏,并且往往不够灵活。 Android 生态系统中的多个参与者为开发者提供了自适应性能 API。为了简化自适应性能功能的集成并减少生态系统中的碎片化,Google 和 MediaTek 携手合作集成了我们的产品:Android 动态性能框架 (ADPF) 和 MediaTek 自适应游戏技术 (MAGT)。 借助 ADPF 使命召唤:战争地带移动版 是广受欢迎的《使命召唤》系列中的第一人称动作游戏。 超受欢迎的主机和 PC 游戏的移动实现利用低层级移动 API 来提供出色的玩家体验。 从技术角度来看,移动实现的目标是支持各种 Android 移动设备,同时尽可能使实现与主机版本保持一致,并确保图形管道和工具链与当前的主机和 PC 游戏及内容保持兼容。 Call of Duty 引擎使用名为 任务图渲染程序 的系统实现渲染提交管理的同步、内存分配和调度,该系统确定在 GPU 《魔灵召唤:克罗尼柯战记》 是韩国游戏开发商 Com2uS 于 2023 年 3 月面向全球发布的一款移动端大型多人在线角色扮演游戏。迄今为止,《魔灵召唤》在全球的下载量超过 1.8 亿,收入超过 27 亿美元。《魔灵召唤》是目前全球最受欢迎的手游之一,这款游戏展现了一个奇幻世界,玩家必须收集并训练各种怪物,才能与其他玩家对战。 近十年过去了,这款游戏庞大的玩家社区依然很是活跃,而且仍在扩展,部分原因在于 Com2uS 不断发布新的内容和更新,使这款游戏历久弥新、十分刺激。Com2uS 《魔灵召唤:克罗尼柯战记》 US(WW) 和 KR by Com2uS 专门利用 Vulkan 在 Android 上进行渲染,可将性能提升高达 30%。 Vulkan 是一种现代化的跨平台 3D 图形 API,旨在最大限度减少设备图形硬件与您的游戏之间的抽象。与 OpenGL ES 相比,Vulkan 的 CPU 开销更低,并且 Vulkan 提供更广泛的功能。 Com2uS 为 《魔灵召唤:克罗尼柯战记》 开发了高级渲染功能,包括: 《魔灵召唤:克罗尼柯战记》 对 Android 《Ares: Rise of Guardians 》是一款移动设备转 PC 的科幻 MMORPG 游戏,由 Second Dive 开发,后者是一家位于韩国的游戏工作室,以其动作角色扮演系列开发方面的专业知识而闻名。该游戏由 Kakao Games 发布。 《阿瑞斯》以广袤的宇宙为背景,采用细节满满的未来主义背景。《阿瑞斯》充满了刺激的玩法和画面精美的角色,涉及身着战斗服的战斗人员。然而,由于这些细节丰富的图形,一些用户的设备在处理游戏内容时有些吃力。 Cat Daddy Games 是一家全资 2K 工作室,位于华盛顿州柯克兰,是 NBA 2K Mobile 的开发者。该团队希望提高游戏的整体质量和稳定性,具体方法是减少“应用无响应”错误 (ANR)。如果 Android 应用的界面线程处于阻塞状态的时间过长,就会发生 ANR。发生这种情况时,负责更新界面的应用主线程将无法绘制或处理用户输入事件,这会引起用户的不满。如果应用在前台运行,系统会显示一个对话框,允许用户强制退出应用。 减少 ANR 一直是 Cat Daddy 的首要任务。QA Devsisters 是一家全球性的移动游戏开发商和发布商,专门制作基于《跑跑姜饼人》IP 的休闲游戏。他们最受欢迎的游戏包括 《跑跑姜饼人:烤箱大逃亡》 (跑步街机)和 《跑跑王国》 (社交 RPG),这两款游戏都深受全球用户的喜爱,尤其是在韩国、台湾和美国。虽然《跑跑姜饼人:烤箱大逃亡》是一款休闲游戏,但五年积累的资源使 CDN 容量提高到了 2.5GB,这使得 CDN 费用的增加。Devsisters 需要找到一种可持续的模式来宣传他们的大文件游戏。 Devsisters 发现,大量的 NEW STATE Mobile 是 Krafton 的一款大逃杀游戏于 2021 年 11 月面向全球发布,在发布后的第一个月便获得了超过 4500 万次下载。KRAFTON, Inc. 是一个由多个独立的游戏开发工作室组成的联合公司,旨在为全球游戏玩家打造富有吸引力的创新娱乐体验。该公司包括 PUBG Studio、Bluehole Studio、Striking Distance Studio、RisingWings、Dreamotion 和 Unknown Spokko 住在波兰,是一群雄心勃勃的创作者,他们致力于打造要求极为严苛的 IP。Spokko 是 CD PROJEKT 家族的成员,但是一家独立公司,已将 《巫师:怪物杀手》 的精彩世界转移到智能手机上。 《巫师:怪物杀手》是一款使用增强现实技术的基于位置的角色扮演游戏。这是一款计算密集型游戏,会给许多设备带来挑战。发布之初,Spokko 希望确保其游戏可以覆盖尽可能多的用户,同时为所有人提供高品质的体验。 Cat Daddy Games 是一家全资 2K 工作室,总部位于华盛顿州柯克兰。NBA 2K Mobile、NBA SuperCard 和 WWE SuperCard 系列背后的团队正在寻找一个解决方案来为用户提高游戏的整体质量,具体方法是在支持这些游戏的设备上提供更优质的素材资源。 他们实现了 Play Asset Delivery,以简单而更灵活的方式针对每位用户的设备配置生成并提供经过优化的 APK,并使用纹理压缩格式定位功能针对特定设备提供更好的美术资源并减少资源下载。 首先,Cat Unreal Engine 是由 Epic Games 开发的游戏引擎,可为各行各业的创作者提供自由和控制权,让他们提供先进的娱乐、富有吸引力的可视化内容和沉浸式虚拟世界。一些主要的 Android 游戏都是使用 Unreal Engine 构建的。 图 1. 在 Pixel 4 上运行的 Unreal Engine Suntemple 示例的屏幕截图 Epic 和其他游戏开发者使用 Android Studio 调试 C++、Kotlin 或 Java 编程语言,但许多游戏开发者都有以 Electronic Arts (EA) 是一家总部位于美国加利福尼亚州的游戏公司。它制作了各种不同类型的游戏,例如体育、动作、赛车和模拟游戏。EA 的开发工作室 Firemonkeys 因开发 真实赛车 3 、 模拟人生自由玩 和 Need For Speed: No Limits 而闻名于世。Firemonkeys 使用自定义游戏引擎来开发游戏,现在他们的所有 Android 游戏都在其开发工作流中使用 Android Game Development Extension (AGDE) 游戏开发商 CD Projekt RED (CDPR) 位于波兰华沙,他们重新构思了自己的迷你游戏《巫师 3》、 《巫师之昆特牌》 ,并于 2020 年 3 月在 Google Play 上以独立的免费畅玩形式发布。由于初始文件较大,且定期更新需要额外的设备存储空间,用户通常必须重新安装完整版游戏,才能获得更新的版本。这是游戏社区中最突出的挫败点。为了帮助进行差分修补,CDPR 通过实现 Play Asset Delivery 取得了巨大的成功。 CDPR 是实现 Play Asset 20 多年来, Gameloft 为数字平台打造了富有创意的游戏体验,从移动游戏到跨平台 PC 和主机游戏,不一而足。除了自有的知名游戏系列外,Gameloft 还为乐高、环球和长宝等热门品牌开发游戏。他们的游戏团队在全球拥有 3,600 名员工,每月可在 100 多个国家/地区覆盖 5500 万唯一身份玩家。 竞速街机游戏 Asphalt 9: Legends 于 2018 年首次发布,他们需要找到一种平衡性能、保真度和电池的方式。为此,Gameloft 怀着对游戏的热情以及将游戏带给世界各地玩家的渴望, Gameloft 于 2000 年成立。他们是开发移动游戏的先驱,现在旗下有超过 190 款游戏。Gameloft 的许多移动游戏是图形密集型游戏,下载大小较大。这使得他们成为 Google Play Asset Delivery (PAD) 早期开发阶段一个极具吸引力的合作伙伴。PAD 是一套基于我们的 app bundle 基础架构构建的游戏服务分发功能。PAD 会在适当的时间免费向适当的设备提供免费、动态的适当游戏素材资源。这引起了 RV AppStudios 是一家总部位于美国的游戏开发商,该公司旗下的休闲游戏、教育儿童应用和实用类应用到目前为止已有超过 2 亿的下载量。该团队在其应用 Puzzle Kids - Animals Shapes and Jigsaw Puzzles 中引入了 Google Play Asset Delivery,属于早期测试用户。他们希望优化应用的大小,节省资金,并在需要下载新的资源包时消除任何干扰,从而提升用户体验。 当用户安装 Puzzle Kids Pixonic 是一个总部位于莫斯科的视频游戏开发团队,其以抓住每一个机会升级自己的移动应用并覆盖更广泛的玩家群体为荣。该公司最著名的游戏之一是 《War Robots》 ,这是一款 12 人的玩家对战 (PVP) 游戏,玩家可以在即时战场上操作定制的机器人进行决斗。 《War Robots》于 2014 年发布,最初是专为 Android 的早期设备设计的,通过触控板操控游戏角色,用不到鼠标。Pixonic Gameloft 一直致力于成为首批在最新便携式硬件上发布游戏的开发商之一,以便随时随地为玩家提供激动人心的体验。因此,Gameloft 知道 ChromeOS 是适合其移动赛车系列最新游戏《狂野飙车 8:极速凌云》的平台。 Gameloft 非常了解如何针对不同的设备开发游戏,但将 Asphalt 体验转换为 Chromebook 特有的触摸屏/键盘混合控制(可随时切换)似乎具有挑战性。然而,结果证明这个过程没有想象的那么困难,值得一试。 利用 ChromebookMir 2 通过使用 Frame Pacing 库来提升渲染性能
Kuro Games 使用 Android Studio 功耗性能分析器和 ODPM 为 Wuthering Waves 降低了 9.68% 的功耗
适用于 Android 的 Godot Engine Vulkan 优化
在 Unreal Engine 中使用 Android 动态性能框架 (ADPF) 入门
NCSoft Lineage W 使用 ADPF 提高了持续性能并防止温控降频
MediaTek 提升了 Android SoC 的动态性能
《使命召唤:战争地带手游》使用 Vulkan 提升图形效果
Com2uS - Google Play 游戏电脑版
Com2uS 使用 Vulkan 提升图形效果
Kakao Games 通过 Android 自适应功能将 FPS 稳定性提升至 96%
2K 利用 Android Game Development Kit 将 ANR 发生率降低了 35%
《跑跑姜饼人:烤箱大逃亡》通过 Play Asset Delivery 节省了超过 20 万美元的 CDN 费用
新 STATE Mobile 利用 Android GPU 检查器将 GPU 使用量降低了 22%
《The Witcher: Monster Slayer》借助 Android Performance Tuner 扩大覆盖面
2K 借助 Play Asset Delivery 提供高清画质
“AGDE 简直太棒了!”;使用 Unreal Engine 进行 Android 开发
Firemonkeys 借助 AGDE 缩短了开发和调试时间
CD Projekt RED 借助 Play Asset Delivery 将游戏更新大小缩减了 90% 并将更新率提高了 10%
借助 Game Mode API,Gameloft 将设备功耗降低了 70%,从而使游戏时长延长了 35%
Gameloft 借助 Google Play Asset Delivery 将新用户数增加了 10%
RV AppStudios 借助 Google Play Asset Delivery 提高了用户留存率
Pixonic 针对大屏设备进行优化后,在 ChromeOS 上的互动度提高了 25%
Gameloft 针对 ChromeOS 进行优化后,收入迅速提升了 9 倍