在瞭解了簡單注入對象的使用後,我們將問題升級。我們平常開發中爲了節省資源,在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
註解的方法返回類型一樣的問題
在上面的示例中,可能有同學已經嘗試將providesFactoryA
和providesFactoryB
的方法返回修改爲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 {}
這裏如果我們要使獲取到的Product
在MainComponent實例的生命週期
內保持單例,我們就需要給提供實例的方法註解上同樣的@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
的實現類