Dagger2入门学习之MVP项目整合(下)

上一篇Dagger2入门学习之MVP项目整合(上)的最后我们列举了Dagger的关键注释,其中@Singleton和@Scope还没有接触,接下来我们来先看看这两个标签的使用。

@Singleton

类似于SharedPreferences对象,我们可能在整个App的生命周期中只需要一个单例,而不需要每次都通过application.getSharedPreferences(“spfile”, Context.MODE_PRIVATE)获得新的对象,那么怎么办呢,我们只需要在你需要单例的对象提供方法上加一个注解@Singleton ,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Module
public class AppModule {

private MyApplication application;

public AppModule(MyApplication application) {
this.application = application;
}

//全局单例SharedPreferences
@Provides
@Singleton
SharedPreferences provideSharedPreferences() {
return application.getSharedPreferences("spfile", Context.MODE_PRIVATE);
}

@Provides
MyApplication provideApplication() {
return application;
}
}

只需要一个@Singleton,无论我们在多少个Activity容器中通过@Inject获取这个SharedPreferences对象的实例,该对象都是同一个对象,从而实现了全局单例的效果。

你需要什么对象全局单例,就在提供该对象方法的@Provides注解旁加上一个@Singleton,并且在该module关联的Component中加上同样的注解

1
2
3
4
5
6
7
8
9
@Singleton      //不要忘了还要在关联的Component接口中声明,否则会编译报错
@Component(modules = AppModule.class)
public interface AppComponent {

SharedPreferences sharedPreferences();

MyApplication myApplication();

}

@Scope

除了全局单例,我们可能在开发中更多的是需要局部单例,比如,我在ActivityA中需要实例化两个Student对象,在ActivityB中也需要一个Student对象,但我希望ActivityA中的两个Student都叫小明,但ActivityB中的Student对象叫小刚。这个时候我们可以使用@Scope来自定义注解限定注解作用域。

1
2
3
4
@Scope  //声明这是一个自定义@Scope注解
@Retention(RUNTIME)
public @interface ActivityScope {
}

使用自定义@Scope注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Module
public class A03Module {

private A03Activity activity;

public A03Module(A03Activity activity) {
this.activity = activity;
}

@Provides
@ActivityScope//添加注解实现局部单例
Student provideStudent() {
return new Student();
}

}

在Component中同样添加@ActivityScope注解,否则会编译报错

1
2
3
4
5
6
7
@ActivityScope//添加注解实现局部单例
@Component(modules = A03Module.class, dependencies = AppComponent.class)
public interface A03Component {

void inject(A03Activity activity);

}

事实上,Android开发中使用Dagger,开发人员仍然需要面对一些问题。Google工程师们尝试弥补Dagger的问题,于是Dagger2-Android基于Dagger2应用于Android开发,由google开发的的拓展库应运而生。

Dagger2-Android

Demo Github地址:https://github.com/lxqxsyu/TestDagger
_参考文档:https://google.github.io/dagger/android

在Android项目中使用Dagger的主要区别在于Android框架本身具有一些类(例如Activity、Fragment)无法实例化它们。这样就需要我们在这些系统框架类的生命周期方法中实例化和注入,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DO THIS FIRST. Otherwise frombulator might be null!
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}

但是这样做有一些问题:

  1. 复制黏贴,到时后期很难维护和重构。
  2. 打破了依赖注入的核心原则:类不应该知道它是如何注入的。

接下来我们看如何更好的在Android中使用Dagger

Dagger2-Android引入

项目地址:https://github.com/google/dagger

  1. 环境&依赖
1
2
3
4
annotationProcessor 'com.google.dagger:dagger-compiler:2.x' //一定要添加dagger2的annotationProcessor!
compile 'com.google.dagger:dagger-android:2.x'
compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x
  1. Demo结构

Demo结构

这个就是整个demo的结构,图中MVP三层的接口和实现很容易理解,最右边的AppComponent实现的是全局的依赖注入,左边的MainComponent实现的是简单业务逻辑的注入关系,具体我们看代码分析。

Demo实现(被弃用)

说明:这种Dagger实现方式已经在Dagger2.10版本之后被废弃,请往下翻参考最新的

AppComponent

1
2
3
4
5
@Singleton
@Component(modules = {AppModule.class, OkHttpModule.class})
public interface AppComponent {
MainComponent addSub(MainModule mainModule);
}

这是一个全局的Component,这里例举了两个modules(根据实际需要可以添加多个)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Module
public class AppModule {

private Context context;

public AppModule(CustomeApplication application) {
this.context = application;
}

@Singleton
@Provides
public Context ProviderApplicationContext(){
return context;
}

@Singleton
@Provides
@Named("default")
public SharedPreferences providerDefaultSharedPreferences(){
return PreferenceManager.getDefaultSharedPreferences(context);
}

@Singleton
@Provides
@Named("encode")
public SharedPreferences providerEncodeSharedPreferences(){
return context.getSharedPreferences("encode",Context.MODE_PRIVATE);
}
}

后面使用SharePreference和ApplicationContext就可以直接使用了而且是使用@Singleton修饰的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CustomeApplication extends Application{

private static AppComponent appComponent;

@Override
public void onCreate() {
super.onCreate();
}

public static CustomeApplication get(Context context) {
return (CustomeApplication) context.getApplicationContext();
}

private void setupApplicationComponent() {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}

public AppComponent getAppComponent() {
if(appComponent == null){
this.setupApplicationComponent();
}
return appComponent;
}
}

MainComponent

1
2
3
4
5
@ActivityScope
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
void inject(MainMvpActivity mainMvpActivity);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Module
public class MainModule {

private MainMvpView mView;
private MainMvpModel mModel;

public MainModule(MainMvpView view, MainMvpModel model){
mView = view;
mModel = model;
}

@ActivityScope
@Provides
MainMvpView provideMainMvpView() {
return mView;
}

@ActivityScope
@Provides
MainMvpModel provideMainMvpModel(){
return mModel;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainMvpActivity extends AppCompatActivity implements MainMvpView{

@Inject
MainMvpPresenterImpl mPresenter;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CustomeApplication.get(this)
.getAppComponent()
.addSub(new MainModule(this, new MainMvpModelImpl()))
.inject(this);

findViewById(R.id.click_me).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.loadData();
}
});
}

@Override
public void updateUI(String showMsg) {
Toast.makeText(MainMvpActivity.this, showMsg, Toast.LENGTH_LONG).show();
}
}

Dagger新注入方式

上面这种方式破坏了依赖注入的核心准则:一个类不应该知道它是如何被注入的,最新引入的dagger-android模块就是为了解决这个问题——将注入类与注入器分离开来。

在我们的项目中,新模块的配置需要不止一个component和module。每一个Activity都需要自己的subComponent并将其与Application的Component连接起来。以下是我建议的类结构:

1
2
3
4
5
6
7
8
9
/
| App (extending Application)
| AppComponent
| AppModule
| ContributeAndroidInjectorsModule // 3
+ ----- feature/
| FeatureModule // 2
| FeatureActivityModule // 1
| FeatureActivity

每一个feature拥有它自己的component以及module。

FeatureActivityModule

每一个Activity现在需要它自己的subcomponent,为此,Dagger引入了一个非常方便的注解来供我们使用以提醒Dagger生成subcomponent的代码。就像这样:

1
2
3
4
5
6
@Module
public abstract class FeatureActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = { FeatureModule.class })
abstract FeatureActivity contributeFeatureActivityInjector();
}

FeatureModule

我们将Activity自身需要的绑配置在这个module中。此module中的绑定仅仅在此Activity和它的subcomponent中有效,除非该module在其他component中使用。假设我们正在使用MVP的设计模式,以下是你通常用来绑定View的方式:

1
2
3
4
5
6
7
@Module
abstract class FeatureModule {

@ActivityScope
@Binds
abstract FeatureView provideFeatureView(FeatureActivity featureActivity);
}

ContributeActivityModule

到目前为止,我们告诉了Dagger为我们的Activity生成subcomponent,但是我们并没有将subcomponent与Applicaiton的Component连接起来。为了实现这个,我们需要另一个module来持有所有Activity的module。这个module将会被包含在Application的Component中。

1
2
3
4
5
6
7
@Module(includes = {
FeatureActivityModule.class
// Other Activity modules will be added here
})
abstract class ContributeActivityModule {

}

AppComponent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Singleton
@Component(modules = [
AndroidSupportInjectionModule::class, // 1
ContributeActivityModule::class, // 2
AppModule::class
])
public interface AppComponent extends AndroidInjector<App> { // 3

@Component.Builder
abstract class Builder extends AndroidInjector.Builder<App> {

}

}

注意:AppComponent中的一些修改:

  • 为了使Dagger Android能够正常运行,我们在Application的Component中包含了AndroidSupportInjectionModule
  • 包含ContributeActivityModule是为了将Activity的subcomponent与AppComponent连接起来
  • AppComponent必须继承自AndroidInjector 并将其泛型设定为Application类
  • Appcomponent的Builder可以选择性的继承AndroidInjector.Builder并提供Application类。因为这个base类已经实现了公共的component builder,这个builder中包含了一个设置Application实例的方法以及另一个构建component的方法,这可以很轻松的为我们介绍一些代码行数。

自定义的Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class App extends DaggerApplication {

private AppComponent appCompoent;

@Override
public void onCreate() {
appCompoent = DaggerAppComponent.builder()
.application(this)
.build();

super.onCreate();
}

@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return appCompoent;
}
}

最后在Activity中使用

在FeatureActivity中,我们将之前写的注入Activity的代码删除,同时使其继承自DaggerAppCompatActivity而不是AppCompatActivity

1
2
3
4
5
6
7
8
9
10
public class FeatureActivity extends DaggerAppCompatActivity implements FeatureView {
public static final String EXTRA_SOME_ID = "some_id";
@Inject FeaturePresenter presenter;

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

相关链接

Demo Github地址
轻松学,听说你还没有搞懂 Dagger2
告别Dagger2模板代码:Dagger Android使用详解