前言:

最近牵头发起公司app的重构工作,如何通过重构让项目的耦合降低、开发效率提高,一直是我努力的方向,今天来学习一下一个注解框架Dagger2,然后看看如何使用它来降低项目的耦合。

Dagger2

一句话:一款快速的注解框架,应用于Android、Java,由 Google 开发和维护,是 Square 的 Dagger 项目的分支。

gitHub:https://github.com/google/dagger

Dagger2采用依赖注入方式,依赖注入是一种面向对象的编程模式,它的出现是为了降低耦合性,所谓耦合就是类之间依赖关系,所谓降低耦合就是降低类和类之间依赖关系。

依赖关系

Java的面向对象编程特性,通常会在一个Java对象中引用另一个Java对象,举例说明一下:

  1. public class ClassA {
  2. private ClassB classB;
  3.  
  4. public ClassA(){
  5. classB =new ClassB();
  6. }
  7.  
  8. public void doSomething(){
  9. classB.doSomething();
  10. }
  11. }

通过上面的例子可以看出,ClassA需要借助ClassB才能完成一些特定操作,但是我们在ClassA直接实例化了ClassB,这样耦合就产生了,第一违背了单一职责原则,ClassB的实例化应该由自己完成,不应该由ClassA来完成,第二违背了开闭原则,一旦ClassB的构造函数产生变化,就需要修改ClassA的构造函数。

通过依赖注入降低这种耦合关系:

1.通过构造参数传参的方式

  1. public class ClassA {
  2. private ClassB classB;
  3.  
  4. public ClassA(ClassB classB){
  5. this.classB =classB;
  6. }
  7.  
  8. public void doSomething(){
  9. classB.doSomething();
  10. }
  11. }

2.通过set方法的方式

  1. public class ClassA {
  2. private ClassB classB;
  3.  
  4. public ClassA(){
  5. }
  6.  
  7. public void setClassB(ClassB classB) {
  8. this.classB = classB;
  9. }
  10.  
  11. public void doSomething(){
  12. classB.doSomething();
  13. }
  14. }

3.通过接口注入的方式

  1. interface ClassBInterface {
  2. void setB(ClassB classB);
  3. }
  4.  
  5. public class ClassA implements ClassBInterface {
  6. private ClassB classB;
  7.  
  8. public ClassA() {
  9. }
  10.  
  11. @Override
  12. public void setB(ClassB classB) {
  13. this.classB = classB;
  14. }
  15.  
  16. public void doSomething() {
  17. classB.doSomething();
  18. }
  19. }

4.通过注解注入

  1. public class ClassA {
  2. @Inject
  3. ClassB classB;
  4.  
  5. public ClassA() {
  6. }
  7.  
  8. public void doSomething() {
  9. classB.doSomething();
  10. }
  11. }

Dagger2采用的就是注解注入的方式,然后编译自动生成目标代码的方式实现宿主与被依赖者之间的关系。

Dagger2在Android的使用方式及简单说明

在Android中的使用方式很简单:只需在Module的build.gradle中添加一下配置

  1. dependencies {
  2. compile 'com.google.dagger:dagger:2.x'
  3. annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
  4. }

Dagger2 annotation讲解

  • @Module 修饰的类专门用来提供依赖

  • @Provides 修饰的方法用在Module类里

  • @Inject  修饰需要依赖的地方(可以是构造方法、field或者一般的方法)

  • @Component 连接@Module和注入的桥梁

Dagger2举例说明

以项目中实际场景缓存管理为例,来体验一下解耦效果。设计遵循单一职责原则。

1.首先定义缓存类和多任务类。并且在其构造函数上添加@Inject注解

LCache类

  1. /**
  2. * Created by lichaojun on 2017/3/30.
  3. * 处理缓存
  4. */
  5. public class LCache {
  6. private static final String DEFAULT_CACHE_NAME="LCache";//默认缓存名字
  7. private static final int DEFAULT_MAX_CACHE_SIZE=1024;//默认缓存名字
  8. private String cacheName=DEFAULT_CACHE_NAME;//缓存名字
  9. private int maxCacheSize=DEFAULT_MAX_CACHE_SIZE;
  10.  
  11. public LCache (){
  12. }
  13.  
  14. @Inject
  15. public LCache(String cacheName,int maxCacheSize){
  16. this.cacheName=cacheName;
  17. this.maxCacheSize=maxCacheSize;
  18. }
  19.  
  20. public void saveCache(String key ,String value){
  21. Log.e(LCacheManager.TAG,"cacheName: = "+cacheName);
  22. Log.e(LCacheManager.TAG,"maxCacheSize: = "+maxCacheSize);
  23. Log.e(LCacheManager.TAG,"saveCache: key = "+key +" value = "+value);
  24. }
  25.  
  26. public void readCache(String key){
  27. Log.e(LCacheManager.TAG,"readCache: key: = "+key);
  28. }
  29. }

LExecutor类

  1. public class LExecutor {
  2. private static final int DEFAULT_CPU_CORE = Runtime.getRuntime().availableProcessors();//默认线程池维护线程的最少数量
  3. private int coreSize = DEFAULT_CPU_CORE;//线程池维护线程的最少数量
  4.  
  5. @Inject
  6. public LExecutor(int coreSize) {
  7. this.coreSize = coreSize;
  8. }
  9.  
  10. public void runTask(Runnable runnable) {
  11. if (runnable == null) {
  12. return;
  13. }
  14. Log.e(LCacheManager.TAG,"coreSize: = "+coreSize);
  15. Log.e(LCacheManager.TAG, "runTask");
  16. runnable.run();
  17. }
  18. }

2.使用@Module分别定义LCacheModule、LExecutorModule类来提供相关依赖

LCacheModule类

  1. @Module
  2. public class LCacheModule {
  3.  
  4. /**
  5. * 提供缓存对象
  6. * @return 返回缓存对象
  7. */
  8. @Provides
  9. @Singleton
  10. LCache provideLCache() {
  11. return new LCache("lcj",500);
  12. }
  13.  
  14. }

LExecutorModule类

  1. @Module
  2. public class LExecutorModule {
  3.  
  4. /**
  5. * 提供app 多任务最少维护线程个数
  6. * @return 返回多任务最少维护线程个数
  7. */
  8. @Provides
  9. @Singleton
  10. LExecutor provideLExecutor() {
  11. return new LExecutor(10);
  12. }
  13. }

3.使用@Component 用来将@Inject和@Module关联起来,新建LCacheComponent类

  1. @Component(modules = {LCacheModule.class,LExecutorModule.class})
  2. @Singleton
  3. public interface LCacheComponent {
  4.  
  5. LCache lCache(); // app缓存
  6.  
  7. LExecutor lExecutor(); // app多任务线程池
  8.  
  9. void inject(LCacheManager lCacheManager);
  10. }

4.在宿主中注入想要依赖的对象

  1. /**
    * Created by lichaojun on 2017/3/30.
    * 缓存处理管理
    */
    public class LCacheManager {
    public static final String TAG=LCacheManager.class.getSimpleName();
    private LCacheComponent cacheComponent;
  2.  
  3. private static class SingletonHolder {
    private static LCacheManager instance = new LCacheManager();
    }
  4.  
  5. private LCacheManager(){
    cacheComponent = DaggerLCacheComponent.builder().lCacheModule(new LCacheModule()).build();
    cacheComponent.inject(this);
    }
  6.  
  7. public static LCacheManager getInstance() {
    return SingletonHolder.instance;
    }
  8.  
  9. public void saveCache(final String key , final String value) {
    cacheComponent.lExecutor().runTask(new Runnable() {
    @Override
    public void run() {
    cacheComponent.lCache().saveCache(key,value);
    }
    });
    }
  10.  
  11. public void readCache(final String key){
    cacheComponent.lExecutor().runTask(new Runnable() {
    @Override
    public void run() {
    cacheComponent.lCache().readCache(key);
    }
    });
    }
    }

5.使用场景调用及简单解说

  1. LCacheManager.getInstance().saveCache("key","who is lcj ?");

看下打印结果:

通过Dagger2的方式刚开始可能会觉得突然间一个简单的事情,变得复杂了,其实没有,通过Dagger2很好的处理好了依赖关系,具体说明,比如我们缓存LCache需要添加一个最大缓存个数变化,如果按照之前的方式,我们首先需要对LCache进行修改,比如修改构造函数增加maxCacheSize,然后必须对LCacheManager进行修改,现在通过Dagger2的方式的话,我们只需修改LCacheModule就可以了,LCache实例化和相关参数和LCacheManager之间并没有太大的依赖关系。

6.关于@Module提供多个同类型@Provides

基于上面的缓存处理需求,我们需要实现读写分别使用不同的多任务LExecutor,并且LExecutor的最小线程数为5,我们会在LCacheComponent添加提供writeLExecutor函数,如下:

  1. @Component(modules = {LCacheModule.class,LExecutorModule.class})
  2. @Singleton
  3. public interface LCacheComponent {
  4.  
  5. LCache lCache(); // app缓存
  6.  
  7. LExecutor lExecutor(); // app多任务线程池
  8.  
  9. LExecutor writeLExecutor(); // app 写缓存多任务线程池
  10.  
  11. void inject(LCacheManager lCacheManager);
  12. }

在LExecutorModule中添加提供依赖初始化的provideWriteLExecutor函数。如下:

  1. @Module
  2. public class LExecutorModule {
  3.  
  4. /**
  5. * 提供app 多任务最少维护线程个数
  6. * @return 返回多任务最少维护线程个数
  7. */
  8. @Provides
  9. @Singleton
  10. LExecutor provideLExecutor() {
  11. return new LExecutor(10);
  12. }
  13.  
  14. /**
  15. * 提供app 多任务最少维护线程个数
  16. * @return 返回多任务最少维护线程个数
  17. */
  18. @Provides
  19. @Singleton
  20. LExecutor provideWriteLExecutor() {
  21. return new LExecutor(5);
  22. }
  23. }

然后写完之后Rebuild一下项目,以为万事大吉了,结果报了如下错误,

怎么办呢,难道Dagger2就这么不堪一击吗,当然不是解决这个问题很容易,使用@Named注解解决这个问题,我们只需要在LCacheComponent的writeLExecutor()和

LExecutorModule的provideWriteLExecutor()函数上添加相同的@Named("WriteLExecutor")即可。

对于Module的provide函数也是可以传递参数的,不过需要在当前Module中需要提供相关的参数的函数。例如:LCacheModule可以修改如下:

  1. @Module
  2. public class LCacheModule {
  3.  
  4. /**
  5. * 提供缓存对象
  6. * @return 返回缓存对象
  7. */
  8. @Provides
  9. @Singleton
  10. LCache provideLCache( @Named("LCache")String name , @Named("LCache")int maxCacheSize) {
  11. return new LCache(name,maxCacheSize);
  12. }
  13.  
  14. /**
  15. * 提供缓存对象
  16. * @return 返回缓存对象
  17. */
  18. @Provides
  19. @Singleton
  20. @Named("LCache")
  21. String provideLCacheName() {
  22. return "lcjCache";
  23. }
  24.  
  25. /**
  26. * 提供缓存对象
  27. * @return 返回缓存对象
  28. */
  29. @Provides
  30. @Singleton
  31. @Named("LCache")
  32. int provideLCacheMaxSize() {
  33. return 600;
  34. }
  35.  
  36. }

这里又使用了别名@Name也是因为为了避免bound multiple times错误导致编译失败,在编译的过程中Dagger2会自动去寻找相关参数进行绑定依赖关系,这点还是挺神奇的。

总结:

今天简单的写个例子对Dagger2有个初步的理解与认识,由于项目并没有采用MVP设计模式,准备逐步采用Dagger2+MVP来降低项目中耦合。

Android注解使用之Dagger2实现项目依赖关系解耦的更多相关文章

  1. android编译/反编译常用工具及项目依赖关系

    项目依赖关系 apktool:依赖smali/baksmali,XML部分 AXMLPrinter2 JEB:dx 工具依赖 AOSP , 反编译dex 依赖 apktool dex2jar:依赖 A ...

  2. Android Gradle Plugin指南(三)——依赖关系、android库和多项目配置

    原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Dependencies-Android-Librari ...

  3. Android注解框架实战-ButterKnife

    文章大纲 Android注解框架介绍 ButterKnife实战 项目源码下载   一.框架介绍 为什么要用注解框架?  在Android开发过程中,我们经常性地需要操作组件,操作方法有findVie ...

  4. Bean 注解(Annotation)配置(3)- 依赖注入配置

    Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...

  5. Python之用虚拟环境隔离项目,并重建依赖关系

    下面将以安装django和mysqlclient介绍如何用虚拟环境隔离项目,并重建依赖关系.操作系统:windows 10:python版本:python3.7 1. 安装python虚拟环境 (1) ...

  6. Chromium之工程依赖关系.

    Chromium各版本可能有差异,我的版本是chromium.r197479,2013/08前后下载的source code. Visual Studio Ultimate版本有工具可以自动生成项目依 ...

  7. 【WPF学习笔记】之WPF基础:依赖关系属性和通知

    这些天来,对象似乎已经忙得晕头转向了.每个人都希望它们做这做那.Windows® Presentation Foundation (WPF) 应用程序中的典型对象会接到各种各样不同的请求:有要求绑定到 ...

  8. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  9. Android Studio Jar、so、library项目依赖

    Eclipse跟AS的不同 从Eclipse到AS不要带着在Eclipse中的主观色彩去在AS中使用,从项目的构成到构建是不同的,下面列举在Eclipse和AS中的一些概念的区别: WorkSpace ...

随机推荐

  1. windows下部署免费ssl证书(letsencrypt)

    随着网络的发展,网络安全也越来越重要,对于网站来说,从Http升级到https也是我们要做的首要事情.要实现https,首先我们需要申请一张SSL证书,这篇文章我主要介绍下边这几个方面: 1. SSL ...

  2. [Kafka] - Kafka内核理解:Message

    一个Kafka的Message由一个固定长度的header和一个变长的消息体body组成 header部分由一个字节的magic(文件格式)和四个字节的CRC32(用于判断body消息体是否正常)构成 ...

  3. Mac入门推荐(写给Mac小白)

    本人第一次接触Mac是在2016年10月中旬,那时由于对苹果系统的不熟悉,导致自己一开始的时候用的很不习惯,甚至还想换回Windows系统.总所周知,苹果系统的软件比较少,在此我向大家推荐一些个人觉得 ...

  4. 【PHP系列】PHP推荐标准之PSR-3,日志记录器接口

    上节聊完了PHP官方的相关代码规范,下面给大家带来了PHP系列的PHP推荐标准的另外两个,PSR-3,PSR-4. 首先,我们先来了解下PSR-3是怎么回事. PHP-FIG发布的第三个推荐规范与前两 ...

  5. keepalived配置文件

    1. 查看进程 ps aux | grep keepalived ,其输出为: [root@lvs-m ~]# ps aux| grep keepalived |grep -v greproot 21 ...

  6. angular 1.26 版本 window.history.back() 自动去顶部

    在1.26版本,在url ("www.example.com#xx"),接着按back,会自动调到顶部,这是因为angular的默认设置 只要在config注入$AnchorScr ...

  7. java基础之路(一)

    Java数据类型分为内置类型和扩展类型两大类,其中的内置类型就是基本数据类型,而扩展类型则是Java语言根据基本类型扩展出的其他类型(也叫引用类型)(如:class,String等).本文主要讨论的是 ...

  8. jquery小测

    1.在div元素中,包含了一个<span>元素,通过has选择器获取<div>元素中的<span>元素的语法是? 提示使用has() $("div:has ...

  9. jquery ajax标准写法

    $.ajax({ url:url,                      //地址 type:'post', //请求方式 还可以是get type不可写成Type 不让会导致数据发送不过去,使用 ...

  10. weui.css中flex容器下子项目的水平和垂直居中

    想用weui.css写微信平台的页面,发现没有让flex(weui-flex)容器下,子项目(weui-flex__item)居中的类. 百度了一下,是用justify-content:center; ...