Dagger2進階篇(二)

稀飯_發表於2018-08-19

上一篇文章講解了無參情況兩種注入方式和有參情況的兩種注入方式。以及兩種注入方式的優先順序。這一篇我們會更加深入的去學習更多關於Dagger2的用法。

上一篇我們被注入的物件構造方法都是一個,但是如果構造方法進行了過載,我們怎麼用這兩種方式進行注入呢?

如果兩個構造方法同時需要注入,那麼第一種注入方式解決不了這個問題,因為Dagger2不允許同時是Inject註解兩個構造方法,而且自定義的註解不能註解構造方法,編譯會不通過。

這時候我們就需要第二種方式去注入,程式碼如下:

public class HotCar {

    public String name;


    public HotCar() {

    }

    public HotCar(String name) {
        this.name = name;
    }


    public void logName() {
        Log.e("rrrrrrrrr", "引數構造方法" + name);
    }
}複製程式碼

對應的module如下

@Module
public class A03Module {

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

    @Provides
    @noParm
    public HotCar provideNullParm() {
        return new HotCar();
    }

    @Provides
    @hasParm
    public HotCar providehasParm(String name) {

        return new HotCar(name);
    }

}複製程式碼

這裡需要說明 @noParm和 @hasParm註解都是我們自定義的註解,用來標記區分不同的構造方法建立的物件。

那麼我們使用的程式碼如下:

public class ThereActivity extends AppCompatActivity {

    @Inject
    @hasParm
    HotCar hotCar1;

    @Inject
    @noParm
    HotCar hotCar2;

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

        DaggerA03Component.builder().build().inject(this);
        hotCar1.logName();

        hotCar2.logName();
    }
}複製程式碼

還是通過這兩個註解去區分不同的物件。完成注入:

這個自定義註解寫法也非常簡單,程式碼如下

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface noParm {
}
複製程式碼
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface hasParm {
}複製程式碼

同時Dagger2還給我們提供了另外一種寫法,就是使用@Named(“flag”)來代替自定義註解去區分不同的物件。點進去@Named的原始碼可以看到它是Qualifier的預設實現。當然在實際開發的時候使用自定義註解去實現還是顯得很酷的。

二Dagger2怎麼實現一個單例呢?

我們在開發時候很多時候用到單例模式,那麼對於Dagger2怎麼去建立一個單例的依賴注入物件呢?這裡的單例模式還是需要通過自定義註解完成,所以這裡還是隻能使用第二種方式進行注入。這裡需求是讓Student實現單例模式,程式碼如下:

Scope 顧名思義是作用域,用於標註一個物件的作用域。Scope也是一個元註解,首先用Scope 來定義一個註解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentScope {
}
複製程式碼

將這個定義好的註解 StudentScope 標註在 Component 以及 Module 提供物件的方法上。

@StudentScope
@Component(modules = A04Module.class)
public interface A04Component {
    void inject(FourActivity activity);
}複製程式碼
@Module
public class A04Module {
    @StudentScope
    @Provides
    public Student provideStudent() {
        return new Student();
    }
}複製程式碼

然後就是使用程式碼

public class FourActivity extends AppCompatActivity {

    @Inject
    Student student1;
    @Inject
    Student student2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_four);
        DaggerA04Component.builder().build().inject(this);
        Log.e("rrrrrrrrr",student1.toString());
        Log.e("rrrrrrrrr",student2.toString());


    }
}複製程式碼

通過列印程式碼我們可以確定這兩個Student物件是同一個物件。那麼我們去看一下原始碼是怎麼實現的(因為篇幅我們只看主要程式碼)

@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {

  this.provideStudentProvider =
      DoubleCheck.provider(A04Module_ProvideStudentFactory.create(builder.a04Module));

  this.fourActivityMembersInjector = FourActivity_MembersInjector.create(provideStudentProvider);
}複製程式碼

這裡和之前不同的地方就是呼叫了 DoubleCheck.provider 方法對provider進行了包裝,那麼 DoubleCheck 做了什麼工作呢,繼續看DoubleCheck的原始碼:

public T get() {   // 實現單例
    Object result = this.instance;
    if(result == UNINITIALIZED) {
        synchronized(this) {
            result = this.instance;
            if(result == UNINITIALIZED) {
                result = this.provider.get();
                this.instance = reentrantCheck(this.instance, result);
                this.provider = null;
            }
        }
    }

    return result;
}複製程式碼

看到這裡,我們找到了單例的實現程式碼,也直到了這裡實現了單例模式的邏輯。但是這裡需要強調兩點:

  • 這裡的單例只是對於同一個 Component物件來說的,在同一個 Component物件的生命週期裡,持有的依賴物件為同一個。不同的Component物件中的自然就是不同的了。
  • Scope的注意事項:Module 中 provide 方法的 Scope 註解和 與之繫結的 Component 的 Scope 需要保持一致,否則作用域不同會導致編譯時會報錯。

到了這裡你一定有很多疑問:

每個Activity都有對應的 Component類,每個Component物件都持有相應的Module類,如果某個Activity希望和另外一個Activity共享依賴例項,應該如何做呢?----這也就是很多文章說的區域性單例效果。

還有我們在開發中常用到全域性的單例模式,例如聯網的,Sp儲存的,那麼全域性單例模式又該怎麼寫呢?

接下來我們學習Component的組織關係,用於解決上邊區域性單例和全域性單例的問題。Component擁有兩種關係依賴關係繼承關係。

依賴關係:一個Component可以依賴於一個或多個Component,依賴關係通過Component中的dependencies屬性來實現,具體用法通過一個例子來看一下;

建立三個Bean類:People類,Teacher類,Money類,FiveActivity中有的People和Money注入,TeacherActivity類中有Teacher和Money注入。他們共同有Money注入。所以我們對應的寫三個Component,再寫三個module,他們之間的組織關係如下:

Bean類:

public class People {
}
public class Teacher {
}
public class Money {
}複製程式碼

Model類:

@Module
public class A05Module {
    @Provides
    public People providePeople() {
        return new People();
    }
}
@Module
public class A06module {
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }
}
@Module
public class ShareModule {
    @Provides
    @MoneyScope
    public Money provideMoney() {
        return new Money();
    }
}複製程式碼

這裡我們自定義了註解去標記這個money提供的物件,因為我們的目標是使用同一個物件,同時我們還有三個Component類程式碼如下:

@PeopleScope
@Component(modules = A05Module.class, dependencies = ShareComponent.class)
public interface A05Component {
    void inject(FiveActivity activity);
}
@TeacherScpoe
@Component(modules = A06module.class, dependencies = ShareComponent.class)
public interface A06TeacherComponent {
    void inject(TeacherActivity activity);
}
@MoneyScope
@Component(modules = ShareModule.class)
public interface ShareComponent {
    Money getMoney();
}
複製程式碼
這裡每個Component上邊都有一個自定義註解:ShareComponent上邊的註解是因為,要和對應繫結的module中作用域一樣。

而對應的其他Component上的註解是因為與有作用域的Component依賴關係,所以要定義自己的作用域,否則編譯報錯。

然後就是我們使用的程式碼:

public class FiveActivity extends AppCompatActivity {
    @Inject
    People people;
    @Inject
    Money money;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);

        ShareComponent share = DaggerShareComponent.builder().build();
        DaggerA05Component.builder().shareComponent(share).build().inject(this);

        Log.e("rrrrrrrrrrrrr", people.toString());
        Log.e("rrrrrrrrrrrrr", money.toString());


    }
}複製程式碼
public class TeacherActivity extends AppCompatActivity {
    @Inject
    Teacher teacher;
    @Inject
    Money money;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);
        ShareComponent share = DaggerShareComponent.builder().build();
        DaggerA06TeacherComponent.builder().shareComponent(share).build().inject(this);
        Log.e("rrrrrrrrrrrrr", teacher.toString());
        Log.e("rrrrrrrrrrrrr", money.toString());
    }
}複製程式碼

這裡執行列印兩個頁面的Money的地址:

com.advertising.administrator.dagger_simple.FiveActivity.Money@22c348
com.advertising.administrator.dagger_simple.FiveActivity.Money@ad51d45複製程式碼

發現並沒有實現這兩個頁面的單例模式,原因在於我們每個頁面都建立了一個ShareComponent物件,所以導致單例模式失效。

那麼只要解決了建立同一個ShareComponent物件,就能保證單例模式的實現。

這裡我們就可以在Application下去建立這個ShareComponent,然後在需要的地方注入依賴通一個ShareComponent就完成了全域性的單例,如果其他不同的頁面不用同一個ShareComponent,就完成了區域性單例模式。

依賴模式除了可以寫全域性單例和區域性單例,我們還可以通過依賴模式讓任意的Component之間有依賴關係,減少注入程式碼的書寫。

Component除了有依賴關係還有繼承關係的寫法。並且通過繼承關係,繼承關係也可以完成單例模式(這裡單例模式寫起來不那麼優雅,實際開發中不常用到,所以不做書寫)

這裡我們先不去用繼承完成這個單例,因為它的寫法有點複雜。我們先學習繼承的基本寫法。然後再寫區域性單例模式。

我們去建立兩個Bean分別是Melon,Noodle,如下:

public class Noodle {
}
public class Melon {
}複製程式碼

建立兩個module如下:

@Module
public class FoodModule {
    @Provides
    public Noodle provideNoodle() {
        return new Noodle();
    }
}
@Module
public class SnackModule {
    @Provides
    public Melon provideMelon() {
        return new Melon();
    }
}複製程式碼

然後就是Component類:

@Component(modules = FoodModule.class)
public interface FoodComponent {
    SnackComponent getSnack();
}
@Subcomponent(modules = SnackModule.class)
public interface SnackComponent {
    void inject(SixActivity activity);
}複製程式碼

這裡有兩點需要注意:

  • SnackComponent 用這個進行註解,並且抽象方法是注入的方法。
  • FoodComponent 的抽象方法的返回值是另外一個Component。

對應的使用方法如下:

DaggerFoodComponent.builder().build().getSnack().inject(this);複製程式碼

上邊就是繼承的最簡單基本的用法。

通過上邊的講解,我們解決了依賴注入迷失問題。同時學會了區域性單例和全域性單例的寫法。而且學會了Component之間的組織關係:繼承和依賴。

下篇文章介紹一下Dagger2依賴注入框架怎麼和mvp框架進行配合,融入,進而提高開發效率。

程式碼下載地址:github.com/XiFanYin/Da…


相關文章