热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

IOC架构设计之Dagger2架构设计(三)

阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https:space.bilibili.com474380680一、概述Dagger2依赖注入框架的好处:依赖的注入

阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680


一、概述

Dagger2依赖注入框架的好处:



  • 依赖的注入和配置独立于组件之外

  • 依赖对象是在一个独立、不耦合的地方初始化,当初始化方式改变的时候修改的代码少。

  • 依赖注入使得单元测试更加简单。

Dagger2相对于其它框架的优点:



  • 编译期生成代码,有错误会在编译期报出。

  • 错误可追踪。

  • 易于调试。

Dagger2的缺点:



  • 缺少灵活性。

  • 没有动态机制。


二、Dagger2的注解

Dagger2的注解主要有以下七类:



  • @Inject:这个注解有两个作用:在目标类中标记成员变量告诉Dagger这个类型的变量需要一个实例对象;标记依赖类中的构造方法,告诉Dagger我可以提供这种类型的依赖实例。

  • @Component:用来标记接口或者抽象类,也被称为注入器,是@Inject@Module的桥梁,所有的Component都可以通过它的modules知道它所提供的依赖范围,一个Componet可以依赖一个或多个Component,并拿到被依赖Component暴露出来的实例,Componenetdependencies属性就是确定依赖关系的实现。

  • @Module:用来标记类,一般类名以Module结尾,Module的主要作用是用来集中管理@Provides标记的方法,我们定义一个被@Module注解的类,Dagger就会知道在哪里找到依赖来满足创建类的实例,Module的一个重要特征是被设计成区块并可以组合在一起。

  • @Provides:对方法进行注解,并且这些方法都是有返回类型的,告诉Dagger我们向如何创建并提供该类型的依赖实例(一般会在方法中new出实例),用@Provides标记的方法,推荐用provide作为前缀。

  • @Qualifier:限定符,当一个类的类型不足以标示一个依赖的时候,我们就可以用这个注解,它会调用DataModule中方法来返回合适的依赖类实例。

  • @Scope:通过自定义注解来限定作用域,所有的对象都不再需要知道怎么管理它的实例,Dagger2中有一个默认的作用域注解@Singleton,通常用来标记在App整个生命周期内存活的实例,也可以定义一个@PerActivity注解,用来表明生命周期要与Activity一致。

  • @SubComponent:如果我们需要父组件全部的提供对象,我们就可以用包含方式,而不是用依赖方式,包含方式不需要父组件显示显露对象,就可以拿到父组件全部对象,且SubComponent只需要在父Component接扣中声明就可以了。


三、Dagger2的简单应用 - @Inject@Component

第一步:基础配置,在build.gradle中添加相应的依赖:

//添加(1)
apply plugin: 'com.neenbedankt.android-apt'
buildscript {
repositories {
jcenter()
}
dependencies {
//添加(2)
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.0"
defaultConfig {
applicationId "com.demo.zejun.repodragger2"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.0'
//添加(3)
apt 'com.google.dagger:dagger-compiler:2.0'
//添加(4)
compile 'com.google.dagger:dagger:2.0'
}

第二步:User作为目标类中需要实例化的成员对象,给其构造函数添加@Inject标签:

public class User {
public String name;
@Inject
public User() {
name = "lizejun";
}
public String getName() {
return name;
}
}

第三步:声明Component

@Component()
public interface OnlyInjectComponent {
void inject(AnnotationActivity annotationActivity);
}

第四步:在目标类中添加注解@Inject,并根据我们第3步中声明的Component,调用DaggerXXX方法来进行注入:

public class AnnotationActivity extends AppCompatActivity {
@Inject
public User mUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dragger2);
//在第3步声明的Component接口或者抽象类的基础上,添加Dagger前缀。
DaggerOnlyInjectComponent.builder().build().inject(this);
}
}

上面这个例子有两个缺点:



  • 只能标记一个构造方法,因为如果标记两个以上,不知道要用哪一个构造提供实例。

  • 不能标记其它我们不能修改的类,例如第三方库。

  • 如果用@Inject标记的构造函数如果有参数,那么这个参数也需要其它地方提供依赖,而类似于String这些我们不能修改的类,只能用@Module中的@Provides来提供实例了。


四、采用@Module来提供依赖

采用@Module标记的类提供依赖是常规套路,@Module标记的类起管理作用,真正提供依赖实例靠的是@Provides标记的带返回类型的方法。
第一步:和上面类似,我们定义一个依赖类,但是它的构造方法并不需要用@Inject标记:

public class Person {
private String name;
public Person() {
this.name = "lizejun";
}
public String getName() {
return name;
}
}

第二步:我们需要定义一个@Module来管理这些依赖类的实例:

@Module
public class PersonDataModule {
@Provides
public Person providePerson() {
return new Person();
}
}

第三步:定义一个@Component,它指向上面定义的@Module

@Component(modules = {PersonDataModule.class})
public interface PersonInjectComponent {
void inject(PersonInjectActivity injectActivity);
}

第四步:在目标类中进行依赖注入

public class PersonInjectActivity extends Activity {
@Inject
Person mPerson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPersonInjectComponent.create().inject(this);
System.out.println("Person name=" + mPerson.getName());
}
}

这里注入的方式有两种,一种是像上面这样的,它适合于PersonDataModule中只有一个无参的构造方法,否则我们需要这样调用:

DaggerPersonInjectComponent.builder().personDataModule(new PersonDataModule()).build().inject(this);


五、初始化依赖实例的步骤


  • 查找Module中是否存在创建该类型的方法(即@Component标记的接口中包含了@Module标记的Module类,如果没有则直接查找@Inject对应的构造方法)。



  • 如果存在创建类方法,则查看该方法是否有参数



  • 如果不存在参数,直接初始化该类的实例,一次依赖注入到此结束。



  • 如果存在参数,则从步骤1开始初始化每个参数。



  • 如果不存在创建类方法,则查找该类型的类中有@Inject标记的构造方法,查看构造方法是否有参数:



  • 如果不存在参数,则直接初始化该类实例,一次依赖注入到此结束。



  • 如果存在参数,则从步骤1开始初始化每个参数。




六、@Qualifier限定符

Dagger中,有一个已经定义好的限定符,@Name,下面我们也自己定义一个限定符:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PeopleThreeQualifier {}

第一步:和前面类似,我们先定义一个需要实例化的依赖类:

public class People {
private int count;
public People() {
count = 0;
}
public People(int count) {
this.count = count;
}
public int getCount() {
return count;
}
}

第二步:我定义一个DataModule,和前面不同的是,在它的provideXXX方法的注解中,我们添加了@Name(xxx)和自定义的注解PeopleThreePeople

@Module
public class PeopleDataModule {
@Provides
@Named("Five People")
People provideFivePeople() {
return new People(5);
}
@Provides
@Named("Ten People")
People provideTenPeople() {
return new People(10);
}
@Provides
@PeopleThreeQualifier
People provideThreePeople() {
return new People(3);
}
}

第三步:定义Component

@Component(modules = PeopleDataModule.class)
public interface PeopleInjectComponent {
void inject(PeopleInjectActivity peopleInjectActivity);
}

第四步:在目标类中进行依赖注入,在提供@Inject注解时,我们还需要声明和PeopleDataModule中对应的限定符,这样Dagger就知道该用那个函数来生成目标类中的依赖类实例:

public class PeopleInjectActivity extends Activity {
@Inject
@Named("Five People")
People mFivePeople;
@Inject
@Named("Ten People")
People mTenPeople;
@Inject
@PeopleThreeQualifier
People mThreePeople;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerPeopleInjectComponent.builder().peopleDataModule(new PeopleDataModule()).build().inject(this);
System.out.println("Five People=" + mFivePeople.getCount() + ",Ten People=" + mTenPeople.getCount() + ", Three People=" + mThreePeople.getCount());
}
}


七、@Scope

@Scope的作用主要是在组织ComponentModule的时候起到一个提醒和管理的作用,在Dagger中,有一个默认的作用域@Singleton
@Scope的作用是:Dagger2可以通过自定义Scope注解,来限定通过ModuleInject方式创建的类的实例的生命周期能够与目标类的生命周期相同。Scope的真正作用在与Component的组织:



  • 更好的管理Component之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定的Scope注解标注这些Component,而且编译器会检查有依赖关系或包含关系的Component,若发现有Component没有用自定义Scope注解,则会报错。

  • 更好地管理ComponentModule之间地关系,编译器会检查Component管理的Module,若发现Component的自定义Scope注解与Module中的标注创建类实例方法的注解不一样,就会报错。

  • 提高程序的可读性。

下面是一个使用@Singleton的例子:
第一步:定义需要实例化的类:

public class AnSingleObject {
private String objectId;
public AnSingleObject() {
objectId = toString();
}
public String getObjectId() {
return objectId;
}
}

第二步:定义DataModule,在它的provideXXX方法,提供了@Singletion注解:

@Module
public class AnSingleObjectDataModule {
@Provides
@Singleton
AnSingleObject provideAnSingleObject() {
return new AnSingleObject();
}
}

第三步:定义Component,和前面不同的是,需要给这个Component添加@Singleton注解:

@Component(modules = {AnSingleObjectDataModule.class})
@Singleton
public abstract class AnSingleObjectInjectComponent {
private static AnSingleObjectInjectComponent sInstance;
public abstract void inject(AnSingleObjectInjectActivity anSingleObjectInjectActivity);
public static AnSingleObjectInjectComponent getInstance() {
if (sInstance == null) {
sInstance = DaggerAnSingleObjectInjectComponent.create();
}
return sInstance;
}
}

第四步:在目标类中进行依赖注入,每次启动Activity的时候,我们可以发现打印出来的hash值都是相同的:

public class AnSingleObjectInjectActivity extends Activity {
@Inject
AnSingleObject object;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AnSingleObjectInjectComponent.getInstance().inject(this);
System.out.println("AnSingleObject id=" + object.getObjectId());
}
}


八、组织Component

Component有三种组织方式:



  • 依赖:一个Component依赖一个或多个Component,采用的是@Componentdependencies属性。

  • 包含:这里就用到了@SubComponent注解,用它来标记接口或者抽象类,表示它可以被包干。一个Component可以包含一个或多个Component,而且被包含的Component还可以继续包含其它的Component

  • 继承:用一个Component继承另外一个Component


九、Google官方框架分析

下面是Google官方框架的目录结构:


 
1949836-312ef6841c974953.png
 

可以看出,它把每个界面都作为一个独立的包(addedittask、statistics、taskdetail、tasks),而数据、依赖类是其它的两个包(data、util),我们先从ToDoApplication开始分析:

public class ToDoApplication extends Application {
private TasksRepositoryComponent mRepositoryComponent;
@Override
public void onCreate() {
super.onCreate();
mRepositoryCompOnent= DaggerTasksRepositoryComponent.builder()
.applicationModule(new ApplicationModule((getApplicationContext())))
.build();
}
public TasksRepositoryComponent getTasksRepositoryComponent() {
return mRepositoryComponent;
}
}

ToDoApplication中,我们实例化了一个变量TasksRepositoryComponent,它相当于是项目中所有其它Component的管理者,它被声明为@Singleton的,即在App的生命周期中只存在一个,同时用@Component表明它和TaskRepositoyModuleApplicationModule这两个Module关联。

@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}

TaskRpositotyModule提供了两种类型的数据源对象,它们是用@Local@Remote来区分的:

@Module
public class TasksRepositoryModule {
@Singleton
@Provides
@Local
TasksDataSource provideTasksLocalDataSource(Context context) {
return new TasksLocalDataSource(context);
}
@Singleton
@Provides
@Remote
TasksDataSource provideTasksRemoteDataSource() {
return new FakeTasksRemoteDataSource();
}
}

接下来再看一下ApplicationModule

@Module
public final class ApplicationModule {
private final Context mContext;
ApplicationModule(Context context) {
mCOntext= context;
}
@Provides
Context provideContext() {
return mContext;
}
}

下面我们用一个比较简单的界面来看一下TasksRepositoryComponent是怎么和其它的Component关联起来的,首先看StatisticsActivity

public class StatisticsActivity extends AppCompatActivity {
private DrawerLayout mDrawerLayout;
@Inject
StatisticsPresenter mStatiticsPresenter; //依靠Dagger实例化的对象。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.statistics_act);
//初始化Fragment界面。
StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (statisticsFragment == null) {
statisticsFragment = StatisticsFragment.newInstance();
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
statisticsFragment, R.id.contentFrame);
}
DaggerStatisticsComponent.builder()
.statisticsPresenterModule(new StatisticsPresenterModule(statisticsFragment))
.tasksRepositoryComponent(((ToDoApplication) getApplication())
.getTasksRepositoryComponent())
.build().inject(this);
}
}

原文链接:https://www.jianshu.com/p/5285f48b6336
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680



推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
author-avatar
灵小星星_364
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有