Dagger2使用學(xué)習(xí)筆記

注:本文是小生自學(xué)時(shí)做(fanyi)的筆記,可能含有少兒不宜的誤導(dǎo)性內(nèi)容,學(xué)習(xí)Dagger請(qǐng)移步原博。
原博地址:http://frogermcs.github.io/

Dagger2使用:
需求:主頁(yè)上有一個(gè)TextView,在TextView中顯示人名和年齡
使用Dagger2做依賴注入,需要?jiǎng)?chuàng)建兩樣?xùn)|西:
1.Component接口: 想象成是注射器的針管,單詞的含義貌似跟作用完全沒(méi)關(guān)系啊,待我查查看。Component類使用 @Component(modules = {xxx.class}) 注解。
UserComponent:

@Component(modules = {UserModule.class})
public interface UserComponent {
    void inject(MainActivity mainActivity);
}

2.Module: 想象是注射器的針筒。注意,Module不是提供的依賴(注射液),而是提供依賴的組件(針筒)。Module類使用 @Module 注解,提供依賴的方法用 @Provides 標(biāo)識(shí),方法名以 provide 開(kāi)頭。

@Module
public class UserModule {

    UserModule() {}

    @Provides
    UserModel provideUsers() {
        UserModel user = new UserModel();
        user.setName("lala");
        user.setAge(18);
        return user;
    }
}

以上就是我們?yōu)榱耸褂肈agger需要額外添加的東西,針筒加針管,還缺少:1.注射器的活塞。2.需要注射的液體。3.接受注射的病人。

1.注射器的活塞
也就是用來(lái)真正實(shí)施注入的東西。Dagger會(huì)根據(jù)注解生成一個(gè)實(shí)現(xiàn)了MembersInjector接口的類。MembersInjector直譯過(guò)來(lái)就是成員注射器,我們把它看成是注射器的活塞。此接口只有一個(gè)方法,就是 void injectMembers(T instance).調(diào)用生成類的 injectMembers 方法即相當(dāng)于推動(dòng)活塞的動(dòng)作,真正實(shí)施注射。對(duì)于這個(gè)東西我們不需要添加額外的代碼,它由Dagger在預(yù)編譯時(shí)期自動(dòng)生成。

2.需要注射的液體
這就是我們需要注入的依賴了。它可以是任何可以實(shí)例化的東西。
UserModel:

//UserModel沒(méi)有添加任何額外的東西
public class UserModel {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.接受注射的病人
也就是接收依賴的需求者。我們用 @inject 注解來(lái)標(biāo)識(shí)。
MainActivity:

//需要注入的依賴使用 @Inject 標(biāo)記
public class MainActivity extends AppCompatActivity {
    private TextView textView;

    @Inject
    UserModel user;

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

        textView = (TextView) findViewById(R.id.text_view);

        //進(jìn)行真正的注入操作
        DaggerUserComponent.builder()
                .userModule(new UserModule())
                .build()
                .inject(this);

        textView.setText("Name:" + user.getName() + "::Age:" + user.getAge());
    }
}

效果:


successInject.png

咦,這么看起來(lái)整個(gè)實(shí)現(xiàn)還是比較麻煩的。直接在MainActivity new一個(gè)user出來(lái)不就完了,為什么要用Dagger繞這么大一圈。
先看看什么是依賴。
在MainActivity需要一個(gè)UserModel的實(shí)例,此時(shí),我們稱MainActivity對(duì)UserModel有依賴,如果我們要改UserModel,比如在構(gòu)造函數(shù)里就傳進(jìn)去姓名和年齡,而不通過(guò)set方法。此時(shí),我們就要更改MainActivity中的代碼,這里的MainActivity和UserModel是高耦合的。
而使用Dagger后,即使UserModel有了改變,我們只要改一下Module里的provide方法即可,而不用動(dòng)MainActivity的代碼,此時(shí),MainActivity和UserModel是低耦合的。真真是在平坦的路面上曲折前行啊...

上面是使用Dagger做一個(gè)最基本的依賴注入。下面來(lái)詳解一下各個(gè)部分。

  1. @Inject 注解

構(gòu)造函數(shù)注入

public class LoginActivityPresenter {
    
    private LoginActivity loginActivity;
    private UserDataStore userDataStore;
    private UserManager userManager;
    
    @Inject
    public LoginActivityPresenter(LoginActivity loginActivity,
                                  UserDataStore userDataStore,
                                  UserManager userManager) {
        this.loginActivity = loginActivity;
        this.userDataStore = userDataStore;
        this.userManager = userManager;
    }
}

直接在構(gòu)造函數(shù)上加 @Inject 注解,所有的參數(shù)都從Module里獲取。同時(shí),這個(gè)類也可以被用于注入。如下:

public class LoginActivity extends BaseActivity {

    @Inject
    LoginActivityPresenter presenter;
    
    //...
}

限制是一個(gè)類中只能有一個(gè)構(gòu)造函數(shù)有 @Inject 注解。

成員注入

public class SplashActivity extends AppCompatActivity {
    
    @Inject
    LoginActivityPresenter presenter;
    @Inject
    AnalyticsManager analyticsManager;
    
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        getAppComponent().inject(this);
    }
}

也就是上面我們使用的注入方式,這種方式比較好理解,不過(guò)需要手動(dòng)調(diào)用注入才能完成注入過(guò)程。注入前該成員一直都是null。
限制是被注入成員不能是private的,因?yàn)镈agger生成的代碼是這么注入的:
splashActivity.analyticsManager = analyticsManagerProvider.get();
這個(gè)后面分析生成代碼的時(shí)候會(huì)談到。

方法注入

public class LoginActivityPresenter {
    
    private LoginActivity loginActivity;
    
    @Inject 
    public LoginActivityPresenter(LoginActivity loginActivity) {
        this.loginActivity = loginActivity;
    }

    @Inject
    public void enableWatches(Watches watches) {
        watches.register(this);    //Watches instance required fully constructed LoginActivityPresenter
    }
}

被注解的方法的所有參數(shù)都由Module提供。當(dāng)被注入方需要當(dāng)前類的實(shí)例(this)時(shí)可以用這種方式把自己傳給被注入方。注入方法會(huì)在構(gòu)造函數(shù)調(diào)用完畢后立馬被調(diào)用。

@Module 注解
用來(lái)標(biāo)注那些提供依賴的類,Dagger通過(guò)這個(gè)注解來(lái)找到提供依賴的類。

@Module
public class GithubApiModule {
    
    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient() {
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
        return okHttpClient;
    }

    @Provides
    @Singleton
    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
        RestAdapter.Builder builder = new RestAdapter.Builder();
        builder.setClient(new OkClient(okHttpClient))
               .setEndpoint(application.getString(R.string.endpoint));
        return builder.build();
    }
}

@Provides 注解
標(biāo)注返回依賴的方法。

@Module
public class GithubApiModule {
    
    //...
    
    @Provides   //This annotation means that method below provides dependency
    @Singleton
    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
        RestAdapter.Builder builder = new RestAdapter.Builder();
        builder.setClient(new OkClient(okHttpClient))
               .setEndpoint(application.getString(R.string.endpoint));
        return builder.build();
    }
}

@Component 注解
標(biāo)注Module與Inject之間的橋梁接口。在這個(gè)接口中定義從哪個(gè)module獲取依賴,也用于定義那些Module可以用于注入,以及可以注入對(duì)象到哪里。
這個(gè)例子表示:當(dāng)前Component使用兩個(gè)Modules,可以注入依賴到GithubClientApplication,可以使三個(gè)依賴公開(kāi)可見(jiàn):

@Singleton
@Component(
    modules = {
        AppModule.class,
        GithubApiModule.class
    }
)
public interface AppComponent {

    void inject(GithubClientApplication githubClientApplication);

    Application getApplication();

    AnalyticsManager getAnalyticsManager();

    UserManager getUserManager();
}

Component也可以依賴別的Component,也可以有自己的生命周期(詳見(jiàn)下面的Scope)

@ActivityScope
@Component(      
    modules = SplashActivityModule.class,
    dependencies = AppComponent.class
)
public interface SplashActivityComponent {
    SplashActivity inject(SplashActivity splashActivity);

    SplashActivityPresenter presenter();
}

@Scope 注解

@Scope
public @interface ActivityScope {
}

用于定義自定義作用域注解。有點(diǎn)類似于單例,不同的是,單例的作用域是整個(gè)application,而自定義作用域可以自定義(特么廢話么)。下面還會(huì)詳解Scope,這里按下不表。先說(shuō)好,Scope是拿來(lái)干這個(gè)的:保持對(duì)象的單一實(shí)例。

然后附贈(zèng)一些不咋用,不太重要的東西:

@MapKey 注解
用于定義依賴的集合。
定義:

@MapKey(unwrapValue = true)
@interface TestKey {
    String value();
}

提供依賴:

@Provides(type = Type.MAP)
@TestKey("foo")
String provideFooKey() {
    return "foo value";
}

@Provides(type = Type.MAP)
@TestKey("bar")
String provideBarKey() {
    return "bar value";
}

使用:

@Inject
Map<String, String> map;

map.toString() // => ?{foo=foo value, bar=bar value}”

@Qualifier 注解
給高階程序猿(強(qiáng)迫癥)準(zhǔn)備的,用于為繼承自同一個(gè)接口的依賴打 TAG 來(lái)區(qū)分他們。比如有兩個(gè)Adapter繼承自同一個(gè)Adapter接口,你還想讓代碼看上去整齊有型,就這么干。
命名依賴:

@Provides
@Singleton
@GithubRestAdapter  //Qualifier
RestAdapter provideRestAdapter() {
    return new RestAdapter.Builder()
        .setEndpoint("https://api.github.com")
        .build();
}

@Provides
@Singleton
@FacebookRestAdapter  //Qualifier
RestAdapter provideRestAdapter() {
    return new RestAdapter.Builder()
        .setEndpoint("https://api.facebook.com")
        .build();
}

注入依賴:

@Inject
@GithubRestAdapter
RestAdapter githubRestAdapter;

@Inject
@FacebookRestAdapter
RestAdapter facebookRestAdapter;

@Singleton 注解(Java自帶)
Dagger不單可以注入實(shí)例,還可以注入單例。當(dāng)年老大要咱們做單元測(cè)試,結(jié)果因?yàn)轫?xiàng)目里各種單例用得太亂導(dǎo)致大量代碼處于不可測(cè)的狀態(tài)。一開(kāi)始注意到Dagger也是因?yàn)橛辛薉agger,就不需要寫(xiě)那種很難Mock對(duì)象的單例了。
我們更改一下一開(kāi)始的示例的界面,加一個(gè)TextView,MainActivity如下:

public class MainActivity extends AppCompatActivity {
    private TextView textView1;
    private TextView textView2;

    @Inject
    UserModel user1;

    @Inject
    UserModel user2;

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

        textView1 = (TextView) findViewById(R.id.text_view_1);
        textView2 = (TextView) findViewById(R.id.text_view_2);

        DaggerUserComponent.builder()
                .userModule(new UserModule())
                .build()
                .inject(this);
        user2.setName("Stark");
        user2.setAge(10);
        textView1.setText("Name:" + user1.getName() + "::Age:" + user1.getAge());
        textView2.setText("Name:" + user2.getName() + "::Age:" + user2.getAge());
    }
}

可以看到這里我注入了兩個(gè)UserModel的實(shí)例,在注入后重新設(shè)置user2的name和Age,然后顯示在兩個(gè)TextView上。結(jié)果如圖:


differentName.png

可以看到,上面的user1還是我在Module中創(chuàng)建的UserModel實(shí)例,user2是另一個(gè)獨(dú)立的對(duì)象,所以它們之間互不干擾。
好,下面我們改動(dòng)一丟丟Component和Module的代碼,如下:

@Component(modules = {UserModule.class})
@Singleton
public interface UserComponent {
    void inject(MainActivity mainActivity);
}
@Module
public class UserModule {

    UserModule() {}

    @Provides
    @Singleton
    UserModel provideUsers() {
        UserModel user = new UserModel();
        user.setName("lala");
        user.setAge(18);
        return user;
    }
}

為Component加了 @Singleton 的類注解,為Module創(chuàng)建相應(yīng)實(shí)例的方法加了同樣的 @Singleton 注解,再運(yùn)行一下看看。


singleton.png

恩,就是這么簡(jiǎn)單,我只更改了user2的數(shù)據(jù),結(jié)果user1也更改了,因?yàn)樗鼈冎赶蛲粋€(gè)對(duì)象。就這樣,成功使用Dagger注入了單例。且這個(gè)單例的代碼是可測(cè)的,因?yàn)樗膶?shí)例很容易Mock。

Scope 詳解
Scope,直譯過(guò)來(lái)是范圍、圈子的意思。就像前面提到的,在Dagger中,Scope用來(lái)保證在指定作用域中,拿到的依賴對(duì)象是唯一的(指定范圍內(nèi)是單例)。比如,我有一個(gè)登錄系統(tǒng),用戶登錄后,一直到登出前,拿到的UserModel就應(yīng)該是單例。此時(shí),我可以自定義一個(gè)UserScope的作用域,即實(shí)現(xiàn)一個(gè) @UserScope 注解,用此注解標(biāo)識(shí)的Component在指定的范圍內(nèi)(從用戶登錄到用戶登出)注入的一定是同一個(gè)實(shí)例。有沒(méi)有很爽的感覺(jué)。。。
Dagger是沒(méi)有默認(rèn)自帶的各種Scope的,什么 @ActivityScope 啊,@ApplicationScope 啊統(tǒng)統(tǒng)沒(méi)有,只有Java自帶的一個(gè)@Singleton,也就是上面講到的那個(gè),它與Apllication處于同一作用域,從App開(kāi)啟到結(jié)束只有一個(gè)實(shí)例。那么下面我們來(lái)看看如何自定義一個(gè) Scope 注解。

Scope的實(shí)現(xiàn)在Dagger2里頭就是對(duì)Component做正確的配置工作。有兩種方式實(shí)現(xiàn)自定義Scope,使用 @Subcomponent 注解 或 使用 Components 依賴。這兩種實(shí)現(xiàn)方式最終出來(lái)的結(jié)果是不同的,使用 @Subcomponent 注解的話,子域可以獲取到父域的所有依賴;而如果使用 Components 依賴,則子域只能獲取到父域通過(guò)Component接口暴露出來(lái)的依賴。

先來(lái)看看使用 @Subcomponent 注解的方式實(shí)現(xiàn):

@Singleton
@Component(
        modules = {
                AppModule.class,
                GithubApiModule.class
        }
)
public interface AppComponent {

    UserComponent plus(UserModule userModule);

    SplashActivityComponent plus(SplashActivityModule splashActivityModule);

}

可以看到,在AppComponent接口中有兩個(gè)plus方法,這兩個(gè)方法的意思是,我們可以從APPComponent中創(chuàng)建兩個(gè)子Component: UserComponent 和 SplashActivityComponent. 因?yàn)樗鼈兪茿ppComponent的子Component,所以都可以獲取到AppModule和GithubApiModule產(chǎn)生的實(shí)例。
規(guī)則:返回類型是子Component類型,方法名任性著來(lái),參數(shù)是子Component所需的就行。

可以看到,UserComponent需要另一個(gè)Module,該Module被當(dāng)做plus方法的參數(shù)傳入。這樣,我們我們用新Module產(chǎn)生的額外對(duì)象拓展了AppComponent可注入的依賴表。UserComponent如下:

@UserScope
@Subcomponent(
        modules = {
                UserModule.class
        }
)
public interface UserComponent {
    RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);

    RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}

@UserScope 注解肯定是自定義的咯:

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

在UserComponent中,可以創(chuàng)建另外兩個(gè)子Component: RepositoriesListActivityComponent 和 RepositoryDetailsActivityComponent.
重要的東西在這兒呢。所有從UserComponent拿到的從AppComponent繼承下來(lái)的實(shí)例都是單例(范圍是Application)。而那些UserModule自己產(chǎn)生的實(shí)例都是“本地單例”,其周期就是UserComponent存在的周期。
所以,每次我們調(diào)用UserComponent userComponent = appComponent.plus(new UserComponent(user));時(shí),從userComponent拿到的對(duì)象都是不同的實(shí)例。
對(duì)于這個(gè)我們自定義的UserScope,它的生命周期當(dāng)然也得咱們自己維護(hù),要維護(hù)它的初始化和銷毀。

public class GithubClientApplication extends Application {

    private AppComponent appComponent;
    private UserComponent userComponent;

    //...

    public UserComponent createUserComponent(User user) {
        userComponent = appComponent.plus(new UserModule(user));
        return userComponent;
    }

    public void releaseUserComponent() {
        userComponent = null;
    }

    //...
}

當(dāng)我們從網(wǎng)絡(luò)獲取到User對(duì)象時(shí),調(diào)用createUserComponent方法,UserScope從此時(shí)開(kāi)始。當(dāng)RepositoriesListActivity finish時(shí),調(diào)用releaseUserComponent終結(jié)UserScope。

到此為止,對(duì)于Dagger的使用已經(jīng)學(xué)習(xí)地差不多了。但是只知道怎么用怎么能滿足咱們欲求不滿的內(nèi)心呢?所以下一篇要賞析一下Dagger自動(dòng)生成的代碼,來(lái)看一下整個(gè)注入流程是如何走通的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,697評(píng)論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,098評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,254評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,473評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,680評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,946評(píng)論 1 288
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容