1. 程式人生 > >依賴注入框架Dagger2—2.各註解用法

依賴注入框架Dagger2—2.各註解用法

0.前言

接上一篇入門文章,這篇主要是各屬性實戰。

1.Dagger2各屬性瞭解

  • 必要屬性 @inject//注入,@Component,@Moudle,@Provider 為什麼說這個幾個是必要屬性,因為只要想用dagger2這幾個屬性是繞不開的。
  • 高階屬性 @Named @Qualifier @Scope @Singleton; 這四個屬性實際上可以分文倆組,@Named底層實現是@Qualifier,@Singleton底層實現是@Scope

2.Dagger2各屬性使用及分析

2.1.@Inject

註解建構函式: 通過標記建構函式,告訴Dagger 2可以建立該類的例項(Dagger2通過Inject標記可以在需要這個類例項的時候來找到這個建構函式並把相關例項new出來)從而提供依賴關係。 若註解了建構函式,沒有 註解依賴變數,將不建立例項。 註解依賴變數:

通過標記依賴變數,Dagger2提供依賴關係,注入變數。 若註解了依賴變數,而沒註解建構函式,將獲得一個null物件。

2.2.@Moudle

通過註解類,為Dagger提供依賴關係。還記得上一篇當中

@Module
public class TestModule {
    @Provides
    public Test provideTest() {
        return new Test();
    }
}

2.3.@Provider

@Provides依然是為了提供依賴關係,是對@Inject的補充,彌補了@inject對於第三方框架和介面類的盲區。 @Provides方法本身可能也擁有自身的依賴關係。 @Provides方法本身是不能獨立存在的,它必須依附於一個Module。

2.4.Component

通過註解介面類,作為提供依賴和所需依賴之間的橋樑(連結Moudle 和 Containter),在編譯時生成相關例項,將相關依賴注入完成使命。

@Component(modules={Test.class,Test1.class})
public interface TestComponent{
    void inject(Class class);
}

依賴規則 若@Injdec和@Module都提供了依賴物件 1.查詢Module中是否存在建立該類的方法 2.若存在,初始化引數,完成例項建立 3.不存在用@Inject建立例項

2.5.@Singleton

這個註解的意思就是單例。在Dagger2的使用中,需要有區域性單例全域性單例的意識,那麼什麼是區域性單例,可以理解為在一個Activity內n個型別相同的變數地址相同,但一旦出了這個Acitivty的範圍這一型別的變數地址便不同了;全域性單例就好理解了,同一型別的變數在整個app內的任何地方,地址都一樣。

不管是區域性或者全域性的單例,他們其實基本定義差別都一樣,唯一的差別就是Component的作用域,若Component的作用域是全域性的那麼對應@Singleton或者@Scope的對應moudle就是全域性,反之亦然,這麼說有點抽象,看如下程式碼:

package com.zjandroid.nani.constant;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

@Module
public class UrlConstant {

    @Singleton
    @Provides
    public UrlConstant instance(){
        return new UrlConstant();
    }

}

這是專案內網路地址管理的字串常量類,我把它作為一個Moudle,並在它的例項化方法上使用了 @Singleton註解,接著我又定義了2個Component(作用域為區域性)

package com.zjandroid.nani.dagger.component;

import com.zjandroid.nani.constant.UrlConstant;
import com.zjandroid.nani.view.activity.SplashActivity;
import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component (modules={UrlConstant.class})
public interface SplashComponent {
    void inject(SplashActivity splashActivity);
}
package com.zjandroid.nani.dagger.component;

import com.zjandroid.nani.constant.UrlConstant;
import com.zjandroid.nani.view.activity.MainActivity;

import javax.inject.Singleton;

import dagger.Component;

@Singleton
@Component (modules={UrlConstant.class})
public interface MainComponent {
    void inject(MainActivity splashActivity);
}

這倆個Component分別對應SplashActivity和Mainactivity,接下來就是去這倆個Acitivity內進行注入並觀察輸出結果,這倆個Acitivty內啥邏輯都沒有,唯一有意義的就是這一行程式碼: XXXComponent.builder().urlConstant(new UrlConstant()).build().inject(this);

public class MainActivity extends BaseActivity {

    @Inject
    UrlConstant mUrlConstant11;

    @Inject
    UrlConstant mUrlConstant22;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        DaggerMainComponent.builder().urlConstant(new UrlConstant()).build().inject(this);

        LogUtils.d("mUrlConstant11 == "+ mUrlConstant11.toString());
        LogUtils.d("mUrlConstant22 == "+ mUrlConstant22.toString());

    }
}
public class SplashActivity extends BaseActivity {

    @Inject
    UrlConstant urlConstant1;

    @Inject
    UrlConstant urlConstant2;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        
        DaggerSplashComponent.builder().urlConstant(new UrlConstant()).build().inject(this);
        
        LogUtils.d("mUrlConstant1 == "+ urlConstant1.toString());
        LogUtils.d("mUrlConstant2 == "+ urlConstant2.toString());
        startActivity(new Intent(SplashActivity.this, MainActivity.class));
    }
}
區域性單例的結果:不同Acitivty的地址不一樣
║ mUrlConstant1 == com.zjandroid.nani.constant.UrlConstant@4a7f5dac
║ mUrlConstant2 == com.zjandroid.nani.constant.UrlConstant@4a7f5dac
║ mUrlConstant11 == com.zjandroid.nani.constant.UrlConstant@4a8063c0
║ mUrlConstant22 == com.zjandroid.nani.constant.UrlConstant@4a8063c0

接下來實現全域性的單例: 全域性單例的核心就一點,用同一個component ,並宣告同一個Singleton(Scope) ,創建出來的是同一個物件。 當Component物件是全域性唯一,它所對應的@Singleton~Moudle就是一個全域性單例,它inject出來的物件也是單例。具體到程式碼上,就是造一個全域性唯一的Component讓子Activity拿著自己inject自己。 接下來是具體實施步驟: 1.新增一個GlobalComponent ;

@Singleton
@Component(modules={UrlConstant.class})
public interface GlobalComponent {
    void inject(BaseActivity baseActivity );
}

2.在Application中例項化它; pps:不是說非得在Application例項化,只是利用了它全域性唯一特性,完全可以自己另啟一個全域性唯一的類來承載,但沒必要;

public class NaNiApplication extends Application {
    private GlobalComponent mGlobalComponent;
   
    /**
     * 獲取全域性單例Global
     * @return
     */
    public GlobalComponent getGlobalComponent() {
        if(mGlobalComponent == null)
            mGlobalComponent = DaggerGlobalComponent.builder().build();
        return mGlobalComponent;
    }
}

3.Client中呼叫 對於Client的選擇,我傾向於選擇父類中進行初始化,然後加標記為進行控制,對子類暴露,這樣,子類想要的時候可以直接拿,更重要的,可以避免Componet中的inject(XXXActivity)新增類的問題;

public abstract class BaseActivity extends NetObserverActivity {


    @Inject
    UrlConstant mUrlConstant;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if(!notInitGlobalComponent())
        ((NaNiApplication) getApplication()).getGlobalComponent().inject(this);

    }

    protected abstract boolean notInitGlobalComponent();

    public UrlConstant getUrlConstant() {
        return mUrlConstant;
    }
}

public class SplashActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        LogUtils.d("mUrlConstant11 == "+ getUrlConstant().toString());
        startActivity(new Intent(SplashActivity.this, MainActivity.class));
    }

    @Override
    protected boolean notInitGlobalComponent() {
        return false;
    }
}
public class MainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        LogUtils.d("mUrlConstant11 == "+ getUrlConstant().toString());
    }

    @Override
    protected boolean notInitGlobalComponent() {
        return false;
    }
}
輸出結果
║ mUrlConstant11 == com.zjandroid.nani.constant.UrlConstant@4a7ea1e0
║ mUrlConstant11 == com.zjandroid.nani.constant.UrlConstant@4a7ea1e0

2.6.@Scope

0>Scope是Singleton的底層實現,一般在工程實施中,它作為提高可讀性的手段之一讓程式設計師可以一眼就看出哪些是全域性哪些是區域性的生命週期 1>來標識範圍的註解,並控制如何重複使用類的例項。僅用@inject註解,未用@Scope註解,在每次依賴注入的時候都會建立新的例項。使用@Scope註解會快取第一次建立的例項,然後重複注入快取的例項,不會建立新的例項。 2>如果有類注入例項的類被@Scope註解,那麼其Component必須被相同的Scope註解。 3>如果依賴例項的注入來源是@Provides方法時,@Provides方法必須被@Scope註解; 4>如果依賴例項的注入來源是@Inject註解的建構函式時,例項類必須被@Scope註解。@Scope實際上是對注入器的控制。

另外在看其它部落格文章的時候,你會經常看到 @ActivityScope 可以宣告一個Activity生命週期的物件 ,@ApplicationScope 可以宣告一個Application生命週期的物件 , 難道這些Scope這麼神? 定義一個名字就可以控制物件的生命週期了? 其實這和 @Singleton一樣的,都是程式碼是否通過同一個 component 物件來控制的。比如 @ActivityScope 定義的物件 ,其在Activity建立了component物件 ,那這個component物件肯定在這個Activity的生命週期內啊,依賴創建出來的物件也肯定是這個Activity啊。還有@ApplicationScope 中的component 物件是在 Application中的,那依賴創建出來的物件的生命週期也肯定是和 @Singleton的一樣的,單例的生命週期不就是整個 Application 嗎。

2.7.@Qualifier

這是限定符,它可以標記不同的構造方法讓外部呼叫者獲取期望的構造物件,比如:

比如Person類,他有一個屬性Sex,new了第一個Person,設定sex=“male”,new了第二個Person,設定sex=“female”,我們如何在注入依賴時候,準確注入一個男人或者女人呢?

/**
 * 自定義一個限定符
 */ 
 @Qualifier//限定符 
 @Documented 
 @Retention(RetentionPolicy.RUNTIME) 
 public @interface Type { 
    String value() default "";//預設值為"" 
 }

首先自定義一個註解@interface,刨開2個元註解不看 @Documented @Retention,頂部定義一個 @Qualifier限定符標識,然後這一句String value() default ""的意思是,Type註解可以傳參,預設為一個空字串。 定義好了這個註解就相當於定義好了一個可接收引數的標籤,接下來我們要將這個標籤傳入不同的引數然後分別貼到不同的建構函式之上(記住其中的關係,@Provide + @Type("")區分不同建構函式,Type又被@Qualifier修飾),這樣,就相當於讓每個建構函式有了辨識的途徑,若要使用不同的建構函式,則只需去揭開不同的標籤即可。

/**
* 定義Module
*/
@Module public class SalaModule { 
    ......... 
    
    @Type("normal")//使用限定符來區別使用哪個建構函式返回物件 
    @Provides public Apple provideNormalApple() { 
        return new Apple(); 
    } 
    
    @Type("color")
    //使用限定符來區別使用哪個建構函式返回物件 
    @Provides public Apple provideColorApple(String color) { 
        return new Apple(color); 
    } 
    
    //由於我們的Apple建構函式裡使用了String,所以這裡要管理這個String(★否則報錯) 
    //int等基本資料型別是不需要這樣做的 
    @Provides public String provideString(){ 
        return new String(); 
    } 
}

Module傳入不同引數分別標記不同的構造方法。

/**
 * 編寫Component
 */
@Component(modules = {SaladModule.class})
//指明要在那些Module裡尋找依賴 
public interface SaladComponent { 
    ......... 
    
    @Type("normal")
    //關鍵靠這個限定符來區分呼叫哪個建構函式 
    Apple provideNormalApple();
    //這個方法名並不重要,只要是provide開頭就行,但是建議和module裡一致 
    @Type("color") 
    Apple provideColorApple(); 
    String provideString(); 
    //注意:下面的這個方法,表示要將以上的三個依賴注入到某個類中 
    // 這裡我們把上面的三個依賴注入到Salad中 
    // 因為我們要做沙拉 
    void inject(Salad salad); 
}

Component通過剛才自定義的@Type註解標註對外暴露的介面,當這個介面方法被呼叫時,它會去Moudle內找對應的相同的@Type引數列表一致的構造與provider方法。

2.8.@Named

Named底層實現是@Qualifier。

3.Thanks