注:本文是小生自學時做(fanyi)的筆記,可能含有少兒不宜的誤導性內容,學習Dagger請移步原博。
原博地址:http://frogermcs.github.io/
Dagger2使用:
需求:主頁上有一個TextView,在TextView中顯示人名和年齡
使用Dagger2做依賴注入,需要創建兩樣東西:
1.Component接口: 想象成是注射器的針管,單詞的含義貌似跟作用完全沒關系啊,待我查查看。Component類使用 @Component(modules = {xxx.class}) 注解。
UserComponent:
@Component(modules = {UserModule.class})
public interface UserComponent {
void inject(MainActivity mainActivity);
}
2.Module: 想象是注射器的針筒。注意,Module不是提供的依賴(注射液),而是提供依賴的組件(針筒)。Module類使用 @Module 注解,提供依賴的方法用 @Provides 標識,方法名以 provide 開頭。
@Module
public class UserModule {
UserModule() {}
@Provides
UserModel provideUsers() {
UserModel user = new UserModel();
user.setName("lala");
user.setAge(18);
return user;
}
}
以上就是我們為了使用Dagger需要額外添加的東西,針筒加針管,還缺少:1.注射器的活塞。2.需要注射的液體。3.接受注射的病人。
1.注射器的活塞
也就是用來真正實施注入的東西。Dagger會根據注解生成一個實現了MembersInjector接口的類。MembersInjector直譯過來就是成員注射器,我們把它看成是注射器的活塞。此接口只有一個方法,就是 void injectMembers(T instance)
.調用生成類的 injectMembers 方法即相當于推動活塞的動作,真正實施注射。對于這個東西我們不需要添加額外的代碼,它由Dagger在預編譯時期自動生成。
2.需要注射的液體
這就是我們需要注入的依賴了。它可以是任何可以實例化的東西。
UserModel:
//UserModel沒有添加任何額外的東西
public class UserModel {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.接受注射的病人
也就是接收依賴的需求者。我們用 @inject 注解來標識。
MainActivity:
//需要注入的依賴使用 @Inject 標記
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Inject
UserModel user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text_view);
//進行真正的注入操作
DaggerUserComponent.builder()
.userModule(new UserModule())
.build()
.inject(this);
textView.setText("Name:" + user.getName() + "::Age:" + user.getAge());
}
}
效果:
咦,這么看起來整個實現還是比較麻煩的。直接在MainActivity new一個user出來不就完了,為什么要用Dagger繞這么大一圈。
先看看什么是依賴。
在MainActivity需要一個UserModel的實例,此時,我們稱MainActivity對UserModel有依賴,如果我們要改UserModel,比如在構造函數里就傳進去姓名和年齡,而不通過set方法。此時,我們就要更改MainActivity中的代碼,這里的MainActivity和UserModel是高耦合的。
而使用Dagger后,即使UserModel有了改變,我們只要改一下Module里的provide方法即可,而不用動MainActivity的代碼,此時,MainActivity和UserModel是低耦合的。真真是在平坦的路面上曲折前行啊...
上面是使用Dagger做一個最基本的依賴注入。下面來詳解一下各個部分。
- @Inject 注解
構造函數注入
public class LoginActivityPresenter {
private LoginActivity loginActivity;
private UserDataStore userDataStore;
private UserManager userManager;
@Inject
public LoginActivityPresenter(LoginActivity loginActivity,
UserDataStore userDataStore,
UserManager userManager) {
this.loginActivity = loginActivity;
this.userDataStore = userDataStore;
this.userManager = userManager;
}
}
直接在構造函數上加 @Inject 注解,所有的參數都從Module里獲取。同時,這個類也可以被用于注入。如下:
public class LoginActivity extends BaseActivity {
@Inject
LoginActivityPresenter presenter;
//...
}
限制是一個類中只能有一個構造函數有 @Inject 注解。
成員注入
public class SplashActivity extends AppCompatActivity {
@Inject
LoginActivityPresenter presenter;
@Inject
AnalyticsManager analyticsManager;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
getAppComponent().inject(this);
}
}
也就是上面我們使用的注入方式,這種方式比較好理解,不過需要手動調用注入才能完成注入過程。注入前該成員一直都是null。
限制是被注入成員不能是private的,因為Dagger生成的代碼是這么注入的:
splashActivity.analyticsManager = analyticsManagerProvider.get();
這個后面分析生成代碼的時候會談到。
方法注入
public class LoginActivityPresenter {
private LoginActivity loginActivity;
@Inject
public LoginActivityPresenter(LoginActivity loginActivity) {
this.loginActivity = loginActivity;
}
@Inject
public void enableWatches(Watches watches) {
watches.register(this); //Watches instance required fully constructed LoginActivityPresenter
}
}
被注解的方法的所有參數都由Module提供。當被注入方需要當前類的實例(this)時可以用這種方式把自己傳給被注入方。注入方法會在構造函數調用完畢后立馬被調用。
@Module 注解
用來標注那些提供依賴的類,Dagger通過這個注解來找到提供依賴的類。
@Module
public class GithubApiModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
return okHttpClient;
}
@Provides
@Singleton
RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient))
.setEndpoint(application.getString(R.string.endpoint));
return builder.build();
}
}
@Provides 注解
標注返回依賴的方法。
@Module
public class GithubApiModule {
//...
@Provides //This annotation means that method below provides dependency
@Singleton
RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient))
.setEndpoint(application.getString(R.string.endpoint));
return builder.build();
}
}
@Component 注解
標注Module與Inject之間的橋梁接口。在這個接口中定義從哪個module獲取依賴,也用于定義那些Module可以用于注入,以及可以注入對象到哪里。
這個例子表示:當前Component使用兩個Modules,可以注入依賴到GithubClientApplication,可以使三個依賴公開可見:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
void inject(GithubClientApplication githubClientApplication);
Application getApplication();
AnalyticsManager getAnalyticsManager();
UserManager getUserManager();
}
Component也可以依賴別的Component,也可以有自己的生命周期(詳見下面的Scope)
@ActivityScope
@Component(
modules = SplashActivityModule.class,
dependencies = AppComponent.class
)
public interface SplashActivityComponent {
SplashActivity inject(SplashActivity splashActivity);
SplashActivityPresenter presenter();
}
@Scope 注解
@Scope
public @interface ActivityScope {
}
用于定義自定義作用域注解。有點類似于單例,不同的是,單例的作用域是整個application,而自定義作用域可以自定義(特么廢話么)。下面還會詳解Scope,這里按下不表。先說好,Scope是拿來干這個的:保持對象的單一實例。
然后附贈一些不咋用,不太重要的東西:
@MapKey 注解
用于定義依賴的集合。
定義:
@MapKey(unwrapValue = true)
@interface TestKey {
String value();
}
提供依賴:
@Provides(type = Type.MAP)
@TestKey("foo")
String provideFooKey() {
return "foo value";
}
@Provides(type = Type.MAP)
@TestKey("bar")
String provideBarKey() {
return "bar value";
}
使用:
@Inject
Map<String, String> map;
map.toString() // => ?{foo=foo value, bar=bar value}”
@Qualifier 注解
給高階程序猿(強迫癥)準備的,用于為繼承自同一個接口的依賴打 TAG 來區分他們。比如有兩個Adapter繼承自同一個Adapter接口,你還想讓代碼看上去整齊有型,就這么干。
命名依賴:
@Provides
@Singleton
@GithubRestAdapter //Qualifier
RestAdapter provideRestAdapter() {
return new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build();
}
@Provides
@Singleton
@FacebookRestAdapter //Qualifier
RestAdapter provideRestAdapter() {
return new RestAdapter.Builder()
.setEndpoint("https://api.facebook.com")
.build();
}
注入依賴:
@Inject
@GithubRestAdapter
RestAdapter githubRestAdapter;
@Inject
@FacebookRestAdapter
RestAdapter facebookRestAdapter;
@Singleton 注解(Java自帶)
Dagger不單可以注入實例,還可以注入單例。當年老大要咱們做單元測試,結果因為項目里各種單例用得太亂導致大量代碼處于不可測的狀態。一開始注意到Dagger也是因為有了Dagger,就不需要寫那種很難Mock對象的單例了。
我們更改一下一開始的示例的界面,加一個TextView,MainActivity如下:
public class MainActivity extends AppCompatActivity {
private TextView textView1;
private TextView textView2;
@Inject
UserModel user1;
@Inject
UserModel user2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = (TextView) findViewById(R.id.text_view_1);
textView2 = (TextView) findViewById(R.id.text_view_2);
DaggerUserComponent.builder()
.userModule(new UserModule())
.build()
.inject(this);
user2.setName("Stark");
user2.setAge(10);
textView1.setText("Name:" + user1.getName() + "::Age:" + user1.getAge());
textView2.setText("Name:" + user2.getName() + "::Age:" + user2.getAge());
}
}
可以看到這里我注入了兩個UserModel的實例,在注入后重新設置user2的name和Age,然后顯示在兩個TextView上。結果如圖:
可以看到,上面的user1還是我在Module中創建的UserModel實例,user2是另一個獨立的對象,所以它們之間互不干擾。
好,下面我們改動一丟丟Component和Module的代碼,如下:
@Component(modules = {UserModule.class})
@Singleton
public interface UserComponent {
void inject(MainActivity mainActivity);
}
@Module
public class UserModule {
UserModule() {}
@Provides
@Singleton
UserModel provideUsers() {
UserModel user = new UserModel();
user.setName("lala");
user.setAge(18);
return user;
}
}
為Component加了 @Singleton 的類注解,為Module創建相應實例的方法加了同樣的 @Singleton 注解,再運行一下看看。
恩,就是這么簡單,我只更改了user2的數據,結果user1也更改了,因為它們指向同一個對象。就這樣,成功使用Dagger注入了單例。且這個單例的代碼是可測的,因為它的實例很容易Mock。
Scope 詳解
Scope,直譯過來是范圍、圈子的意思。就像前面提到的,在Dagger中,Scope用來保證在指定作用域中,拿到的依賴對象是唯一的(指定范圍內是單例)。比如,我有一個登錄系統,用戶登錄后,一直到登出前,拿到的UserModel就應該是單例。此時,我可以自定義一個UserScope的作用域,即實現一個 @UserScope 注解,用此注解標識的Component在指定的范圍內(從用戶登錄到用戶登出)注入的一定是同一個實例。有沒有很爽的感覺。。。
Dagger是沒有默認自帶的各種Scope的,什么 @ActivityScope 啊,@ApplicationScope 啊統統沒有,只有Java自帶的一個@Singleton,也就是上面講到的那個,它與Apllication處于同一作用域,從App開啟到結束只有一個實例。那么下面我們來看看如何自定義一個 Scope 注解。
Scope的實現在Dagger2里頭就是對Component做正確的配置工作。有兩種方式實現自定義Scope,使用 @Subcomponent 注解 或 使用 Components 依賴。這兩種實現方式最終出來的結果是不同的,使用 @Subcomponent 注解的話,子域可以獲取到父域的所有依賴;而如果使用 Components 依賴,則子域只能獲取到父域通過Component接口暴露出來的依賴。
先來看看使用 @Subcomponent 注解的方式實現:
@Singleton
@Component(
modules = {
AppModule.class,
GithubApiModule.class
}
)
public interface AppComponent {
UserComponent plus(UserModule userModule);
SplashActivityComponent plus(SplashActivityModule splashActivityModule);
}
可以看到,在AppComponent接口中有兩個plus方法,這兩個方法的意思是,我們可以從APPComponent中創建兩個子Component: UserComponent 和 SplashActivityComponent. 因為它們是AppComponent的子Component,所以都可以獲取到AppModule和GithubApiModule產生的實例。
規則:返回類型是子Component類型,方法名任性著來,參數是子Component所需的就行。
可以看到,UserComponent需要另一個Module,該Module被當做plus方法的參數傳入。這樣,我們我們用新Module產生的額外對象拓展了AppComponent可注入的依賴表。UserComponent如下:
@UserScope
@Subcomponent(
modules = {
UserModule.class
}
)
public interface UserComponent {
RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
}
@UserScope 注解肯定是自定義的咯:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
在UserComponent中,可以創建另外兩個子Component: RepositoriesListActivityComponent 和 RepositoryDetailsActivityComponent.
重要的東西在這兒呢。所有從UserComponent拿到的從AppComponent繼承下來的實例都是單例(范圍是Application)。而那些UserModule自己產生的實例都是“本地單例”,其周期就是UserComponent存在的周期。
所以,每次我們調用UserComponent userComponent = appComponent.plus(new UserComponent(user));
時,從userComponent拿到的對象都是不同的實例。
對于這個我們自定義的UserScope,它的生命周期當然也得咱們自己維護,要維護它的初始化和銷毀。
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
當我們從網絡獲取到User對象時,調用createUserComponent方法,UserScope從此時開始。當RepositoriesListActivity finish時,調用releaseUserComponent終結UserScope。
到此為止,對于Dagger的使用已經學習地差不多了。但是只知道怎么用怎么能滿足咱們欲求不滿的內心呢?所以下一篇要賞析一下Dagger自動生成的代碼,來看一下整個注入流程是如何走通的。