引言
在正式開始見解dagger2之前,我們先看一個例子。
第一個版本:
public class Engine { //車的引擎
public Engine(){}
public void run(){
System.out.println(" Engine ------------> run : "+hashCode());
}
}
public class Car{ //每臺車都需要一個引擎
Engine engine;
public Car(){
engine = new Engine();
}
public void run(){
engine.run();
}
}
我們用下圖表示兩者關係
我們要製造一臺汽車,每臺汽車裏麪包含了一個發動機。但我們發現汽車和引擎的結合太緊密了,比如想汽車從汽油機改爲柴油機,那就重新造一臺汽車,太費事了。於是,我們把汽車類稍爲改一下。
第二個版本:
public class Car {
Engine mEngine;
public Car(Engine engine){
mEngine = engine;
}
public void run(){
mEngine.run();
}
}
現在不一樣,我們的發動機不是和汽車一起製造的,而變成組裝了。是在外面造好之後,再安裝進來,他們邏輯關係圖就成了以下這樣:
這就是最簡單的依賴注入(DI)了,Engine是Car的依賴類,現在, 我們改變依賴的實現方式時,我們不需要修改Car源代碼。所有它的依賴都是從外面提供的,所以我們只需修改依賴對象。
使用依賴注入的優勢是什麼呢?
1. 構造/使用 的分離
當我們構造類的實例,通常這些對象會在其它的地方被使用到,使用依賴注入可以讓我們的代碼更加模塊化,並且所有的依賴都可以被很簡單地替換掉(只要他們實現了相同的接口),並且不會與我們應用的邏輯產生衝突。
2. 單元測試(Unit testing)
使用依賴注入可以讓一個類是完全被隔離,不需要了解它的相關依賴,方便我們編寫的單元測試的例子。
3. 獨立/並行開發
模塊化的代碼,它可以非常方便在程序員間進行代碼的分離,獨立或並行的開發。
當然,除了這些優點之外還有一些缺點。其中一個缺點是會產生很大的模版代碼。使用依賴注入框架時,也會增加學習成本。
下面我們開始真正介紹dagger2這個在java和android上的依賴注入框架。
Dagger2
Dagger2是Dagger1的分支,dagger1由Square公司開發,dagger2由谷歌公司接手開發,目前的版本是2.7。Dagger2解決問題的基本思想是:利用編譯器apt工具自動生成部分代碼和自己手寫代碼混合達到看似所有的產生和提供依賴的代碼都是手寫的樣子。值得一提的是,dagger2沒有用到java反射技術,所以性能相比dagger1有大大的提高。
依賴注入(Dependency Injection簡稱ID): 就是目標類(目標類需要進行依賴初始化的類)中所依賴的其他的類的初始化過程,不是通過手動編碼的方式創建,而是通過技術手段可以把其他的類的已經初始化好的實例自動注入到目標類中。
dagger2註解:
要學習Dagger2,就必須要知道下面這些註解和這其中的每一個概念:
@Inject: 通常在需要依賴的地方使用這個註解。換句話說,你用它告訴Dagger這個類或者字段需要依賴注入。這樣,Dagger就會構造一個這個類的實例並滿足他們的依賴。
@Module: Modules類裏面的方法用來專門提供依賴,所以我們定義一個類,用@Module註解,這樣Dagger在構造類的實例的時候,就知道從哪裏去找到需要的依賴。
@Provide: 在modules中可以定義很多方法,只有使用了這個註解,Dagger才知道這個方法是提供依賴類的。
@Component: Components可以說是一個注入器,也可以說是@Inject和@Module的橋樑,它的主要作用就是連接這兩個部分。 如果沒有以@Components註解的接口,dagger是無法找到相應的@module類的,也就無法實現@Inject對象注入。
@Scope: Scopes可是非常的有用,Dagger2可以通過自定義註解限定註解作用域,即可以限定依賴類實例的生命週期,比如有的跟activity生命週期一樣,有的是app生命週期等等。
@Qualifier: 當兩個依賴類型一樣,無法鑑別他們的時候,我們就可以使用這個註解標示。相當如給依賴打上不同的“tag”,例如:在Android中,我們會需要不同類型的context,所以我們就可以定義 @qualifier註解“@ForApplication”和“@ForActivity”,這樣當注入一個context的時候,我們就可以告訴 Dagger我們想要哪種類型的context。
dagger2使用
gradle添加依賴:
項目gradle裏添加apt工具依賴:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
app的gradle裏添加dagger2依賴:
apply plugin: 'com.neenbedankt.android-apt'//添加插件
//添加依賴
compile 'com.google.dagger:dagger:2.7'
apt 'com.google.dagger:dagger-compiler:2.7'
使用:
@Inject註解,在Dagger 2中有3種不同的方式來提供依賴
1. 構造器注入:@Inject使用在類的構造器上作註解;
2. 屬性注入: 給指定的屬性作註解;
3. 方法注入:是在類的public方法中作註解。
自定義依賴類:
public class Engine {
//注意這裏放在構造器上,如果這個構造器有參數,則它的參數需要被注入,
//同時意味着這個類可以被注入到別的類裏
@Inject
public Engine(){
}
public void run(){
System.out.println(" Engine ------------> run : "+hashCode());
}
}
自定義需要注入依賴的類:
public class Car {
@Inject //放在屬性成員上,代表這個成員需要被依賴注入
public Engine mEngine;
public Car(){
}
public void run(){
mEngine.run();
}
}
有了上面這兩步後,dagger2就可以開始工作了麼?,當然不行,dagger2無法聰明到自己去找到Car中正確的Engine類,我們還需要有一個類來告訴dagger2到什麼地方去找到需要的依賴。那就是@Component註解。
定義依賴注入的橋樑Component類:
那我們看看這橋樑是怎麼工作的:
Component需要引用到目標類的實例,Component會查找目標類中用Inject註解標註的屬性,查找到相應的屬性後會接着查找該屬性對應的用Inject標註的構造函數(這時候就發生聯繫了),剩下的工作就是初始化該屬性的實例並把實例進行賦值。因此我們也可以給Component叫另外一個名字注入器(Injector)
代碼如下:
/**
* 注意這裏Component()參數裏沒有用到@Module類,這樣也是可行的,
* 因爲上面Engine類裏面在構造器上定義@inject註解,相當於已
* 經告訴了dagger2從哪裏去找依賴,否則必須制定@Module類。
*/
@Component()
public interface CarComponent {//這個類名不固定,但這樣更易讀
// 注入具體的類中(方法名可隨意取, 重要的是參數)
void inject(Car car);
}
有了Component橋樑後,就是使用這個component,進行依賴注入了,修改上面Car類,如下:
public class Car {
@Inject
public Engine mEngine;
public Car(){
//特別注意DaggerCarComponent是Dagger2自動生成的
CarComponent carComponent = DaggerCarComponent.builder().build();
//進行注入
carComponent.inject(this);
}
public void run(){
mEngine.run();
}
}
通過以上步驟,一個簡單的dagger2依賴注入就完成了。我們可以隨便new一個Car出來使用,而不用擔心Engine爲空了。但上面只是一個特例,Engine類是我們自己寫的,我們能在其構造函數上標註@Inject註解,如果我們的依賴是第三方庫中的類呢,比如OkHttpClient,這裏我們就要用到@Module註解了,下面看一個完整例子:
先建立一個HttpHelper類,用於封裝OkHttp操作:
public class HttpHelper {
@Inject //需要依賴注入
OkHttpClient client;
private HttpHelper(){
//這裏進行注入
DaggerHttpHelperComponent.builder().build().inject(this);
}
public static HttpHelper getInstance(){
return Holder.httpHelper;
}
private static class Holder{
public static HttpHelper httpHelper = new HttpHelper();
}
//get請求
public void get(String url, final Listener listener){
final Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
listener.onFailed();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String s = response.body().string();
listener.onSuccess(s);
}
});
}
//請求回調
public interface Listener{
void onSuccess(String response);
void onFailed();
}
}
再添加一個module類提供上面需要被注入的依賴
@Module
public class HttpHelperModule {
@Provides //必須用這個標註
@Singleton //注意這裏代表是單例
OkHttpClient provideOkHttpClient(){
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS);
return builder.build();
}
}
添加一個component類,將上面的module和inject聯繫起來:
@Component(modules = HttpHelperModule.class)
@Singleton //module裏面添加了Singleton,則component裏面必須也要添加
public interface HttpHelperComponent {
void inject(HttpHelper helper);
}
有了module和component後,dagger2就會自動幫我們生成DaggerHttpHelperComponent類,所以就可以調用 :
DaggerHttpHelperComponent.builder().build().inject(this);
完成依賴注入。其原理就是使用apt在編譯時,在build目錄下產生相應的java源代碼,如上面的DaggerHttpHelperComponent.java代碼,大家可以自行分析下。
總結
Inject,Component,Module,Provides是dagger2中的最基礎最核心的知識點。奠定了dagger2的整個依賴注入框架:
- Inject主要是用來標註目標類的依賴和依賴的構造函數;
- Component它是一個橋樑,一端是目標類,另一端是目標類所依賴類的實例,它也是注入器(Injector)負責把目標類所依賴類的實例注入到目標類中,同時它也管理Module。
- Module和Provides是爲解決第三方類庫而生的,Module是一個簡單工廠模式,Module可以包含創建類實例的方法,這些方法用Provides來標註
dagger2中Scope(作用域),Qualifier(限定符),Singleton(單例),SubComponent等都是對dagger2中整個注入依賴注入框架進行的補充,我們後面會繼續講解,敬請期待……
github代碼:https://github.com/nickyangjun/Dagger2Test
更多精彩Android技術可以關注我們的微信公衆號,掃一掃下方的二維碼或搜索關注公共號:
Android老鳥