Dagger2在Android開發中的應用

 世界是廣泛聯繫的,任何事物和個體都直接或間接相互依賴,在時空長河中共同發展。在面向對象的世界中,更是如此,類與類之間的依賴,關聯關係,模塊(亦或是分層架構中的層)之間的耦合關係,都是咱們在軟件開發實踐中,時刻在處理着的聯繫。html

在軟件開發中,咱們一直嘗試弱化這種聯繫,以便讓軟件程序更健壯,更靈活,便於維護和升級。從面向過程遍程中提倡方法的分離(過程分離),到面向對象開發中的減小類依賴,層間低耦合,層內高類聚,無一不體現了這一點。正由於在面向對象的世界中,咱們更容易處理類(對象),模塊(或層)之間的關係,因此面向對象開發才成爲了更受歡迎的開發方式。java

爲了處理代碼中的依賴關係,依賴注入框架應運而生。在Java的世界中,開源的依賴注入框架更是不勝枚舉,如PicoContainer ,google-guice,Butterfly Container 。。。。。而正如上一篇文章中提到的,Dagger2是第一個使用生成代碼的方式實現依賴注入的框架,它徹底依賴Java的註解處理和編譯時檢查來分析和驗證依賴,在代碼執行效率上擁有無可比擬的優點,而大部分的依賴注入框架都有依賴XML、運行期驗證依賴或者影響應用啓動速度的問題,因此它依然是咱們今天要介紹的主角。android

關於Dagger2,已在上一篇文章中詳細介紹,在Android開發中,咱們可使用ButterKnife來進行控件注入(可是在子module中使用會有問題),使用Dagger2來進行依賴注入。下面咱們來看看在Android開發中,如何使用Dagger2來處理依賴關係。git

原理

使用依賴注入是爲了下降類之間的依賴關係,從而達到被依賴的類(被依賴者)能夠相對獨立的變化而不影響其使用者(依賴者),同時在開發和測試過程當中,也方便使用mocking object替代原有的被依賴類型,適應不一樣的開發環境和測試。如何處理好關係,關鍵在於劃分二字,咱們一般所說的責任、功能的劃分,層、模塊、類、方法的劃分,都是爲了更好的處理各類各樣的關係。在Dagger2的使用中,咱們依然須要良好劃分它的各個組件以便更好的使用它。github

下圖是Android中推薦的組件級別劃分,不熟悉Component,Provide和Module的小夥伴請看上一篇文章。api

Application component/module

Application component/module通常提供和應用相同生命週期的依賴,因此一般和@Singleton關聯,並暴露必要的依賴以便component dependency使用(一般application component會最爲父組件或者component dependency中的dependency)。緩存

 

Activity component/module

Activity component/module通常提供和Activity相同生命週期的依賴,一般和@PerActivity關聯,而且通常是SubComponent或者component dependency。使用Activity component有如下幾個好處:網絡

可在Activity中注入字段(暴露inject方法在Activity中調用)。架構

在Activity基類中注入和使用單例對象app

全局對象圖不用涉及只在Activity中用到的依賴。

 

User component/module

User component/module用於提供User相關的依賴,通常也和@PerActivity關聯,一般會擴展Activity Component,用於注入Fragment。

 

一般咱們會創建一個包含不少接口的主Component,若是但願有多個不須要一直保留在內存中的components(例如綁定到Activity、Fragment甚至用戶會話期的components),咱們能夠建立Dependent component。使用Dependent components須要注意這些方面:

1.兩個Dependent components不能共享相同的Scope。

2.雖然Dagger2有建立Scope實例的能力,你依然有責任根據場景建立和刪除(實例)應用。

3.建立Dependent components的時候,父component須要明確暴露下游組件須要的依賴對象。

Dependent components和SubComponents的差異在於:

  1. SubComponent須要在子組件聲明工廠方法提供實例或Builder。Dependent components只須要傳入父Component的實例便可正常初始化。
  2. SubComponent能夠訪問父Component的全部依賴,而Dependent components只能訪問父Component聲明提供的依賴。

注意

Dagger2區別於其餘諸如框架的主要優點在於它嚴格依靠生成代碼實現(而非反射),這意味着它能夠適用於Android。固然,在Android應用中使用Dagger的時候仍然有一些須要考慮的地方。

由於Android應用採用Java語言來開發,從風格來講會很是不一樣。這種典型的區別是爲了適應移動平臺獨特的表現考慮。

可是與其餘的Java代碼相反的是,不少模式在Android上都不適用,甚至《高效Java開發》中的大部分建議都被認爲不適合Android。

爲了同時達到代碼慣用(符合語言習慣)和輕便的目標,Dagger依靠ProGuard來處理編譯的字節碼,這容許Dagger生成在Server和Android上看起來都很天然的代碼(當使用不一樣的工具鏈來生成在這兩個環境中都能高效執行的代碼時)。更多的是,Dagger有明確的目標來確保生成的代碼和ProGuard的優化兼容一致。

固然不是全部的問題都能以這種方式定位,可是它確實是提供Android特定兼容的主要機制。

Dagger假設Android開發者會使用ProGuard。

 

Talk is cheap,Show your the code

Setup

咱們以AndroidStudio做爲IDE,如下簡稱AS。AS 默認不會把Dagger2生成的代碼視做合法的classes,咱們能夠經過添加android-apt插件將這些文件加入到IDE的class path。咱們能夠在Project的build.gradle中添加工程依賴:

dependencies {
     // other classpath definitions here
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 }

 

而後在用到Dagger2的Module中(如app)應用apt插件:

// add after applying plugin: 'com.android.application'  
apply plugin: 'com.neenbedankt.android-apt'

 

同時添加以下Module依賴:

dependencies {
    // apt command comes from the android-apt plugin
    apt 'com.google.dagger:dagger-compiler:2.2'
    compile 'com.google.dagger:dagger:2.2'
    provided 'javax.annotation:jsr250-api:1.0'
}

 

 

Provided關鍵詞引用的依賴只會在編譯時須要,apt關鍵詞是android-apt插件提供的,用於註解處理。

Coding

下面是一個簡單的關於寵物的Demo,僅用於說明Dagger2的使用。

通常狀況下,在Dagger2中,咱們直接使用到的是Component,因此咱們首先想到的是建立Component,按照上文的建議和通常的開發習慣,咱們會先建立Application級別的Component,用於提供全局的服務或資源。

public interface AppComponent {

    /**
     * Provide your application level handler instance.
     *
     * @return
     */
    Handler getHandler();

    /**
     * Provide your global http requester instance.
     *
     * @return
     */
    IHttpRequester getHttpRequester();

    /**
     * Provide your global storage service.
     *
     * @return
     */
    IStorage getStorage();

}

 

咱們聲明瞭AppComponent,你們能夠發現它是一個接口文件,咱們在裏面咱們提供了全局的Handler、HttpRequester和Storage服務,固然在實際工程中你能夠提供更多的服務。

既然聲明瞭接口,那必然須要有它的實現,可是Dagger2會爲咱們作這件事,咱們須要作的是使用Module來爲Component接口聲明的方法提供實際的依賴。

/**
 * Created by Irwin on 2016/5/16.
 */
@Module
public class AppModule {

    @Provides
    @Singleton
    public static Handler provideHandler() {
        return new Handler();
    }

    @Provides
    @Singleton
    public static IHttpRequester provideHttpProvider() {
        return new DefaultHttpRequester();
    }

    @Provides
    @Singleton
    public static IStorage provideStorage() {
        return new SqliteStorage();
    }

}

 

 咱們實現一個AppModule來爲AppComponent提供依賴,其中提供了實際的Handler,HttpRequester和Storage。由於都是全局單例,全部都使用了Singleon關鍵字和static關鍵字。而後咱們須要經過註解把AppComponent和AppModule關聯起來,以下:

/**
 * Created by Irwin on 2016/5/16.
 */
@Component(modules = AppModule.class)
@Singleton
public interface AppComponent {

          ......

}

 完成後咱們能夠編譯代碼,若是沒有出錯,能夠在Build目錄下找到Dagger2爲咱們生成的Component接口的實現代碼。

接下來,咱們在Application中初始化Component:

public class MyApplication extends Application {
    private AppComponent mAppComponent;
    private String TAG = "ApplicationInfo";

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.create();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }


}

 

 爲了增長趣味性,咱們這個Demo是簡單展現寵物的,因此接下來咱們添加一個寵物類。

/**
 * Created by Irwin on 2016/5/16.
 */
public interface IPet {

    public int getPortrait();

    public String getType();

    public String getName();

    public String getStatus();

    public void enjoy(TextView view);
}

 

/**
 * Created by Irwin on 2016/5/16.
 */
public abstract class AbsPet implements IPet {

    private int mIndex = -1;

    private String mStatus;

    private int mPortrait;

    @Inject
    Handler mHandler;

    @Inject
    List<String> mStatusList;

    @Inject
    protected String mName;

    @Inject
    @Descript("Yell")
    protected String mYell;

    public AbsPet(int portrait) {
        mPortrait = portrait;
    }

    @Override
    public String getName() {
        return mName;
    }

    @Override
    public int getPortrait() {
        return mPortrait;
    }

    public Handler getHandler() {
        return mHandler;
    }

    public List<String> getStatusList() {
        return mStatusList;
    }

    @Override
    public String getStatus() {
        if (mStatus == null) {
            mStatus = mYell;
        }
        return mStatus;
    }

    @Override
    public void enjoy(final TextView view) {
        if (mIndex == -1) {
            view.setText(mYell);
        }
        Random random = new Random(System.currentTimeMillis());
        int seconds = random.nextInt(5);
        if (seconds < 1) {
            seconds = 1;
        }
        getHandler().postDelayed(new Runnable() {
            @Override
            public void run() {
                boolean hasNext = nextStatus();
                if (hasNext) {
                    enjoy(view);
                }
                view.setText(getStatus() + (hasNext ? " Ing" : " ..."));
            }
        }, seconds * 1000);
    }

    public boolean nextStatus() {
        if (++mIndex < getStatusList().size()) {
            mStatus = getStatusList().get(mIndex);
            return true;
        }
        mIndex = -1;
        return false;
    }

}

 

而後分別實現兩隻寵物,Dog和Cat,這是咱們具體要依賴的對象,下面是Dog的代碼。

public class Dog extends AbsPet {
    private String mType;

    @Inject
    public Dog(int portrait) {
        super(portrait);
        this.mType = "汪星人";
    }

    @Override
    public String getType() {
        return mType;
    }

}

 

 

注意構造函數上的@Inject註解,它代表Dagger2能夠自動new 這個類的實例。

接下來,咱們添加一個Activity來顯示寵物,

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Info";
    private GifImageView mPetView;
    private TextView mTV_Info;
    private TextView mTV_Status;

    @Inject
    IPet mPet;

    ......

}

 

如代碼中所示,咱們將mPet字段添加@Inject註解,經過依賴注入的方式提供值。

須要注入字段的類也須要有一個Component來實現注入,因此咱們爲此Activity添加一個Component,在此以前咱們先添加一個Module來爲Component提供依賴,下面是提供Dog依賴的Module

 
 
@Module(includes = DogStatusModule.class)
public class DogModule {

    @Provides
    @Activity
    public IPet provideDog(Dog dog) {
        return dog;
    }

    @Provides
    public List<String> provideStatus(Set<String> set) {
        return new ArrayList<>(set);
    }

    @Provides
    public String provideName() {
        return "二汪";
    }

    @Provides
    public int providePortrait() {
        return R.drawable.dog2;
    }

    @Provides
    @Descript("Yell")
    public String provideYell()
    {
        return "汪汪";
    }


}

 

其中咱們爲DogModule提供了它所須要的狀態Module  DogStatusModule。

接下來添加Activity的Component並關聯DogModule,

@Component(dependencies = AppComponent.class, modules = DogModule.class)
@Activity
public interface PetActivityComponent {

public void inject(MainActivity activity);

public IPet getPet();

}

完成並編譯後,能夠在Build目錄下找到Dagger2爲咱們生成的Component接口的實現代碼。接下來咱們就能夠在Activity中簡單的使用:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Info";
    private GifImageView mPetView;
    private TextView mTV_Info;
    private TextView mTV_Status;

    @Inject
    IPet mPet;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mPetView = (GifImageView) findViewById(R.id.IMV_Pet);
        mTV_Info = (TextView) findViewById(R.id.TV_Info);
        mTV_Status = (TextView) findViewById(R.id.TV_Status);

        PetActivityComponent component = DaggerPetActivityComponent.builder()
                .appComponent(((MyApplication) getApplicationContext()).getAppComponent())
                .build();
        //You must call inject first so that Dagger2 will inject fields for you, otherwise you'll get NullPointer exceptions.
        component.inject(this);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                playWith(mPet);
                getHttpRequester().request(0, "http://www.baidu.com", "search=dagger2", new IResultHandler<String>() {
                    @Override
                    public void onSuccess(String o) {
                        Log.i(TAG, "Response: " + o);
                    }

                    @Override
                    public void onFail(Throwable error) {

                    }
                });
            }
        });
        bindPet(mPet);
    }

    public void bindPet(IPet pet) {
        mPetView.setImageResource(pet.getPortrait());
        mTV_Info.setText(getString(R.string.PetInfo, mPet.getType(), mPet.getName()));
        mTV_Status.setText(pet.getStatus());
    }

    public void playWith(IPet pet) {
        pet.enjoy(mTV_Status);
    }

    public IHttpRequester getHttpRequester() {
        return ((MyApplication) getApplicationContext()).getAppComponent().getHttpRequester();
    }

    public IStorage getStorage() {
        return ((MyApplication) getApplicationContext()).getAppComponent().getStorage();
    }

}

 

在調用Component的Inject後,Dagger2就爲mPet字段注入了值,而後咱們就能夠放心的使用mPet字段了,同時,咱們還可使用AppComponent中提供的全部服務或資源,如

 ((MyApplication) getApplicationContext()).getAppComponent().getHttpRequester().request(...);

 

至此,代碼編寫完成。固然,咱們還添加了一些額外的代碼,如HttpRequester,不一樣的Storage實現,CatModule,全局都是經過Dagger2使用這些實現,因此你能夠隨意改變本身的實現或者替換依賴,而不影響上層代碼的使用,可使你的開發和測試變得更簡單哦!

運行工程,就能夠看到效果了,

 

此時,若是咱們但願使用依賴對象的不一樣實現,簡單的替換Component依賴的Module,或者直接修改Module中相應提供依賴的地方便可。下面咱們將Activity中的關聯的DogModule替換爲CatModule

@Component(dependencies = AppComponent.class, modules = CatModule.class)
@Activity
public interface PetActivityComponent {

   ...
}

 

替換後效果:

 

在實際的開發環境中,你能夠隨意替換不一樣的存儲,網絡請求實現來知足不一樣的場景和需求,是否是很方便:D

 

總結

1、      使用Dagger須要引入Dagger和Dagger compiler,在IDEA和Eclipse中可能須要打開Annotation processor。在AndroidStudio中須要引入android-apt plugin(see https://bitbucket.org/hvisser/android-apt,用於將Dagger2生成的代碼加入classpath)

Android-apt插件幫你把annotation processors和AndroidStudio結合起來使用,它主要有兩個功能:

1.容許配置編譯時的annotation做爲依賴,可是不會把這些東西包括到最終的Apk或Library中。
2.設置源碼路徑以便註解處理生成的代碼能夠被AndroidStudio識別。

2、      類構造函數加上@Inject生成實例,類字段加上@Inject可注入字段,注入字段依靠@Inject的構造函數或者@Provides方法。

注:

  1. Dagger2不能注入final字段和static字段,以及不可訪問的字段(Private)。
  2. Dagger2沒法注入多個類型相同的字段,可是能夠經過加上qualifier識別依賴。
  3. Dagger2建立實例是在生成的T_Factory中進行的,而注入字段是經過T_MembersInjector中完成的,因此直接調用new T()不會注入字段。對於已有的非Dagger注入的對象,能夠在Component中添加inject(T)(方法名能夠隨意)方法,調用這個方法就能夠注入須要的字段。

3、      @Provides方法能夠有本身的依賴,返回不一樣的類(也能夠是基本類型)。

4、      用一個接口(或抽象類)來提供Component,這個接口裏面應該聲明沒有參數但返回須要類型的方法,接口上須要加上@Component,並經過module參數引入須要的module。Dagger會按照約定生成一個Component實現。

5、      Component的使用方式有兩種:一種是使用builder並傳入須要的module;另外一種是直接調用它的create()方法(適用於它的module中全是@Provides的靜態方法時或有無參構造函數時)。

Component像鏈接@Module和@Inject的橋樑,將全部的東西聯繫起來。

Component實現主要是經過生成的builder來實例化。使用它的builder()方法來獲取一個builder實例。若是一個嵌套的@Component.Builder類型已經存在,builder()方法會返回它的一個生成實現;若是嵌套的@Component.Builder不存在,返回的builder會提供設置每個須要的modules和component依賴的方法(方法名是以小駝峯命名法命名的module和component類型名)。沒有可見默認構造函數的module和component依賴必須明確設置,可是有默認或無參構造函數的可被component訪問的module能夠省略。下面是使用component builder的例子:(From:http://google.github.io/dagger/api/latest/dagger/Component.html)

public static void main(String[] args) {

     OtherComponent otherComponent = ...;

     MyComponent component = DaggerMyComponent.builder()

         // required because component dependencies must be set

         .otherComponent(otherComponent)

         // required because FlagsModule has constructor parameters

         .flagsModule(new FlagsModule(args))

         // may be elided because a no-args constructor is visible

         .myApplicationModule(new MyApplicationModule())

         .build();

   }

 

在component只有無參(構造函數)modules,沒有component依賴的狀況下,component實現也會有一個工廠方法create(),SomeComponent.create()和SomeComponent.builder().build()是等同的。

 

6、      Module是一個用來知足稍複雜依賴(在@Inject沒法知足的時候)的類,以@Module聲明,在它裏面能夠聲明@Provides的方法(Provides方法優先於@Inject構造函數),@Provides方法提供Component裏面的綁定須要的依賴(經過返回類型指定,因此沒法注入多個類型相同的字段),這些依賴能夠類實例,也能夠是基本類型。Module還能夠經過include引入其餘的module。

7、      Scope

@Singleton

@Singleton用來標記單例,可是是在component的生命週期而非應用的生命週期中的單例。

若是T被註解爲單例(在T的類聲明或在provideT上加@Singleton,同時在Component上加@Singleton),引入T的component中的TProvider會使用以下方式生成(其餘的Scope也應如此):

TProvider= ScopedProvider.create(CoffeeMaker_Factory.create(…));

若是T未被標記爲單例,TProvider會這樣生成:

TProvider=T_Factory.create(…);

 

@Scope

用@Scope註解組件就能夠加上範圍限定。在這種狀況下,組件實現會保存對全部加上範圍限定的類實例的引用以便重用。有Scope的帶有@Provides方法的模塊只能被擁有相同範圍限定的組件引用。

每個Component均可以用Scope註解來添加Scope。Component實現確保每一個實例中每一個Scope綁定只有一個規定。若是Component聲明瞭一個Scope,它就只能包括Unscope或者該Scope的綁定。例如:

@Singleton @Component
   interface MyApplicationComponent {
     // this component can only inject types using unscoped or @Singleton bindings
   }

爲了讓Scope表現正確,什麼時候初始化component實例是調用者的責任。好比說,一個單例組件,在一個應用中只應該被實例化一次,而一個RequestScoped的組件應該請求一次,實例化一次。由於組件是自維護(Self-contained)的實現,退出一個scope跟丟掉對component實例的全部引用同樣簡單。

父子組件的Scope應該是一種包含關係,父組件的Scope包括的子組件Scope的範圍。

 

8、      Lazy injection

Lazy injection會等到第一次調用Lazy<T>.get()的時候建立T的實例並緩存。

9、      Provider

Provider<T>提供能夠返回不一樣實例的功能,每一次調用Provider<T>.get()都會調用綁定邏輯,生成新的實例。

10、      Qualifier

Qualifier用於單靠類型沒法識別依賴的時候(即多個依賴(可注入字段)類型相同時),使用Qualifier註解,Dagger會同時經過類型和qualifier來識別依賴。

11、        MultiBindings

多重綁定能夠把多個對象綁定到一個Set或Map,經過@Provides(type=SET|MAP)方法提供對象到集合中,對於Map 的Key,分爲兩種方式:一種是在運行時知道Key的狀況,須要經過註解來指定Key(支持複雜的Mapkey);一種是在運行時才知道Key的狀況,這時能夠經過提供Set<Map.Entry>,而後組裝成Map。

12、        子組件

使用子組件能夠把應用的對象圖劃分紅子圖,能夠壓縮應用的各個部分,或者在一個組件上使用多個範圍限定。組件提供子組件有兩種方式,一種是在組件中提供工廠方法來返回子組件或子組件的Builder;第二種是注入子組件的builder。

父子組件會共享相同的Module實例,父組件已經引用的Module類,子組件不須要再傳入(包括工廠方法和Builder方法)。

十3、        Producer實現異步注入

Producers引入了新的註解@ProducerModule, @Produces, 和@ProductionComponent,分別對應@Module,@Provides, 和 @Component。咱們把@ProducerModule註解的類視爲producer modules,@Produces註解的方法視爲producer methods,@ProductionComponent註解的接口視爲producer graphs(相似於modules, provider methods, 和object graphs)。

和原始Dagger最關鍵的不一樣在於producer方法會返回ListenableFuture<T>,而且後面的producer會依賴於這個未封裝的T,當future可用的時候,框架會計劃執行後續的方法。

經過綁定@Production Executor來指定一個線程池,每個Producer 的方法都會在這個線程池中被計劃執行。

相關文章
相關標籤/搜索