【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 {

    @Inject
    public HttpObject() {

    }

    public void post() {

    }
}

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

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

创建 Module
import dagger.Module;
import dagger.Provides;

@Module
public class HttpModule {

    @Provides
    public 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;

    @Inject
    HttpObject httpObject;

    @Inject
    DatabaseObject databaseObject;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityHomeBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        //编写测试代码
        try {
            test();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    @Function
    protected 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
    @Singleton
    public HttpObject provideHttpObject() {
        return new HttpObject();
    }
}
@Singleton
public class HttpObject {

    @Inject
    public 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;

    @Inject
    HttpObject httpObject;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityHomeBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        test();
    }

    @Function
    protected void test() {
        activityComponent = DaggerActivityComponent.builder().build();
        activityComponent.injectHomeActivity(this);
        System.err.println(httpObject.hashCode());
        //add fragment
        FragmentManager 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;

    @Inject
    HttpObject httpObject;

    @Override
    public 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.Builder
    interface 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;

    @Inject
    HttpObject httpObject;

      @Function
    protected 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
    @Provides
    public 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);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

verilog 每日一练- 移位寄存器

module shift_1x64 (clk, shift,sr_in,sr_out,);input clk, shift;input sr_in;output sr_out;reg [63:0] sr;always(posedge clk)beginif (shift 1b1)beginsr[63:1] < sr[62:0];sr[0] < sr_in;endendassign sr_out sr[63];endmodule 这个Verilog模块 shift_1x64 实现了…

Leetcode—226.翻转二叉树【简单】

2023每日刷题&#xff08;二十四&#xff09; Leetcode—226.翻转二叉树 实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* …

分享vmware和Oracle VM VirtualBox虚拟机的区别,简述哪一个更适合我?

VMware和Oracle VM VirtualBox虚拟机的区别主要体现在以下几个方面&#xff1a; 首先两种软件的安装使用教程如下&#xff1a; 1&#xff1a;VMware ESXI 安装使用教程 2&#xff1a;Oracle VM VirtualBox安装使用教程 商业模式&#xff1a;VMware是一家商业公司&#xff0c;而…

npm 下载包失败解决方案

1.【问题描述】使用 npm 下载vue项目依赖包时失败&#xff0c;版本不一致。 【解决方法】使用 npm install --force npm install --force 是一个命令行指令&#xff0c;用于在 Node.js 环境中使用 npm&#xff08;Node Package Manager&#xff09;安装包或模块。–force 参数表…

ubuntu18-recvfrom接收不到广播报文异常分析

目录 前言 一、UDP广播接收程序 二、异常原因分析 总结 前言 在ubuntu18.04系统中&#xff0c;编写udp接收程序发现接收不到广播报文&#xff0c;使用抓包工具tcpdump可以抓取到广播报文&#xff0c;在此对该现象分析解析如下文所示。 一、UDP广播接收程序 UDP广播接收程序如…

第三章 UI开发的点点滴滴

一、常用控件的使用方法 1.TextView android:gravity"center" 可选值&#xff1a;top、bottom、left、right、center等&#xff0c;可以用"|"来同时指定多个值&#xff0c;center表示文字在垂直和水平方向都居中 android:textSize 指定文字的大小&#…

双十一数码好物推荐,盘点那些错过等一年的好物!

双十一购物狂欢节马上到来&#xff0c;对于热爱数码产品的人来说&#xff0c;双十一无疑是一个绝佳的时机&#xff0c;因为许多知名品牌和零售商都会推出各种令人心动的数码好物促销活动。从佩戴服饰到大件智能装备&#xff0c;再到健康科技产品&#xff0c;市场上的选择多种多…

ASUS华硕灵耀X2 Duo UX481FA(FL,FZ)_UX4000F工厂模式原装出厂Windows10系统

下载链接&#xff1a;https://pan.baidu.com/s/1sRHKBOyc3zu1v0qw4dSASA?pwd7nb0 提取码&#xff1a;7nb0 带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、MyASUS华硕电脑管家等预装程序所需要工具&#xff1a;16G或以上…

【mongoose】mongoose 基本使用

1. 连接数据库 // 1. 安装 mongoose // 2. 导入 mongoose const mongoose require(mongoose) // 3. 连接 mongodb 服务 mongoose.connect(mongodb://127.0.0.1:27017/xx_project) // 4. 设置回调 .on 一直重复连接 .once 只连接一次 mongoose.connection.on(open, () >…

【MySQL】一文学会所有MySQL基础知识以及基本面试题

文章目录 前言 目录 文章目录 前言 一、主流数据库以及如何登陆数据库 二、常用命令使用 三、SQL分类 3.1 存储引擎 四、创建数据库如何设置编码等问题 4.1操纵数据库 4.2操纵表 五、数据类型 六、表的约束 七、基本查询 八、函数 九、复合查询 十、表的内连和外连 十一、索引…

Flink之状态管理

Flink状态管理 状态概述状态分类 键控、按键分区状态概述值状态 ValueState列表状态 ListStateMap状态 MapState归约状态 ReducingState聚合状态 Aggregating State 算子状态概述列表状态 ListState联合列表状态 UnionListState广播状态 Broadcast State 状态有效期 (TTL)概述S…

JSON——数组语法

一段JSON可能是以 ”{“ 开头 也可能仅包含一段JSON数组 如下 [ { "name" : "hello,world"}, {"name" : "SB JSON”}&#xff0c; {“name” : "SB互联网房地产CNM“}&#xff0c; ] 瞧&#xff0c;蛋疼不...CJSON过来还是得搜下网…

持续集成交付CICD:安装Gitlab Runner(从节点)

目录 一、实验 1.选择Gitlab Runner版本 2.安装Gitlab Runner&#xff08;第一种方式&#xff1a;交互式安装&#xff09; 3.安装Gitlab Runner&#xff08;第二种方式&#xff1a;非交互式安装&#xff09; 二、问题 1.如何查看Gitlab版本 一、实验 1.选择Gitlab Runne…

『 MySQL数据库 』数据库基础之库的基本操作

文章目录 库的操作创建数据库字符集与校验集那么该如何查看当前数据库默认的字符集与校验规则?查看数据库所支持的字符集与校验集不同字符集(校验集)之间的区别 基本操作查看数据库显式数据库创建语句数据库的修改数据库的删除数据库的备份检查连接 库的操作 创建数据库 CRE…

【ES专题】ElasticSearch功能详解与原理剖析

目录 前言要点阅读对象阅读导航前置知识笔记正文一、ES数据预处理1.1 Ingest Node&#xff1a;摄入节点1.2 Ingest Pipeline&#xff1a;摄入管道1.3 Processor&#xff1a;预处理器——简单加工1.4 Painless Script&#xff1a;脚本——复杂加工1.5 简单实用案例 二、文档/数据…

网际报文协议ICMP及ICMP重定向实例详解

目录 1、ICMP的概念 2、ICMP重定向 3、利用ICMP重定向进行攻击的原理 4、如何禁止ICMP重定向功能&#xff1f; 4.1、在Linux系统中禁用 4.2、在Windows系统中禁用 5、关于ICMP重定向的问题实例 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xf…

数据结构与算法—插入排序选择排序

目录 一、排序的概念 二、插入排序 1、直接插入排序 直接插入排序的特性总结&#xff1a; 2、希尔排序 希尔排序的特性总结&#xff1a; 三、选择排序 1、直接选择排序 时间复杂度 2、堆排序—排升序(建大堆) 向下调整函数 堆排序函数 代码完整版&#xff1a; …

VUE多语言i18n配置

1、i18n官网 格式化 | Vue I18n 2、安装i18n 8---指版本号 // vue2必须安装8版本的i18n包&#xff0c;vue3必须安装9版本的i18n包。 npm i vue-i18n8 3、卸载i18n npm uninstall vue-i18n 4、安装 js-cookie npm install vue-cookies --save 5、代码 5.1 main.js /…

GPT出现了Something went wrong.

网络上的一种说法如下

数据结构(超详细讲解!!)第二十二节 广义表

1.定义 广义表&#xff0c;顾名思义&#xff0c;也是线性表的一种推广。广义表被广泛地应用于人工智能等领域的表处理语言LISP语言中。在LISP语言中&#xff0c;广义表是一种最基本的数据结构&#xff0c;就连LISP 语言的程序也表示为一系列的广义表。 广义表又称列表&#x…