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

news2024/12/23 17:11:12

文章目录

  • 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);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue路由使用参数传递数据

一、使用query参数传递数据 &#xff08;一&#xff09;参数的传递 1. 携带参数进行传递 <router-link to"/路径?参数名1参数值1&参数名2参数值2">内容</router-link> 我们在下面的代码中传递每条消息的id和标题&#xff1a; 2. 配置对象进行传递…

计算/存储虚拟化高级特性

目录 计算虚拟化特性 HA高可用 虚拟机热迁移 虚拟机快照技术 存储虚拟化特性 链接克隆 存储热迁移 裸设备映射 计算虚拟化特性 HA高可用 通过HA&#xff08;High Available&#xff09;机制&#xff0c;可以提升虚拟机的可用度&#xff0c;允许虚拟机所在的服务器节点…

采集Prestashop独立站

这是一个用Lua编写的爬虫程序&#xff0c;用于采集Prestashop独立站的内容。爬虫程序使用代理信息&#xff1a;proxy_host: jshk.com.cn。 -- 首先&#xff0c;我们需要导入所需的库 local http require(socket.http) local url require(socket.url)-- 然后&#xff0c;我们…

互联网线上预约洗衣洗鞋店软件功能介绍:

互联网线上预约洗衣洗鞋店软件功能介绍&#xff1a; 1. 在线下单&#xff1a;用户可以直接打开小程序&#xff0c;查看各类鞋子洗护服务的费用、细节等情况&#xff0c;方便用户按照需求进行对应的服务下单&#xff0c;并设置收货地址进行在线支付。用户可以选择不同的洗护服务…

广告垄断是对创业者的一种不公平

每次过节例如国庆节&#xff0c;中秋节&#xff0c;双十一&#xff0c;618&#xff0c;春节&#xff0c;抖音上面都会充满了各色各样的品牌广告&#xff0c;但是都有一个特点&#xff1a;几乎都是很少几个人的广告&#xff0c;但是小公司的广告几乎看不见&#xff0c;或者没有人…

宝塔部署QQ机器人,提示OpenSSL 1.0.2k-fips 26 Jan 2017

1、报错预览 Traceback (most recent call last):File "/www/wwwroot/python/bot-one/main.py", line 5, in <module>import requestsFile "/www/wwwroot/python/bot-one/343ae0eb0d491a10a1a00c0621b03ed0_venv/lib/python3.9/site-packages/requests/_…

XCTF刷题十一道(01)

文章目录 Training-WWW-RobotsPHP2unserialize3view-sourceget_postrobotsbackupcookiedisabled_buttonweak_authsimple_php Training-WWW-Robots robots.txt&#xff0c;防爬虫&#xff0c;访问urlrobots.txt PHP2 phps源码泄露 >phps文件就是php的源代码文件&#xff0…

MaHDE

FHM means ‘fitness hierarchical mutation’&#xff0c;DGS means ‘directed global search’&#xff0c;ELS means ‘elite local search’ 辅助信息 作者未提供代码

新生儿发烧:原因、科普和注意事项

引言&#xff1a; 新生儿发烧是新父母常常担心的问题之一&#xff0c;因为婴儿的免疫系统尚未完全发育&#xff0c;对感染更为脆弱。尽管发烧在婴儿中是相对常见的&#xff0c;但它可能引起家长的焦虑。本文将科普新生儿发烧的原因&#xff0c;提供相关信息&#xff0c;并为父…

面试题:经典常见排序算法 插入 冒泡 选择 归并 快速排序

1.插入排序 从头向尾不断扩大排序范围 (保持范围内顺序) 时间复杂度 O(n2) 2.冒泡排序 从第i1个数据和第i1个数据 进行比较 大的向后移 直到移动到他的为止&#xff08;以最大值为主要观察对象 最大值逐个排到正确位置&#xff09; 时间复杂度 O(n2) 3.选择排…

presto插件机制揭秘:探索无限可能的数据处理舞台

文章目录 1. 前言2. Presto插件架构3. Plugin接口3.1 插件协议3.2 插件实现类 4. 插件加载过程4.1 PluginManager 5. 插件应用6. 总结 关键词&#xff1a;Presto Plugin 1. 前言 本文源码环境&#xff1a; presto: prestoDb 0.275版本 在Presto框架中插件机制设计是一种非常常见…

ubuntu| sudo apt-get update 更新失败, 没有 Release 文件 无法安全地用该源进行更新,所以默认禁用该源

xiaoleubt:~$ sudo apt-get update -y 命中:1 https://dl.google.com/linux/chrome/deb stable InRelease 忽略:2 http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make/ubuntu focal InRelease 命中:3 https://packages.microsoft.com/repos/code stable InRelease 命中:4 ht…

全志A40i应用笔记 | 3种常见的网卡软件问题以及排查思路

在飞凌嵌入式OKA40i-C开发板上虽然只有一个网口&#xff0c;但全志A40i-H处理器本身是有两个网络控制器的&#xff0c;因此在飞凌嵌入式提供的产品资料中提供了双网口解决方案。有的工程师小伙伴在开发过程中会遇见一些网卡的设计问题&#xff0c;今天小编为大家分享3种在使用O…

(待完善)python学习参考手册

这里写目录标题 观前浅谈:学习路线 :学习心得笔记:Step1:简单但一问不知怎么的组织语言去回答的小问题:什么是提示符?python解释器是什么?请正在阅读本文的朋友,安装一下PyCharm以及如何进行科学的省钱:Python中的命令行模式和交互模式的区别是什么?请正在阅读本文的朋友安装…

伦敦金开户需要多少资金,有开户条件吗?

伦敦金&#xff08;London Gold&#xff09;是黄金市场中备受瞩目的投资种类之一&#xff0c;无论是专业投资者还是新手&#xff0c;都对伦敦金感兴趣。但关于开户需要多少资金&#xff0c;以及是否有特定的开户条件&#xff0c;这些问题可能会让一些新手投资者感到困惑。 首先…

SpringCloud之Seata基本介绍与安装

目录 基本介绍 概述 核心组件 四种方案 部署TC服务&#xff08;安装&#xff09; 下载 修改registry.conf nacos添加配置 建表(仅db) 启动 基本介绍 概述 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将…

(免费领源码)C#语言;SQL数据库基于.NET的土特产销售系统的设计与实现27155-计算机毕业设计项目选题推荐

内容摘要 随着Internet技术的发展&#xff0c;土特产销售系统应运而生&#xff0c;土特产销售系统为广大提供了一个更为便利的商品查询、购买、管理平台。为了充分满足用户在线购买土特产的需求&#xff0c;特开发了本土特产销售系统。 本土特产销售系统的开发采用的是C#语言&a…

【STM32 PWM输出+串口调整PWM周期和占空比】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、PWM是什么&#xff1f;1. PWM 图解二、认识STM32的PWM功能1.哪些定时器有PWM输出功能1.1 高级定时器&#xff0c;7路PWM输出&#xff0c;3组是互补输出&…

Node.js如何处理多个请求?

前言 在计算机科学领域&#xff0c;关于并发和并行的概念经常被提及。然而&#xff0c;这两个术语常常被混为一谈&#xff0c;导致很多人对它们的理解存在着很多混淆。本文小编将通过对并发和并行的深入解析&#xff0c;帮助读者更好地理解它们之间的不同特点和应用场景。同时…