【Android】Dagger2 框架设计理念和使用方式详解

文章目录

  • Dagger 框架作用
  • 基本使用方法
            • 引入依赖
            • 创建 Object
            • 创建 Module
            • 创建 Component
            • 向 Activity 注入对象
  • Component 内部单例
  • 全局单例
  • 自定义 Scope
  • 关于单例作用域的理解
  • 注入多种同类型对象
  • Component 依赖
  • Component 继承
  • 传递 Activity

Dagger 框架作用

这里,我们直接跳过低级问题,假设大家已经知道 dagger 是什么

我们着重介绍,它的核心价值和一些高阶的用法

先上结论,再将它的设计理念和使用方式

Dagger 框架的意义在于

  • 确定类之间的依赖关系和调用顺序

  • 保证代码一定是按照这种依赖关系来编写的

  • 使用注解注入的方式统一管理对象创建过程

  • 对象创建方式修改时,只需修改 Module 和 Component 代码,不影响注入目标

确定依赖关系,才是其核心目的,其它只是关系明确后的附带效果

基本使用方法

假设我们现在有个 Activity,想要访问数据,有网络数据,也有本地数据

那么我们需要创建一个 HttpObject 和 DatabaseObject 来处理这两个工作

为了让 Activity 专注于业务代码,我们会专门建立一个 Repository 来统一管理各种 Object

这样的设计是完全没问题的,但是得靠代码编写者来保证这种依赖关系

现在我们来看看如何用 Dagger 去实现这个功能

在 Dagger 中,负责完成单个具体功能的类叫做 Module

完成整个业务往往需要多个 Module,统一管理多个 Module,向 Activity 中注入对象的类叫做 Component

下面我们来编写代码,看看实际使用方式

引入依赖
    api "com.google.dagger:dagger:2.35.1"api "com.google.dagger:dagger-android:2.35.1"api "com.google.dagger:dagger-android-support:2.35.1"annotationProcessor  "com.google.dagger:dagger-compiler:2.35.1"annotationProcessor "com.google.dagger:dagger-android-processor:2.35.1"
创建 Object
import javax.inject.Inject;public class HttpObject {@Injectpublic HttpObject() {}public void post() {}
}

这里的@Inject表示自动创建时调用的构造函数

可以不使用此注解,但必须提供其它方式创建对象,比如Module::@Provides

创建 Module
import dagger.Module;
import dagger.Provides;@Module
public class HttpModule {@Providespublic HttpObject provideHttpObject() {return new HttpObject();}
}

简单对象管理也可以不提供 Module,此时会使用Object::@Inject标记的构造函数来创建对象

创建 Component
import dagger.Component;@Component(modules = {HttpModule.class, DatabaseModule.class
})
public interface DataComponent {void injectHomeActivity(HomeActivity homeActivity);
}

Component 也可以不指定 modules,此时Module::@Provides不会生效,只会调用Object::@Inject标记的构造函数来创建对象

向 Activity 注入对象

这里需要先 Rebuild,IDE 会帮我们自动生成 Component 实例

import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import com.android.code.databinding.ActivityHomeBinding;import javax.inject.Inject;@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {ActivityHomeBinding binding;@InjectHttpObject httpObject;@InjectDatabaseObject databaseObject;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityHomeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());//编写测试代码try {test();} catch (Throwable e) {e.printStackTrace();}}@Functionprotected void test() throws Throwable {DaggerDataComponent.builder().build().injectHomeActivity(this);System.err.println(httpObject.hashCode());System.err.println(databaseObject.hashCode());}
}

对象必须通过 Component 注入,因此 Component 是必须的

并且 Component 中的 inject 的方法不允许使用基类,因为 Dagger 编译器要根据真实类名来生成注入代码

Component 内部单例

@Singleton
@Component(modules = {HttpModule.class, DatabaseModule.class
})
public interface DataComponent {void injectHomeActivity(HomeActivity homeActivity);
}

Component::@Singleton可以允许同一个 Component 使用单例模式创建对象

注意,这是的单例是在同一个 Component 对象内部生效的,如果是两个 DataComponent 实例创建的对象,并不是单例的

另外,Component::@Singleton并不保证所有对象就是单例的,只是提醒我们这个组件有使用到单例特性

具体对象是否是单例的,还要看 Component 是如何创建对象的

如果使用Module::@Provides来创建对象,则对应方法上要标记@Singleton

如果使用Object::@Inject来创建对象,则需要在对应的类上面标记@Singleton

如果都没有标记,仅仅是添加了Component::@Singleton注解,那么最终的对象仍然不是单例的

@Module
public class HttpModule {@Provides@Singletonpublic HttpObject provideHttpObject() {return new HttpObject();}
}
@Singleton
public class HttpObject {@Injectpublic HttpObject() {}
}

全局单例

全局单例很容易实现

只要将 Component 实例放到 Application 中,整个应用共享同一个 Component 实例即可

自定义 Scope

现在我们已经知道,可以通过@Singleton来实现全局单例

那么如果我们想在实现在某个类里面单例共享,不同类之间允许多例,要怎么做呢

我们可以通过 Dagger 的自定义 Scope 特性来实现这个目标,步骤如下

首先,自定义一个@LoginActivityScope注解,表示被标记的类在LoginActivity里面是单例共享的

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;import javax.inject.Scope;@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginActivityScope {}

实际上,@Singleton@LoginActivityScope一样,只是一个被@Scope标记的自定义注解,用来标记单例作用域的

下一步,指定 Component 的作用域

import dagger.Component;@LoginActivityScope
@Component(modules = {LoginHttpModule.class, LoginDatabaseModule.class
})
public interface LoginDataComponent {void injectHomeActivity(HomeActivity homeActivity);
}

@Singleton一样,我们需要在 Module 或 Object 上使用一样的作用域注解

如果我们还需要在其它 Activity,比如 HomeActivity 内实现单例共享

我们可以再创建@HomeActivityScope HomeDataComponent HomeHttpModule HomeDatabaseModule

最后,在 LoginActivity 中使用 LoginDataComponent 注入对象,在 HomeActivity 中使用 HomeDataComponent 注入

关于单例作用域的理解

其实认真理解我们就会发现

  • Component 创建单例对象的条件是:Component::ScopeModule::ScopeObject::Scope名称一致

  • 事实上,Dagger 其实只提供了 Component 内部单例这一个特性,其它单例效果都是我们自己间接实现的

  • 根本不存在所谓的单例作用域,我们只是创建了多个 Component 和 Module,自己控制 Component 该在哪里使用而已

  • 如果我们在 LoginActivity 和 HomeActivity 里面都使用 HomeComponent 来注入对象,它们肯定是系统的,作用域的谎言就会被打破

  • @Singleton @LoginActivityScope @HomeActivityScope完全都是一样的东西,只是名称不一样

  • @Scope并没有自动控制单例作用域的效果,只是给开发者提供了一个标记提醒功能

注入多种同类型对象

有时,同一个类可能有多种实例化方式,用于不同的使用场景

我们可以通过 Dagger 的@Named特性,来区分同类型但不同定位的对象

import javax.inject.Named;
import javax.inject.Singleton;import dagger.Module;
import dagger.Provides;@Module
public class HttpModule {@Singleton@Provides@Named("Http-1.0")public HttpObject provideHttpObject1() {return new HttpObject();}@Provides@Named("Http-2.0")public HttpObject provideHttpObject2() {return new HttpObject();}
}

我们在 Module 中提供多种@Provides方法,然后通过@Named去区分他们

在 Activity 中,再通过系统的@Named指定通过哪种方式创建注入对象即可

@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {@Inject@Named("Http-1.0")HttpObject httpObject1;@Inject@Named("Http-1.0")HttpObject httpObject2;@Inject@Named("Http-2.0")HttpObject httpObject3;@Inject@Named("Http-2.0")HttpObject httpObject4;}

打印对象的 HashCode 可以知道,Http-1.0的对象是共享的,而Http-2.0的则是两个不同的对象

Component 依赖

一个 Component 可以依赖另一个 Component,通过其它 Component 创建对象

比如 Fragment 和 Activity 之间的关系,Fragment 中很多数据是可以共享自 Activity,不需要自己去创建

@ActivityScope
@Component(modules = {HttpModule.class
})
public interface ActivityComponent {void injectHomeActivity(HomeActivity homeActivity);//公开接口,表示HttpObject的注入方式可以被其它Component共享HttpObject getHttpObject();
}
@FragmentScope
@Component(dependencies = {ActivityComponent.class
})
public interface FragmentComponent {void injectHomeFragment(HomeFragment homeFragment);
}
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;import android.os.Bundle;import com.android.code.databinding.ActivityHomeBinding;import javax.inject.Inject;@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {ActivityHomeBinding binding;ActivityComponent activityComponent;@InjectHttpObject httpObject;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityHomeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());test();}@Functionprotected void test() {activityComponent = DaggerActivityComponent.builder().build();activityComponent.injectHomeActivity(this);System.err.println(httpObject.hashCode());//add fragmentFragmentManager fragmentManager = getSupportFragmentManager();FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();fragmentTransaction.add(new HomeFragment(), "Home");fragmentTransaction.commitNow();}
}
import android.os.Bundle;import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;import javax.inject.Inject;public class HomeFragment extends Fragment {FragmentComponent fragmentComponent;@InjectHttpObject httpObject;@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);HomeActivity activity = (HomeActivity) getActivity();fragmentComponent = DaggerFragmentComponent.builder().activityComponent(activity.activityComponent).build();fragmentComponent.injectHomeFragment(this);System.err.println(httpObject.hashCode());}
}

通过打印结果可以知道,Fragment 中注入的,和 Activity 中的是同一个对象

组件依赖需要遵循以下关系

  • 如果 ActivityComponent 指定了作用域,那么 FragmentComponent 必须也指定作用域,并且作用域不能相同

  • 这一点很容易理解,因为对象间存在单例共享,一定是 Fragment 和 Activity 之间那种,有上下级作用域关系的

  • 如果 ActivityComponent 没指定作用域,那么 FragmentComponent 作用域无限制

  • 这一点也很容易理解,没有作用域限制,相当于每次都创建新对象,仅仅是代码复用的关系了

Component 继承

继承和依赖的区别在于

  • 依赖只暴漏某个类的创建方法给外部共享

  • 继承将全部功能共享给子组件

继承的使用方法

  • 创建子组件,通过@SubComponent标注

  • 创建子组件 Builder,通过@SubComponent.Builder标注

  • 指定子组件作用域,不能和父组件相同

  • 在父组件中提供创建SubComponent.Builder的接口

  • 通过父组件获得子组件 Builder 来创建子组件

  • 继承和依赖可以混合使用

下面我们来看下代码

@ApplicationScope
@Component(modules = {HttpModule.class
})
public interface ApplicationComponent {void injectApplication(APP app);HomeActivityComponent.Builder activityComponentBuilder();
}
@ActivityScope
@Subcomponent(modules = DatabaseModule.class)
public interface HomeActivityComponent {void injectHomeActivity(HomeActivity homeActivity);HttpObject getHttpObject();@Subcomponent.Builderinterface Builder {HomeActivityComponent build();HomeActivityComponent.Builder databaseModule(DatabaseModule databaseModule);}
}
public class APP extends Application {private static final ApplicationComponent applicationComponent = DaggerApplicationComponent.create();public static ApplicationComponent getApplicationComponent(){return applicationComponent;}
}
@SuppressWarnings("all")
public class HomeActivity extends AppCompatActivity {HomeActivityComponent activityComponent;@InjectHttpObject httpObject;@Functionprotected void test() {activityComponent = APP.getApplicationComponent().activityComponentBuilder().build();activityComponent.injectHomeActivity(this);System.err.println(httpObject.hashCode());}
}

传递 Activity

如果我们自动创建的对象,需要传入一个 Activity 实例,该怎么办呢

其实很简单,对象依赖是通过对应的 Module 来管理的,而 Module 是可用自己指定的

所以我们只要将 Activity 传入 Module,再通过Module::@Provides来创建对象即可

public class DatabaseObject {HomeActivity homeActivity;public DatabaseObject(HomeActivity homeActivity) {this.homeActivity = homeActivity;}public HomeActivity getHomeActivity(){return homeActivity;}
}
@Module
public class DatabaseModule {HomeActivity homeActivity;public DatabaseModule(HomeActivity homeActivity){this.homeActivity = homeActivity;}@ActivityScope@Providespublic DatabaseObject provideDatabaseObject() {return new DatabaseObject(homeActivity);}
}
HomeActivityComponent.Builder builder = APP.getApplicationComponent().activityComponentBuilder();
builder.databaseModule(new DatabaseModule(this));
activityComponent = builder.build();
activityComponent.injectHomeActivity(this);
System.err.println(httpObject.hashCode());
System.err.println(databaseObject.hashCode());
System.err.println(databaseObject.getHomeActivity() == this);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/185343.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

山西电力市场日前价格预测【2023-11-09】

日前价格预测 预测说明: 如上图所示,预测明日(2023-11-09)山西电力市场全天平均日前电价为369.84元/MWh。其中,最高日前电价为784.47元/MWh,预计出现在17: 45。最低日前电价为158.90元/MWh,预计…

物联网水表有什么弊端吗?

物联网水表作为新一代智能水表,虽然在很大程度上提高了水资源的管理效率,但也存在一定的弊端。在这篇文章中,我们将详细讨论物联网水表的弊端,以帮助大家更全面地了解这一技术。 一、安全隐患 1.数据泄露:物联网水表通…

分布式数据库·Hive和MySQL的安装与配置

一、版本要求:Hadoop:hadoop-2.10.1、MySQL:mysql-8.0.35、 HIVE:apache-hive-3.1.2、MySQL驱动:mysql-connector-java-5.1.49 安装包网盘链接:阿里云盘分享 安装位置 Hive:master、MySQL:slave1 二、卸载已安装的…

人工智能辅助职业教育发展——开启教育新时代

人工智能辅助职业教育发展——开启教育新时代 随着科技的飞速发展,人工智能(AI)逐渐渗透到各行各业,并在许多领域发挥着重要作用。如今,AI的应用已经延伸到职业教育领域,为培养高素质人才提供了新的可能和动…

矩阵等价和向量组等价的一些问题

什么是向量组?答:向量组是由若干同维数的列向量(或同维数的行向量)组成的集合。什么是向量组等价?答:两个向量组,各自拼成矩阵A和B,向量组等价就是三秩相等,即r&#xff…

如何开发一个求职招聘小程序?详细步骤解析与教程

一、确定需求和功能 在开发求职招聘小程序之前,需要明确需求和功能。通过对市场和用户需求的调研和分析,确定小程序需要具备哪些功能,如职位发布、简历投递、在线沟通、面试安排等。 二、选择开发方式 求职招聘小程序的开发方式有多种选择…

智能井盖传感器功能,万宾科技产品介绍

在国家治理方面,对社会的治理是一个重要的领域,一定要在推进社会治理现代化过程中提高市政府的管理和工作能力,推动社会拥有稳定有序的发展。在管理过程中对全市井盖进行统一化管理,可能是市政府比较头疼的难题,如果想…

JAVA IDEA 下载

超简单步骤一: IntelliJ IDEA 官方下载链接 点击以上链接进入下图,点击下载 继续点下载,然后等待下载完后打开安装包即可 步骤二: 打开下好的安装包,点击Browse...我们把它下载到自己喜欢的地方(主要是别占…

电脑出现“此驱动器存在问题请立即扫描”该怎么办?

在您将可移动设备(例如:U盘、移动硬盘)连接到计算机时,您可能会收到一条错误消息“此驱动器存在问题请立即扫描并修复问题”。收到此错误消息后,您的设备在大多数情况下将无法访问。那么,电脑出现“此驱动器…

达梦SQL语法兼容笔记

1. DDL工具语法 查看库和表列表 # 查看所有数据库 select distinct object_name from all_objects where object_typeSCH; # 查看所有可见的表名: SELECT table_name FROM all_tables; # 查看用户可见的所有表 SELECT table_name FROM all_tables WHERE owner s…

家用AIO系统架构图(Openwrt 群晖 IPV6 DDNS)

折腾几个月了,摸索出的最合适的系统架构。其余的系统架构也都行得通,但是从逻辑角度,下列方案更加的自然通顺。 系统架构图 疑问解答 为什么用IPV6? 2222年了都不会真有人能从运营商哪里搞到ipv4或者还没有ipv6吧。 光猫为什么桥接? 抠门运…

【LeetCode刷题日志】160.相交链表

🎈个人主页:库库的里昂 🎐C/C领域新星创作者 🎉欢迎 👍点赞✍评论⭐收藏✨收录专栏:LeetCode 刷题日志🤝希望作者的文章能对你有所帮助,有不足的地方请在评论区留言指正,…

将对象与返回的数据所对应的键相同时一一赋值

问题描述 对象与返回的数据直接赋值,会将多余的键与值也添加上 那么赋值时值要 目标对象的键所对应的值 解决方案: 利用双重遍历 来比对 当 键相同时再赋值 duiYingFuZhi(obj,data){for (let key in obj) {for (let index in data) {if (keyindex) {obj…

【第2章 Node.js基础】2.1 JavaScript基本语法

文章目录 学习目标JavaScript版本与JavaScript运行环境JavaScript版本JavaScript运行环境 JavaScript语句与注释语句语句块注释 变量变量的命名变量的声明与赋值变量提升变量泄露全局作用域和函数作用域块级作用域与let关键字使用const关键字声明只读常量注意 数据类型数值&…

uniapp:打包ios配置隐私协议框

使用uniapp打包ios 上架商店需要配置隐私协议政策弹窗。当用户点击确定后才能继续操作。 首先manifest.json中配置使用原生隐私政策提示框是不支持ios的。不用勾选。 解决思路: 1、新建页面:iosLogin.vue,pages.json中 这个页面需要放在第一…

HiSilicon352 android9.0 适配红外遥控器

海思Android解决方案在原生Android基础上,基于传统电视用户使用习惯,增加了对红外遥控器和按键板的支持,使传统电视用户能更好适应智能电视方案。 一.功能描述: 在系统启动时,会先启动android_ir_user;vinp…

游戏平台采集数据

首先,你需要在你的项目中添加Kotlin的网络库,例如OkHttp。你可以在你的build.gradle文件中添加以下依赖: dependencies {implementation com.squareup.okhttp3:okhttp:4.9.0 }然后,你可以使用以下代码来创建一个基本的网络爬虫&a…

基于袋獾算法的无人机航迹规划-附代码

基于袋獾算法的无人机航迹规划 文章目录 基于袋獾算法的无人机航迹规划1.袋獾搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用袋獾算法来优化无人机航迹规划。 1.袋獾搜索算法 …

pandas笔记:读写excel

1 读excel read_excel函数能够读取的格式包含:xls, xlsx, xlsm, xlsb, odf, ods 和 odt 文件扩展名。 支持读取单一sheet或几个sheet。 1.0 使用的数据 1.1 主要使用方法 pandas.read_excel(io, sheet_name0, header0, namesNone, index_colNone, usecolsNon…

康耐视VisionPro 9.0 R2破解安装教程

文章目录 说明下载安装VisionPro破解匹配的Visual Studion将VisionPro的控件添加到VS工具箱中 说明 康耐视VisionPro 9.0 R2 破解版仅用于个人学习使用,如企业中需要请自行购买正版哦。 下载 百度网盘链接:https://pan.baidu.com/s/1rreSzpe8r2Gz8qSp…