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。