架构设计知识梳理(1) - Dagger2

作者: 泽毛 | 来源:发表于2017-02-20 23:59 被阅读176次

    一、概述

    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官方框架的目录结构:

    Paste_Image.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);
        }
    
    }
    

    相关文章

      网友评论

        本文标题:架构设计知识梳理(1) - Dagger2

        本文链接:https://www.haomeiwen.com/subject/zckqpttx.html