Dagger是一個快速的依賴注入框架,供Android和Java開發使用。以前由Square維護,現在轉交給Google,Github鏈接為:https://github.com/google/dagger。
依賴注入
Dagger為依賴注入而生。什么是依賴?什么是注入?為什么要使用依賴注入?這是我們學習Dagger之前必須了解的。依賴就是一個類中要使用其他的類來完成某些工作,這樣一個類就依賴了另外一個類。比如在MainActivity中必須使用一個User類的對象,那么MainActivity就依賴了User類。
public class MainActivity extends AppCompatActivity {
User mUser;//MainActivity依賴User類
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUser = new User();//初始化mUser
setContentView(R.layout.activity_main);
}
}
一般我們直接在MainActivity中直接初始化mUser對象,但如果有天User類的構造方法中增加了一個參數,比如用戶名,那么所有調用User的無參構造方法的地方全部要修改,這顯然不是我們希望看到的。于是我們思考可不可以提供一個User的工廠類或者容器類專門負責User對象的創建,這樣User對象的創建就不會跟MainActivity發生耦合,不管User的構造方法如何變,都不會影響到MainActivity。那么代碼可能是這樣:
public class MainActivity extends AppCompatActivity {
User mUser;//MainActivity依賴User類
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUser = UserFactory.getUser()//通過工廠類注入mUser
setContentView(R.layout.activity_main);
}
}
那么mUser對象就不是在MainActivity創建,而是由UserFactory創建,設置給mUser對象,這就可以稱之為mUser被注入了,這樣MainActivity就不需要關心User對象是怎樣創建出來,對User對象創建的修改就不會影響到MainActivity里面的代碼,這就是依賴注入的好處的。這就好比我們打針,人體依賴藥液來治療疾病,但藥液并不是人體自己生產的,而是醫藥公司生產然后通過注射器注入到人體,而我們人體根本就無需關心藥液是如何被制造出來的。
Dagger的作用就是通過注解的方式,幫我們自動生成創建對象的工廠類。
Dagger 2使用
1. 添加依賴
可通過鏈接https://github.com/google/dagger/releases查找最新版本
dependencies {
compile 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
}
2. 添加注解@Inject
分別在MainActivity的User成員變量和User的構造方法上添加@Inject依賴。
public class MainActivity extends AppCompatActivity {
//成員變量上添加注解
@Inject
User mUser;
}
public class User {
String name;
//構造方法上添加注解
@Inject
public User() {
this.name = "Leon";
}
}
3. 創建Component類
事實上完成第二步后,MainActivity中成員mUser并沒有調用構造方法完成初始化,還需要一個類來完成注入,這就是Component類。
我們創建一個MainComponent接口,提供一個inject方法,其參數為將被注入的類MainActivity
public interface MainComponent {
void inject(MainActivity activity);
}
然后,我們在Android Studio下選擇菜單build->Make Project,這時候,會在app模塊下build/generated/source/apt/debug/包名/目錄下生成三個文件DaggerMainComponent,MainActivity_MembersInjector,User_Factory。DaggerMainComponent即為接口MainComponent的實現類。
4. 注入
最后,我們可以使用生成的DaggerMainComponnet完成注入,成員變量mUser將被賦值。如果是老司機,是不是會覺得這跟ButterKnife.bind(this)有異曲同工之妙呢?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入,內部會調用User的構造方法完成成員變量mUser的初始化
DaggerMainComponent.builder().build().inject(this);
//打印結果,mUser不為null
Log.d(TAG, "onCreate: " + mUser.name);
}
Module使用
假如我想在一個Activity里面注入一個TextView,如下:
public class MainActivity extends AppCompatActivity {
@Inject
TextView mTextView;
}
如果按照上述步驟,就需要找到TextView的構造方法,加上一個@Inject注解,這個顯然是無法做到的。Module就是為了解決不能夠通過注解構造方法來創建一個實例的問題。我們創建一個TextViewMoudle類,使用@Module注解。另外需要提供一個方法返回一個TextView實例,并且用@Provides注解。
@Module
public class TextViewModule {
@Provides
TextView provideTextView(Context context) {
return new TextView(context);
}
}
provideTextView方法有個Context參數,需要外界傳入到TextViewModule,這里給TextViewModule提供一個帶有Context參數的構造方法。另外還需要提供個provideContext方法返回上下文,因為Dagger框架調用provideTextView方法獲取一個TextView實例時,發現要傳一個Context類型的參數,這時候他會查找被@Provides注解并且返回值為Context類型的方法獲取一個Context實例傳入provideTextView方法。
@Module
public class TextViewModule {
//保存一個上下文成員變量
private Context mContext;
//構造方法接收一個上下文
public TextViewModule(Context context) {
this.mContext = context;
}
@Provides
TextView provideTextView(Context context) {
return new TextView(context);
}
@Provides
Context provideContext() {
return mContext;
}
}
接下來還需要在MainComponent接口上指定TextViewModule。然后點擊菜單選項build->Make Project,這時又會在build/generated/source/apt/debug/包名/目錄下生成兩個新文件TextViewModule_ProvideContextFactory和TextViewModule_ProvideTextViewFactory。
@Component(modules = TextViewModule.class)
public interface MainComponent {
void inject(MainActivity activity);
}
最后在注入時創建一個TextViewModule傳入,表示告訴Dagger框架可以從TextViewModule中獲取TextView實例。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//傳入TextViewMoudle
DaggerMainComponent.builder().textViewModule(new TextViewModule(this)).build().inject(this);
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.frame);
//mTextView已被注入,不為null
mTextView.setText(mUser.name);
frameLayout.addView(mTextView);
}