前言
这篇文章是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会增加学习成本,建议在大中型项目、复杂依赖环境、多人合作的情况下中使用。