Android官方架构分析(三)——todo‑mvp‑dagger

前言

这篇文章是android-architecture源码分析系列文章的第三篇,我们将对todo-mvp-dagger设计模式进行分析。由于该Demo是在todo‑mvp项目的基础上扩展出来的,所以在读这篇文章之前最好有了解过todo‑mvp的代码,并且读过上一篇文章android-architecture源码分析(一)——todo‑mvp

如果你还不太了解dagger2的使用,请务必先自行学习dagger2相关知识。dagger2相对标准MVP架构模式会更加抽象更难以理解,所以这篇文章更适合有一定开发经验的同学。我在学习dagger2的时候也遇到过困难和理解不透的地方,如有错误,敬请谅解。

依赖注入

在架构中引入Dagger2的目的就是为了解耦,Dagger2的作用就是从对依赖对象赋值这一方面进行解耦。当一个类需要依赖其他对象时,一般情况下由程序员来操作依赖对象的赋值,而引入Dagger2以后,就可以将赋值操作交给Dagger2而不再需要亲自操作。

以上将赋值操作交由框架来处理的方式,就是控制反转(Inversion of Control,缩写为IoC)

通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中.

一般的Ioc框架都是通过反射来实现,而Dagger2基于性能考虑,通过android的apt动态生成代码来实现。

在架构中引入Dagger2最大的好处就是复杂的依赖关系维护起来会相对简单。当某个核心类中有非常多的依赖对象,且它的构造函数和生命周期也相对复杂,当我们需要修改它的构造函数时,其他依赖了这个类、用到了这个类的构造方法的地方也需要同步修改,而且当这个类的某些依赖对象还是别人写的,且不太熟悉它们的生命周期的情况下,修改起来会变得无从下手。而引入Dagger2后,只需要维护好各个相对应的Component、module,便可以很方便的构建和修改对象的构造函数,维护它们的生命周期也将相对简单。

代码分析

代码结构

上图可以看到,Dagger2引入以后会多出一些Component、module类,这也就是我们在依赖注入中需要使用到的类。

我们从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;
    }

}

在Application里初始化TasksRepositoryComponent类并提供实例方法。DaggerTasksRepositoryComponent是Dagger2自动生成的代码,在实例化TasksRepositoryComponent的过程中创建了ApplicationModule实例。

我们再来看一下ApplicationModule的代码:

@Module
public final class ApplicationModule {

    private final Context mContext;

    ApplicationModule(Context context) {
        mContext = context;
    }

    @Provides
    Context provideContext() {
        return mContext;
    }
}

ApplicationModule的构造函数需要传入Context对象,也就是我们在ToDoApplication中传入的getApplicationContext()。初始化完成以后就可以由provideContext方法对外提供一个Context实例。

@Module 表示该类为Dagger2的Module组件。

@Provides 表示某个实例对象的提供方法或创建方法。

再来看TasksRepositoryComponent的代码:

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

    TasksRepository getTasksRepository();
}

TasksRepositoryComponent提供了TasksRepository实例的方法。TasksRepository实例就是MVP模式中的Model层实例,也就是说TasksRepositoryComponent提供了这个程序的核心数据接口。

modules = {TasksRepositoryModule.class, ApplicationModule.class},表示作为Component组件,TasksRepositoryComponent接口的实例的构建需要创建TasksRepositoryModule, ApplicationModule这两个实例对象。也就是说,Dagger2会自动生成TasksRepositoryComponent接口的实现类,这个实现类的构造则需要TasksRepositoryModule和 ApplicationModule的实例对象。

@Component 表示该类为Dagger2的Component组件。

@Singleton 表示单例对象,必须确保只创建一个类的实例。

来看看TasksRepositoryModule类:

@Module
abstract class TasksRepositoryModule {
    @Singleton
    @Binds
    @Local
    abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource);

    @Singleton
    @Binds
    @Remote
    abstract TasksDataSource provideTasksRemoteDataSource(TasksRemoteDataSource dataSource);
}

TasksRepositoryModule作为抽象类,提供了两个返回TasksDataSource的抽象方法,分别传入了TasksLocalDataSource和TasksRemoteDataSource的实例对象,也就是说这两个抽象方法分别提供本地数据和远程数据两种数据来源。

@Binds@Local@Remote
表示自定义注解,Dagger2在注入过程中是通过返回类型来确定使用哪个创建方法来获得实例对象,而当有多个方法都返回同一类型时(上面两个方法都返回了TasksDataSource对象),则通过自定义注解来分辨。那么@Local和@Remote就是让Dagger2分辨到底使用Local数据还是Remote数据。

再来看看TasksRepository类:

@Singleton
public class TasksRepository implements TasksDataSource {

   private final TasksDataSource mTasksRemoteDataSource;

   private final TasksDataSource mTasksLocalDataSource;

   ...
  
   @Inject
   TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
           @Local TasksDataSource tasksLocalDataSource) {
       mTasksRemoteDataSource = tasksRemoteDataSource;
       mTasksLocalDataSource = tasksLocalDataSource;
   }
   
   ...
   
}

TasksRepository的构造函数需要传入两个TasksDataSource的实例对象,分别对应@Remote和@Local,而@Remote和@Local则对应到TasksRepositoryModule类提供的两种提供方法,提供TasksLocalDataSource和TasksRemoteDataSource两种实力对象。

@Inject 构造方法声明该注解时表示当Component在所拥有的Module类中找不到依赖需求方需要类型的提供方法时,Dagger2就会检查该需要类型的有没有用@Inject声明的构造方法,有则用该构造方法创建一个。

也就是说,TasksRepositoryComponent对应的两个Module类(TasksRepositoryModule, ApplicationModule)都找不到返回TasksRepository的提供方法,就会在声明了@Inject的构造方法中查找。而TasksRepositoryComponent中的TasksRepository就是使用了这一机制调用了构造方法创建实例。

@Local对应的TasksLocalDataSource类:

@Singleton
public class TasksLocalDataSource implements TasksDataSource {

   private TasksDbHelper mDbHelper;

   @Inject
   public TasksLocalDataSource(@NonNull Context context) {
       checkNotNull(context);
       mDbHelper = new TasksDbHelper(context);
   }
   
   ...
   
}

TasksLocalDataSource的构造函数也是声明了@Inject,由Dagger2调用创建;而传入的Context参数则是由ApplicationModule中的provideContext()方法提供,由Dagger2实现注入。

@Remote对应的TasksRemoteDataSource类比较简单,这里略过。

到此我们大体分析了MVP结构的Model层核心数据来源的实例对象的创建过程,下面我们分析AddEditTask模块和AddEditTaskPresenter对象的注入过程。

先了解AddEditTaskComponent类:

@FragmentScoped
@Component(dependencies = TasksRepositoryComponent.class,
        modules = AddEditTaskPresenterModule.class)
public interface AddEditTaskComponent {

    void inject(AddEditTaskActivity addEditTaskActivity);
}

AddEditTaskComponent中的inject(AddEditTaskActivity addEditTaskActivity)方法就是表示将Module组件中的提供方法的实例对象注入到AddEditTaskActivity的声明@Inject的对象中。这里比较抽象,继续往下看就会明白。

dependencies = TasksRepositoryComponent.class表示组件依赖,类似与面向对象中的继承,TasksRepositoryComponent类似于基类,只有继承了该类才能够调用该类的方法来回去相应的对象。

@FragmentScoped 表示自定义作用域,篇幅原因这里不做解释,请自行google自定义作用域。

再来看AddEditTaskPresenterModule类:

@Module
public class AddEditTaskPresenterModule {

    private final AddEditTaskContract.View mView;

    private String mTaskId;

    public AddEditTaskPresenterModule(AddEditTaskContract.View view, @Nullable String taskId) {
        mView = view;
        mTaskId = taskId;
    }

    @Provides
    AddEditTaskContract.View provideAddEditTaskContractView() {
        return mView;
    }

    @Provides
    @Nullable
    String provideTaskId() {
        return mTaskId;
    }
}

这里提供了一个构造方法和两个提供方法,两个提供方法分别提供了View层实例对象和TaskId。

再看一下AddEditTaskPresenter类:

final class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
        TasksDataSource.GetTaskCallback {

    @NonNull
    private final TasksDataSource mTasksRepository;

    @NonNull
    private final AddEditTaskContract.View mAddTaskView;

    @Nullable
    private String mTaskId;

    /**
     * Dagger strictly enforces that arguments not marked with {@code @Nullable} are not injected
     * with {@code @Nullable} values.
     */
    @Inject
    AddEditTaskPresenter(@Nullable String taskId, TasksRepository tasksRepository,
            AddEditTaskContract.View addTaskView) {
        mTaskId = taskId;
        mTasksRepository = tasksRepository;
        mAddTaskView = addTaskView;
    }
    
    ...
    
}

AddEditTaskPresenter的构造函数也声明了@Inject,传入的参数分别是taskId、TasksRepository对象、View层实例对象。

依赖注入的准备工作做好了,下面来看将AddEditTaskPresenter对象注入到AddEditTaskActivity类中的流程:

public class AddEditTaskActivity extends AppCompatActivity {

    public static final int REQUEST_ADD_TASK = 1;

    @Inject AddEditTaskPresenter mAddEditTasksPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.addtask_act);

        ...

        // Create the presenter
        DaggerAddEditTaskComponent.builder()
                .addEditTaskPresenterModule(
                        new AddEditTaskPresenterModule(addEditTaskFragment, taskId))
                .tasksRepositoryComponent(
                        ((ToDoApplication) getApplication()).getTasksRepositoryComponent()).build()
                .inject(this);
    }
    
    ...
    
}

DaggerAddEditTaskComponent为Dagger2自动生成的类,使用了建造者模式的链式结构。new AddEditTaskPresenterModule(addEditTaskFragment, taskId)为实例化AddEditTaskPresenterModule对象,传入的两个参数将由AddEditTaskPresenterModule中的提供方法对外提供。((ToDoApplication) getApplication()).getTasksRepositoryComponent())则提供了TasksRepositoryComponent的实例对象。

至此,AddEditTaskPresenter的构造函数的参数已准备完毕,taskId和addTaskView由AddEditTaskPresenterModule实例提供,而tasksRepository由TasksRepositoryComponent实例提供。mAddEditTasksPresenter变量声明了@Inject,而inject(this);中的this表示AddEditTaskActivity实例,执行结果就是将创建一个AddEditTaskPresenter实例并注入到AddEditTaskActivity的mAddEditTasksPresenter对象中

总结

todo‑mvp‑dagger架构在MVP架构基础上,引入Dagger2依赖注入框架,进一步实现Model、Presenter、View各层的解耦,并在复杂的依赖环境中简化了维护工作,更方便单元模块测试。但是项目引入Dagger2会增加学习成本,建议在大中型项目、复杂依赖环境、多人合作的情况下中使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容