一點關於dagger2在項目中使用的總結

dagger是一款ioc注入框架,相比於butterknife只能進行控件和事件注入,它可以進行任意對象的注入,對於項目的解耦是非常方便的,在中大型項目中使用得比較多,相較於其它第三方庫,這個庫的入門檻還是比較高的,如果不熟悉它的使用,是很容易配置報錯的,因爲它裏面有很多規則是需要遵守的,如果不按照它的要求來,導致的結果就是編譯不通過。最近打算總結下關於它的基本使用,以及在項目中使用的建議。


基本使用

在使用之前先來說下module和component,前者是用來提供創建好的對象注入給使用者調用,而component是用來做注入操作的,起到連接作用。

說完了module和component,就來說下基本使用

添加依賴

annotationProcessor "com.google.dagger:dagger-compiler:2.4"
implementation "com.google.dagger:dagger:2.4"

module提供對象

@Module
public class MainModule {

    @Provides
    public Teacher provideTeacher(){
        return new Teacher();
    }
    
    @Provides
    public Student provideStudent(Teacher teacher){
        return new Student(teacher);
    }

}

提供的module需要使用@Module註解標註,在這個類容器中,提供了兩個對象,Student是依賴Teacher的,提供的方法使用@Provides標註。

component注入操作

@Component(modules = {MainModule.class})
public interface MainComponent {
    void injectActivity(MainActivity activity);
}

負責提供注入的接口,需要使用@Component標註,同時指定綁定的Modules,接口中定義注入的方法,可以任意名稱,但是參數指定爲目標類,注意這個參數要寫死爲需要注入的目標類,每注入一個對象就需要單獨寫一個注入方法。

對象注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);  //1

        System.out.println("teacher--->>>" + teacher.hashCode());
        System.out.println("student---->>>" + student.hashCode());
    }
}

使用@Inject標記的對象,在編譯掃描時就會給標記的對象賦值,使用這些對象之前,先要在註釋1處做注入操作,具體的賦值操作就在這裏進行。


局部單例

dagger有個隱藏很深的“坑”,就是局部單例技術,接下來就用dagger提供的@Singleton來重現這個"坑"

module用@Singleton標註後的樣子

@Singleton
@Module
public class MainModule {

    @Singleton
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }

    @Singleton
    @Provides
    public Student provideStudent(Teacher teacher) {
        return new Student(teacher);
    }
}

component用@Singleton標註後的樣子

@Singleton
@Component(modules = {MainModule.class})
public interface MainComponent {

    void injectActivity(MainActivity activity);
    void injectActivity(SecondActivity activity);

}

然後在MainActivity和SecondActivity中做注入操作

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);

        System.out.println("main-teacher--->>>" + teacher.hashCode());
        System.out.println("main-student---->>>" + student.hashCode());
    }
}

public class SecondActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        DaggerMainComponent.create().injectActivity(this);

        System.out.println("second-teacher--->>>" + teacher.hashCode());
        System.out.println("second-student---->>>" + student.hashCode());
    }
}

接着運行代碼看下效果

main-teacher--->>>145054534
main-student---->>>240869639

second-teacher--->>>240548302
second-student---->>>121819375

我們可以看到兩個Activity的hashCode是不一樣的,這時候單例的效果就失效了,爲什麼單例會失效?只有從它注入處的源碼來看下效果。

private void initialize(final Builder builder) {

  this.provideTeacherProvider =
      DoubleCheck.provider(MainModule_ProvideTeacherFactory.create(builder.mainModule));

  this.provideStudentProvider =
      DoubleCheck.provider(
          MainModule_ProvideStudentFactory.create(builder.mainModule, provideTeacherProvider));

  this.mainActivityMembersInjector =
      MainActivity_MembersInjector.create(provideTeacherProvider, provideStudentProvider);

  this.secondActivityMembersInjector =
      SecondActivity_MembersInjector.create(provideTeacherProvider, provideStudentProvider);
}

dagger注入的時候都會調用這個initialize方法,這個方法裏面加了單例@Singleton之後多了DoubleCheck.provider這部分代碼,看下它是怎麼實現的

public static <T> Provider<T> provider(Provider<T> delegate) {
  checkNotNull(delegate);
  //1
  if (delegate instanceof DoubleCheck) {
    /* This should be a rare case, but if we have a scoped @Bind that delegates to a scoped
     * binding, we shouldn't cache the value again. */
    return delegate;
  }
  //2
  return new DoubleCheck<T>(delegate);
}

第一次進來註釋1處的if肯定不成立,走註釋2處的代碼

private DoubleCheck(Provider<T> provider) {
  assert provider != null;
  this.provider = provider;
}

@SuppressWarnings("unchecked") 
@Override
public T get() {
  Object result = instance;
  if (result == UNINITIALIZED) {
    synchronized (this) {
      result = instance;
      if (result == UNINITIALIZED) {
        instance = result = provider.get();
        provider = null;
      }
    }
  }
  return (T) result;
}

主要看get部分代碼,裏面有個雙重檢查,第一次肯定是走到if裏面從provider中獲取Object對象,然後賦值給instance,下次進來時候result先從instance獲取緩存的值,if就不成立了,此時直接返回剛剛的緩存值。由於我們在兩個Activity中的DaggerMainComponent是不一樣的,這時候就實現不了單例了。


解決方案

關於上面的一個解決方案就是將DaggerMainComponent的創建提升到Application中,而注入只需要在對應的代碼中使用就行了。

public class MyApp extends Application {

    private static MainComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        component = DaggerMainComponent.create();
    }

    public static MainComponent getComponent() {
        return component;
    }
}

注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;

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

        MyApp.getComponent().injectActivity(this);
    }
}

運行效果

main-teacher--->>>145054534
main-student---->>>240869639

second-teacher--->>>145054534
second-student---->>>240869639

此時注入兩個Activty的Teacher和Student都是同一個單例對象

對於單例一般不建議使用Singleton,而是自定義Scope,使用Singleton需要遵守框架裏面的很多規則,而使用自定義Scope只需要遵守兩條規則:1.多個組件之間的scope不能相同,2.沒有scope的不能依賴有scope的組件

後面就使用自定義Scope來實現單例。


多個Component依賴

可能存在這樣一個問題就是有兩個或者多個Component想在同一個Activity中注入,此時該怎麼實現了?還是直接上例子

此時MainComponent想要依賴一個PresenterComponent,那麼就可以使用@Component的dependencies來依賴PresenterComponent

@Component(modules = {MainModule.class}, dependencies = {PresenterComponent.class})

PresenterComponent部分代碼改成這樣

@Component(modules = PresenterModule.class)
public interface PresenterComponent {

    Presenter providePresenter();
}

然後MyApp中component創建代碼改成這樣的

component = DaggerMainComponent.builder()
        .mainModule(new MainModule())
        .presenterComponent(DaggerPresenterComponent.create())
        .build();

presenterComponent也就是依賴的Component是必須要提供的,不然報錯

throw new IllegalStateException(
            PresenterComponent.class.getCanonicalName() + " must be set");

頁面注入

public class MainActivity extends AppCompatActivity {

    @Inject
    Teacher teacher;
    @Inject
    Student student;
    @Inject
    Presenter presenter;

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

        MyApp.getComponent().injectActivity(this);

        System.out.println("main-teacher--->>>" + teacher.hashCode());
        System.out.println("main-student---->>>" + student.hashCode());
        System.out.println("presenter---->>>" + presenter.hashCode());
    }
}

Subcomponent使用

這個註解也可以實現多個Component的組合使用,直接上代碼

//MainComponent
@AppScope
@Component(modules = {MainModule.class})
public interface MainComponent {
	//提供一個方法,表示PresenterComponent是MainComponent的子Module
    PresenterComponent buildPresenterComponent();
}

//PresenterComponent
@UserScope
//子module
@Subcomponent(modules = PresenterModule.class)
public interface PresenterComponent {
    void injectActivity(MainActivity activity);
    void injectActivity(SecondActivity activity);
}

//MyApp的Component聲明,它的類型是PresenterComponent
component = DaggerMainComponent
        .builder()
        .mainModule(new MainModule())
        .build()
        .buildPresenterComponent();

不同的上面已經標出來了,後面的頁面注入是一樣的,就不貼了。

其實一般不推薦使用Subcomponent註解,可以看這裏

@Override
public PresenterComponent buildPresenterComponent() {
  return new PresenterComponentImpl();
}
private PresenterComponentImpl() {
  this.presenterModule = new PresenterModule();
  initialize();
}

PresenterModule的創建是通過無參構造方法創建的,這就表示不能通過外部傳入參數,也就帶來了一定的限制性,所以一般推薦第一種方式,來實現多個Component注入。


動態url獲取

有時候,網絡請求的url不是固定的,這時候,就可以通過接下來方案解決

public class MyApp extends Application {

    private static MainComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        //測試不同url
        String url1 = "www.baidu.com";
        String url2 = "www.123.com";
        List list = new ArrayList();
        list.add(url1);
        list.add(url2);

        component = DaggerMainComponent.builder()
                .mainModule(new MainModule(list))
                .presenterComponent(DaggerPresenterComponent.create())
                .build();
    }

    public static MainComponent getComponent() {
        return component;
    }
}

網絡請求需要的url,通過構造方法傳入MainModule,看MainModule的定義

@AppScope
@Module
public class MainModule {

    private List<String> urls;

    public MainModule(List<String> urls) {
        this.urls = urls;
    }

    @AppScope
    @Provides
    @Named("url1")
    public String provideUrl1() {
        return urls.get(0);
    }

    @AppScope
    @Provides
    @Named("url2")
    public String provideUrl2() {
        return urls.get(1);
    }

    @AppScope
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }

    @AppScope
    @Provides
    public Student provideStudent(Teacher teacher) {
        return new Student(teacher);
    }

}

由於要提供的url都是String類型,此時可以通過@Named來標記,看待注入類中怎麼注入的

@Inject
@Named("url1")
String url1;
@Inject
@Named("url2")
String url2;

也是通過@Named來獲取需要的url,這樣就可以通過@Name來注入不同的url。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章