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

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

@Singleton

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

@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中加上同样的注解

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

    SharedPreferences sharedPreferences();

    MyApplication myApplication();

}

@Scope

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

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

使用自定义@Scope注解

@Module
public class A03Module {

    private A03Activity activity;

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

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

}

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

@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)无法实例化它们。这样就需要我们在这些系统框架类的生命周期方法中实例化和注入,就像下面这样:

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. 环境&依赖

    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
    
  2. Demo结构

Demo结构

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

Demo实现(被弃用)

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

AppComponent

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

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

@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修饰的。

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

@ActivityScope
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
    void inject(MainMvpActivity mainMvpActivity);
}
@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;
    }
}
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连接起来。以下是我建议的类结构:

/
| App (extending Application)
| AppComponent
| AppModule
| ContributeAndroidInjectorsModule // 3 
+ ----- feature/
        | FeatureModule // 2
        | FeatureActivityModule // 1
        | FeatureActivity

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

FeatureActivityModule

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

@Module
public abstract class FeatureActivityModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = { FeatureModule.class })
    abstract FeatureActivity contributeFeatureActivityInjector();
}

FeatureModule

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

@Module
abstract class FeatureModule {

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

ContributeActivityModule

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

@Module(includes = {
        FeatureActivityModule.class
        // Other Activity modules will be added here
})
abstract class ContributeActivityModule {

}

AppComponent

@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

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

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使用详解