Dagger2使用簡析——@Scope、@Qualifier、@binds、dependencies、Lazy

在瞭解了簡單注入對象的使用後,我們將問題升級。我們平常開發中爲了節省資源,在APP的生命週期內很多對象都是作爲單例存在的,因此現在我們嘗試解決三個問題

  • 將一個對象注入到Application中,並且保證它在整個APP的生命週期內是單例的
  • 這個對象應該符合依賴倒置原則,我們使用其抽象類來作爲引用
  • 之後爲了方便在MainActivity中使用它,我們還應該能夠將它注入到MainActivity中

1. 如何解決一個對象在某個生命週期內是單例的

現在我們向App中注入一個FactoryA的實例對象,並使得其在App的生命週期內是單例的,代碼如下:

public class App extends Application {

    @Inject
    FactoryA mFactory;

//    @Inject
//    FactoryA mFactory2;

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

        mAppComponent = DaggerAppComponent
                .builder()
                .build()
                .inject(this);

        mFactory.showMe();
//        mFactory2.showMe();
    }
}
@Module
public abstract class AppModule {
    @Singleton
    @Provides
    public static FactoryA providesFactoryA() {
        return new FactoryA();
    }
}
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(App app);
}

如果你對簡單注入已經熟練掌握的話,你應該發現這裏使得對象成爲單例只需要同時在@Provides註解的方法component接口上添加@Singleton這個註解即可
我們看一下@Singleton這個註解:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {} 

事實上千萬不要誤將@Singleton單例掛鉤,真正起作用的是@Scope這個註解,我們可以用任意被@Scope所註解的註解來替代@Singleton,它告訴Dagger2在自動生成的代碼中,使用DoubleCheck緩存獲取到的對象實例來保證在Component組件所依附的生命週期內對象的單例性,

2. 使用抽象類作爲引用時如何注入

在大部分情況下,FactoryA應該作爲Factory抽象類的具體實現存在,同時Factory還可能有其它實現類,即有如下代碼:

public abstract class Factory {}
public class FactoryA extends Factory {}
public class FactoryB extends Factory {}

此時我們需要對module作出如下修改

@Module
public abstract class AppModule {
    @Singleton
    @Binds
    public abstract Factory bindsFactory(FactoryB factoryB);

    @Singleton
    @Provides
    public static FactoryA providesFactoryA() {
        return new FactoryA();
    }

    @Singleton
    @Provides
    public static FactoryB providesFactoryB() {
        return new FactoryB();
    }
}

從這裏可以看出

  • @Binds註解用於module中的抽象方法,這個方法的參數應該是具體實現類,返回值應該是抽象類,它告訴Dagger2在自動生成的代碼中注入抽象類引用對象時,應該使用哪一個具體實現類作爲實例被獲取
  • 在使用抽象類作爲module時,獲取實例對象的方法只能使用static靜態方法

2.1 解決@Provides註解的方法返回類型一樣的問題

在上面的示例中,可能有同學已經嘗試將providesFactoryAprovidesFactoryB的方法返回修改爲Factory,然而單純這樣修改並不能通過編譯,因爲Dagger2僅通過返回類型無法判斷調用哪一個方法獲取所需的實例,此時需要@Named登場了:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

@Name可以註解在參數、成員變量、方法上,事實上真正起作用的是@Qualifier,我們將AppModule改寫成如下代碼:

@Module
public abstract class AppModule {
    @Singleton
    @Binds
    public abstract Factory bindsFactory(@Named("factoryA") Factory factory);

    @Singleton
    @Provides
    @Named("factoryA")
    public static Factory providesFactoryA(@Named("machine1") Machine machine) {
        return new FactoryA(machine);
    }

    @Singleton
    @Provides
    @Named("factoryB")
    public static Factory providesFactoryB(@Named("machine2") Machine machine) {
        return new FactoryB(machine);
    }
}

不同value的@Named註解標記後,Dagger2就可以正確的區分出對應的實例,除此之外我們可以通過自定義不同的被@Qualifier註解的註解來區分,具體實現請自行實踐

3. 將App中的單例對象同樣注入到MainActivity中

要解決這個問題,我們可以使用@Component的第二個屬性dependencies來依賴AppComponent

@ActivityScope
@Component(modules = {MainModule.class}, dependencies = {AppComponent.class})
public interface MainComponent {
    void inject(MainActivity activity);
}

這裏依賴之後會產生一個小問題,就是存在依賴關係的組件的作用域標記不能相同,因爲在AppComponent中我們標記了@Singleton,因此在這裏我們自定義了新的@Scope,即@ActivityScope

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

這裏如果我們要使獲取到的ProductMainComponent實例的生命週期內保持單例,我們就需要給提供實例的方法註解上同樣的@ActivityScope

@Module
public class MainModule {
    @ActivityScope
    @Provides
    public static Product provideProduct(Worker worker) {
        return new Product(worker);
    }
}

在完成上述改變之後,我們需要在構建MainComponent實例時傳入AppComponent的實例

public class MainActivity extends AppCompatActivity {
    @Inject
    Factory mFactory;

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

        MainComponent mainComponent = DaggerMainComponent
                .builder()
                //這裏傳入appComponent實例,我們可以通過application獲取到
                .appComponent(((App) getApplication()).getAppComponent())
                .build()
                .inject(this);
    }
}

最後一步,我們需要AppComponent接口添加一個方法,用來提供其所持有的單例對象

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(App app);

    Factory factory();
}

4. 懶加載機制——Lazy

因爲在實際場景中,有的對象並不是無時不刻的被使用到,我們需要它在真正被使用時才被實例化,那麼你可以使用Lazy,這裏以Product爲例,代碼修改如下即可:

@Inject
Lazy<Product> mProduct;

Product product = mProduct.get();

5. 小結

成功解決問題之後(建議在每種情況下都看一下自動生成的代碼,代碼本身並不複雜,有利於理解Dagger2在背後是如何關聯這些內容的),我們應該對三個構成部分有一定認識:

  • Module類:提供注入對象的實例,如果可以使用@Inject註解構造函數來提供實例甚至可以不需要這一部分
  • Component接口:解決直接依賴關係的中間橋樑,Dagger2會生成其實現類從而將獲取到的實例賦值到目標容器中,可以認爲它具有與依賴的目標容器相同的生命週期
  • 目標容器:持有注入對象的引用,依賴於Component的實現類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章