[Android]使用Dagger 2依賴注入 - API(翻譯)



以下內容為原創,歡迎轉載,轉載請注明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html

使用Dagger 2依賴注入 - API

原文:http://frogermcs.github.io/dependency-injection-with-dagger-2-the-api/

這章是展示使用Dagger 2在Android端實現依賴注入的系列中的一部分。今天我會探索Dagger 2的基礎並且學習這個依賴注入框架的所有的API。

Dagger 2

在我上一章中我提到了DI框架給我們帶來了什么 - 它讓只寫一小部分代碼就可以使一切聯系在一起成為可能。Dagger 2就是DI框架的一個例子,它可以為我們生成很多模版代碼。但是為什么它比其它的要更好呢?目前為止它是唯一一個可以生成完整模仿用戶手寫的可追蹤的代碼的DI框架。這意味着讓依賴圖表構建過程不再充滿魔幻。Dagger 2相比其它是不具有動態性的(使用時完全不使用反射)但是生成的代碼的簡潔性和性能都是與手寫的代碼同水准的。

Dagger 2 基礎

下面是Dagger 2的API:

public @interface Component {
    Class<?>[] modules() default {};
    Class<?>[] dependencies() default {};
}

public @interface Subcomponent {
    Class<?>[] modules() default {};
}

public @interface Module {
    Class<?>[] includes() default {};
}

public @interface Provides {
}

public @interface MapKey {
    boolean unwrapValue() default true;
}

public interface Lazy<T> {
    T get();
}

還有在Dagger 2中用到的定義在 JSR-330 (Java中依賴注入的標准)中的其它元素:

public @interface Inject {
}

public @interface Scope {
}

public @interface Qualifier {
}

讓我們來學習它們。

@Inject 注解

DI中第一個並且是最重要的就是@Inject注解。JSR-330標准中的一部分,標記那些應該被依賴注入框架提供的依賴。在Dagger 2中有3種不同的方式來提供依賴:

構造器注入:

@Inject使用在類的構造器上:

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;
    }
}

所有的參數都是通過依賴圖表中獲取。@Inject注解被使用在構造器中也會標記這個類為依賴圖表的一部分。這意味着它也可以在我們需要的時候被注入:

public class LoginActivity extends BaseActivity {

    @Inject
    LoginActivityPresenter presenter;
    
    //...
}

這個例子中的局限性是我們不能給這個類中的多個構造器作@Inject注解。

屬性注入

另一種選擇是給指定的屬性作@Inject注解:

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

但是在這個例子中,注入過程必須要在我們類的某處“手動”調用:

public class SplashActivity extends AppCompatActivity {
    
    //...
    
    @Override 
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        getAppComponent().inject(this);    //Requested depenencies are injected in this moment
    }
}

在這個調用之前,我們的依賴是null值。

屬性注入的局限性是我們不能使用private。為什么?簡單說,生成的代碼會直接地調用它們來設置屬性,就像這里:

//This class is generated automatically by Dagger 2
public final class SplashActivity_MembersInjector implements MembersInjector<SplashActivity> {

    //...

    @Override
    public void injectMembers(SplashActivity splashActivity) {
        if (splashActivity == null) {
            throw new NullPointerException("Cannot inject members into a null reference");
        }
        supertypeInjector.injectMembers(splashActivity);
        splashActivity.presenter = presenterProvider.get();
        splashActivity.analyticsManager = analyticsManagerProvider.get();
    }
}
方法注入

最后一種方法使用@Inject注解提供依賴的方式是在這個類的public方法中作注解:

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
    }
}

方法的所有參數都是通過依賴圖表提供的。但是為什么我們需要方法注入呢?在某些情況下會用到,如當我們希望傳入類的當前實例(this引用)到被注入的依賴中。方法注入會在構造器調用后馬上被調用,所以這表示我們可以傳入完全被構造的this

@Module 注解

@Module是Dagger 2 API的一部分。這個注解用於標記提供依賴的類 - 多虧它Dagger才會知道某些地方需要的對象被構建。

@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();
    }
}
view rawGithubApiModule.java hosted with ❤ by GitHub

@Provides 注解

這個注解用在@Module類中。@Provides會標記Module中那些返回依賴的方法。

@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 注解

這個注解用在把一切聯系在一起的接口上面。在這里我們可以定義從哪些module(或者哪些Components)中獲取依賴。這里也可以用來定義哪些圖表依賴應該公開可見(可以被注入)和哪里我們的component可以注入對象。@Component通常是@Module@Inject之間的橋梁。

這里有個使用了兩個module的@Component例子代碼,可以注入依賴到GithubClientApplication,並且標記了三個依賴公開可見:

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

    void inject(GithubClientApplication githubClientApplication);

    Application getApplication();

    AnalyticsManager getAnalyticsManager();

    UserManager getUserManager();
}

@Component也可以依賴其它的component,並且定義了生命周期(我會在以后的文章中講解Scope):

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

    SplashActivityPresenter presenter();
}

@Scope 注解

@Scope
public @interface ActivityScope {
}

JSR-330標准的又一部分。在Dagger 2中,@Scope被用於標記自定義的scope注解。簡單說它們可以類似單例地標記依賴。被作注解的依賴會變成單例,但是這會與component的生命周期(不是整個應用)關聯。但是正如我剛才所說 - 我會在下一篇文章中深入scope。現在值得一提的是所有自定義scope做的是同樣的事情(從代碼角度)- 它們為對象保持單例。但是他們也會被在圖表認證處理中使用,這對盡可能地捕捉圖表結構問題是有幫助的。


接下來是一些不太重要,不會經常用到的東西:

MapKey

這個注解用在定義一些依賴集合(目前為止,Maps和Sets)。讓例子代碼自己來解釋吧:

定義:

@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}”

@MapKey注解目前只提供兩種類型 - String和Enum。

@Qualifier

@Qualifier注解幫助我們去為相同接口的依賴創建“tags”。想象下你需要提供兩個RestAdapter對象 - 一個用於Github API,另一個用於Fackbook API。Qualifier會幫助你區分對應的一個:

命名依賴:

@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;

以上就是全部了。我們剛剛已經了解了所有Dagger 2 API中重要的元素了。

App example

現在是時候讓我們在實踐中檢驗我們的知識了。我們會基於Dagger 2來實現一個簡單的Github客戶端app。

想法

我們的Github客戶端有三個Activity和非常簡單的使用case。它的全部流程:

  1. 輸入Github用戶名
  2. 如果用戶存在則顯示它所有公開的庫。
  3. 當用戶按下list的item后顯示庫的詳情。

這里就是我們的app看起來的樣子:

在內部,從DI角度我們的app結構看起來如下:

簡單說 - 每一個Activity都有它自己的依賴圖表。每個圖表(_Component類)有兩個對象 - _Prresenter_Activity。每個component也會從全局的component(AppComponent,包含了Application, UserManager, RepositoriesManager等)中獲取依賴。

講講AppComponent - 看上面最近的圖表。它包含了兩個modules:AppModuleGithubApiModule

GithubApiModule提供了一些依賴,比如OkHttpClientRestAdapter,他們只會在這個module的其它依賴使用。在Dagger 2中我們可以控制哪些對象對外部的component可見。在我們例子中我們不希望暴露這些被使用了的對象。所以我們只是暴露了UserManagerRepositoriesManager,因為這有這些對象才會被我們的Activity用到。所有都通過public、返回非void類型並無參數的方法被定義。

文檔中的例子:

提供依賴的方法

SomeType getSomeType();
Set<SomeType> getSomeTypes();
@PortNumber int getPortNumber();

此外,我們必須要定義哪里我們希望要去注入依賴(通過成員注入)。在我們例子中AppComponent沒有任何地方可以去注入,因為它只是作為我們Components的依賴。所以它們每一個都被定義了 一個inject(_Activity activity)方法。這里我們也有一些簡單的規則 - 注入通過一個單個參數的方法被定義(定義一個實例,它代表我們需要往這個實例中注入依賴),它可以有任意的名字,但是必須要返回類型是void或者返回被傳入的參數類型。

文檔中的例子:

成員注入的方法

SomeType getSomeType();
Provider<SomeType> getSomeTypeProvider();
Lazy<SomeType> getLazySomeType();

實現

我不想深入太多的代碼。clone GithubClient 代碼並導入到最新的Android Studio。這里給出一些開始的提示:

Dagger 2 安裝

只需要獲得/app/build.gradle文件。我們需要增加Dagger 2依賴和使用 android-apt 插件來對生成的代碼和Android Studio IDE之間進行綁定。

AppComponent

GithubClientApplication類開始探索。在這里AppComponent會被創建和存儲。這意味着所有單例的對象的生命周期都會與Applicaiton一致(所以每時每刻都是如此)。

AppComponent的實現由Dagger 2(對象可以通過builder模式被創建)生成的代碼提供。在這里我們也可以放入所有的Component的依賴(module和其他components)。

限定的component

可以通過SplashActivity來探索Activities Components是怎么創建的。它重寫了setupActivityComponent(AppComponent),在這里它創建它自己的Component(SplashActivityComponent),然后注入所有被做@Inject注解的依賴(這個例子中的SplashActivityPresenterAnalyticsManager)。

在這里我們也會提供AppComponent實例(因為SplashActivityComponent是依賴它的)以及SplashActivityModule(它提供了Presenter和Activity實例)。

剩下的就靠你自己了。認真的說 - 嘗試弄清楚一切是怎么合適地聯系在一起的。下一章節我們嘗試深入Dagger 2的那些緊密的元素(在底層它們是怎么工作的)。

代碼:

以上描述的完整代碼可見Github repository

作者

Miroslaw Stanek

Head of Mobile Development @ Azimo


> __[Android]使用Dagger 2依賴注入 - DI介紹(翻譯):__

> __[Android]使用Dagger 2依賴注入 - API(翻譯):__

> __[Android]使用Dagger 2依賴注入 - 自定義Scope(翻譯):__

> __[Android]使用Dagger 2依賴注入 - 圖表創建的性能(翻譯):__

> __[Android]Dagger2Metrics - 測量DI圖表初始化的性能(翻譯):__

> __[Android]使用Dagger 2進行依賴注入 - Producers(翻譯):__

> __[Android]在Dagger 2中使用RxJava來進行異步注入(翻譯):__

> __[Android]使用Dagger 2來構建UserScope(翻譯):__

> __[Android]在Dagger 2中Activities和Subcomponents的多綁定(翻譯):__


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM