尝试使用Dagger2

时间:2017-01-03 13:21:56

标签: android dependency-injection dagger-2

我有以下代码:

SomeClass的

public class SomeClass {

    @Inject
    @Named("special")
    OkHttpClient mOkHttpClient;

    public SomeClass(Activity activity) {
        ((MyApplication) activity.getApplication()).getApplicationComponent().inject(this);
    }

}

ApplicationModule

@Module
public class ApplicationModule {

    private final Application mApplication;

    public ApplicationModule(Application application) {
        mApplication = Preconditions.checkNotNull(application);
    }

    @Provides
    @Singleton
    Application providesApplication() {
        return mApplication;
    }

    @Provides
    @Singleton
    SharedPreferences provideCustomSharedPreferences() {
        return mApplication.getSharedPreferences("my_custom_file", Context.MODE_PRIVATE);
    }

}

ApplicationComponent

@Singleton
@Component(modules = {
    ApplicationModule.class,
    NetworkModule.class
})
public interface ApplicationComponent {

    void inject(SomeClass someClass);

}

MainActivity

public class MainActivity extends AppCompatActivity {

    @Inject
    SharedPreferences mSharedPreferences;

    @Inject
    @Named("default")
    OkHttpClient mOkHttpClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((MyApplication) getApplication()).getActivityComponent().inject(this);
    }

}

NetworkModule

@Module
public abstract class NetworkModule {

    private static final int DEFAULT_CACHE_SIZE = 10 * 1024 * 1024; // 10 Mib

    @Provides
    @Singleton
    static Cache provideOkHttpCache(Application application) {
        return new Cache(application.getCacheDir(), DEFAULT_CACHE_SIZE);
    }

    @Provides
    @Singleton
    @Named("default")
    static OkHttpClient provideDefaultOkHttpClient(Cache cache) {
        OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();

        if (BuildConfig.DEBUG) {
            // Add logging interceptor here...
        }

        return okHttpClient.cache(cache).build();
    }

    @Provides
    @Singleton
    @Named("special")
    static OkHttpClient provideSpecialOkHttpClient(@Named("default") OkHttpClient okHttpClient) {
        return okHttpClient.newBuilder()
            // Add .certificatePinner() here...
            .build();
    }

}

ActivityComponent

@Singleton
@Component(modules = {
    ApplicationModule.class,
    NetworkModule.class
})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

所有MyApplication

public class MyApplication extends Application {

    private ApplicationComponent mApplicationComponent;
    private ActivityComponent mActivityComponent;

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

    public ApplicationComponent getApplicationComponent() {
        if (mApplicationComponent == null) {
            mApplicationComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
        }

        return mApplicationComponent;
    }

    public ActivityComponent getActivityComponent() {
        if (mActivityComponent == null) {
            mActivityComponent = DaggerActivityComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
        }

        return mActivityComponent;
    }

}

但有一些我不喜欢的东西,我知道它们可以通过范围,组件依赖或子组件进行改进。我也可以使用自定义限定符而不是@Named,但我已经知道了。

这是我想要做的......

  1. 我希望provideSpecialOkHttpClient的范围与SomeClass生命周期相关联。我知道我需要这个范围......但是这个特殊的OkHttpClient取决于默认的OkHttpClient实例,它本身依赖于okhttp3.Cache实例。最后两个实例是@Singleton,因为它们可以在任何地方使用。只有特殊的OkHttpClient实例与SomeClass相关联,因为它是唯一可以使用它的地方。如何使用范围完成此操作?我的所有尝试都让我犯了错误,因为我在@SomeClassScope中使用自定义范围,例如@SingletonApplicationComponent@Singleton本身就是Activity简而言之,我怎样才能将一些单例依赖项与应用程序生命周期联系起来,同时让其他依赖项与其他生命周期相关联(如我的示例中的SomeClassnew ApplicationModule(this))是否依赖于单身依赖?

  2. 正如您所看到的那样,我两次调用@Singleton,违背了ActivityComponent注释组件的目的并提供了方法。如何使ApplicationComponent依赖于ApplicationModule,以便我只需在ApplicationComponent中实例化Response Code : 200 {"message_id": **ID here** } 一次?子?组件依赖?我无法用任何方法完成这项工作......

  3. 我很难掌握所有Dagger方面,所以如果你能在回答时提供一些代码示例,那将真正帮助我形象化并理解一切如何联系在一起。

1 个答案:

答案 0 :(得分:3)

首先让我们来看看你到目前为止所拥有的东西。 ActivityComponent有点奇怪。 @Singleton代表应用程序范围的单例。由于ActivityComponent会注入具有Activity范围而不是整个app的成员,因此我们可能需要为该Component提供一个新的范围:

@Retention(RetentionPolicy.RUNTIME)
@Scope
public @interface PerActivity {}

现在我们可以更改该组件:

@PerActivity
@Component(dependencies = { AppComponent.class 
})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

请注意,我们现在已将其作为AppComponent的依赖组件。我们将不得不稍微重构AppComponent以发布它与依赖组件的绑定。

理由:我们希望ActivityComponent能够使用OkHttpClient中绑定的NetworkModule。但是,NetworkModule不是ActivityComponent的模块 - 它是父AppComponent的一部分。相关组件不会自动"继承父母的所有绑定。为了ActivityComponent使用OkHttpClient作为"特殊"的依赖关系。 OkHttpClient它需要由父组件公开。您可以通过在接口中使用要公开的类型创建方法,将绑定公开给依赖组件。没有必要公开所有绑定,只是那些将在依赖组件中使用的绑定。

@Singleton
@Component(modules = {
    ApplicationModule.class,
    NetworkModule.class
})
public interface ApplicationComponent {

    //injection sites

    void inject(SomeClass someClass);

    //bindings published to subcomponents

    OkHttpClient okHttpClient(); //default

}

现在为"特殊"提取模块。 OkHttpClient

@Module
public class SpecialNetworkModule {

    @Provides
    @PerActivity
    @Named("special")
    static OkHttpClient provideSpecialOkHttpClient(@Named("default") OkHttpClient okHttpClient) {
        return okHttpClient.newBuilder()
        // Add .certificatePinner() here...
        .build();
    }
}

并使用相同的模块撰写ActivityComponent

@PerActivity
@Component(modules = { SpecialNetworkModule.class }
           dependencies = { AppComponent.class })
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

SomeClass基本上属于您的活动范围,因此您可以通过这样做重构它以注入您的活动:

public class SomeClass {

    private final Activity activity;
    private final OkHttpClient mOkHttpClient;

    @Inject
    public SomeClass(Activity activity, @Named("special") OKHttpClient client) {
        this.activity = activity;
        this.mOkHttpClient = client;
    }
}

现在让SomeClassMainActivity字段(我假设您在那里使用它,因为它依赖于Activity,它是您提供的唯一活动代码):

@Inject SomeClass someClass

@Override
public void onCreate() {

并确保您的ActivityComponent提供活动。为此,您需要一个新模块:

@Module
@PerActivity
public class ActivityModule {

    private final Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    @Provides
    @PerActivity
    Activity activity() {
        return this.activity;
    }

    @Provides
    @PerActivity
    Context context() {
         return this.activity;
    }
}

使用此模块编写ActivityComponent:

modules = { SpecialNetworkModule.class, ActivityModule.class }

现在组件的消耗需要一点点工作。摆脱应用程序中的public ActivityComponent getActivityComponent()。 ActivityComponents应该在Activity里面生成,以便它们能够正确地跟踪范围和生命周期。

因此,使用Activity中的组件应该如下所示:

@Inject SomeClass someClass;

@Override
public void onCreate() {
     AppComponent appComponent = ((((MyApplication) getApplication()).getActivityComponent());
     DaggerActivityComponent.builder()
         .appComponent(appComponent)
         .activityModule(new ActivityModule(this))
         .build()
         .inject(this);
}

最后,明确回答你的两个问题:

  

简而言之,如何将一些单独依赖项与应用程序生命周期联系起来,同时将其他依赖项与其他生命周期(如我的示例中的Activity或SomeClass)相关联,当它们依赖于单例依赖项时?

创建自定义范围(@PerActivity),跟踪该范围(ActivityComponent)的组件,并使用单独的模块(SpecialNetworkModuleActivityModule)来缩小范围范围依赖。在这样做时,您需要在更宽范围和更窄范围的组件之间建立某种形式的关系。这可以很好地解决您的下一个问题:

  

如何使ActivityComponent依赖于ApplicationComponent,以便我只需要在ApplicationComponent中实例化ApplicationModule一次?子?组件依赖?我无法用任何方法完成这项工作......

如上例所示,使用依赖组件(子组件也可以考虑)。在此过程中,请确保更宽范围的组件明确地将其绑定发布到其依赖组件。