之前就聽說過 Dagger2,也曾想見識見識,但都說學習成本很高,加上沒太多閒時,後來也就不了了之了。最近得閒就來學習學習,網上看了一些博客後,跟着練習着寫了 demo ,理解了 Dagger2 的用法與設計思想,以及這種分層的結構。所以整理了自己的理解與思路寫下這篇博客。
相信認真看完這一篇文章,你也能夠清晰的瞭解 Dagger2 的用法和一些設計思想。
項目地址:https://github.com/weioule/Dagger2Field
添加依賴:
1. 項目build.gradle 中的 dependencies 節點添加 apt插件
//添加apt插件 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
2. module裏應用apt插件
//應用apt插件 apply plugin: 'com.neenbedankt.android-apt'
3. module裏的 dependencies 節點添加 dagger2 的依賴和 java註解
//引入dagger2 compile 'com.google.dagger:dagger:2.4' apt 'com.google.dagger:dagger-compiler:2.4' //java註解 provided 'org.glassfish:javax.annotation:10.0-b28'
Dagger2用法:
1. 首先,我們來新建一個 AppModule ,一定要記得加 @Module 註解,不然系統就會因爲找不到我們的 module 而報錯。
接着我們聲明兩個函數以提供 Context 與 ToastUtil。也一定要記得加上 @Provides 註解,因爲 @Provides 修飾的函數的返回值表示爲 Module 對應的 Component 中能提供的產品(供外界注入)。
getToastUtil(Context context) 中的 context,不能直接使用 成員變量 mContext,而是要在本類中提供一個 Context getContext()的 @Provides 方法,這樣在發現需要 context 的時候會調用 getContext() 來獲取,這也是爲了解耦。
因爲這是最頂級的父 Module,我們想讓它所提供的對象爲單例,所以我們要在函數上加上 @Singleton 註解,加上 @Singleton表示爲該函數的返回值爲單例模式。
@Module public class AppModule { private Context context; public AppModule(Context context) { this.context = context; } @Singleton @Provides public Context getContext() { return context; } @Singleton @Provides public ToastUtil getToastUtil(Context context) { return new ToastUtil(context); } }
2. 其次我們就新建一個 AppComponent,它是一個接口類型的 interface 。
然後給它添加註解 @Component(modules = AppModule.class) 。@Component 就是跟上面的 @module 一樣,聲明它爲我們的 Component 。括號裏面的 module 就指向我們剛剛創建的 AppModule 。
而這裏呢要的是它的 .class 形式。這個 module 呢可以指向多個 module,它可以以一個數組的形式傳進去。寫法:@Component(modules = {AppModule.class,xxModule.class , xxModule.class})。
因爲是最頂級的 Component,我們需要給下面的 Component 提供 AppModule 中的對象:Context 和 ToastUtil,所以我們就聲明兩個接口函數 getContecxt() 和 getToastUtil() 。
這兩個接口函數的作用就是:當下面的依賴需求方注入的類型爲 Context 時,需要的時候就會調用 Component 中返回值爲Context 的函數 getContecxt(),將 AppModule 中返回值類型爲 Context 的函數的返回值賦值給到依賴需求方注入的變量。
如果沒有聲明這兩個接口函數,那麼下級(可以理解爲繼承)它的 Component 就不能通過注入的形式拿到 AppModule 中的Context 和 ToastUtil 。那麼注入它下級的 Component 的 activity 或 fragment 也就拿不到了。
上面說了,要讓 AppModule 中添加 @Singleton 註解的函數的返回值爲單例,那麼還需要在對應的 Component 上添加@Singleton 註解,不然呢你拿到的將不會是單例而是兩個不同的對象。
@Singleton @Component(modules = AppModule.class) public interface AppComponent { Context getContecxt(); ToastUtil getToastUtil(); }
3. 然後我們新建一個App類,繼承Application。在onCreate函數裏與我們的AppComponent關聯:DaggerAppComponent.builder().appModule(new AppModule(this)).build()。
你直接敲 DaggerAppComponent 是不會給你提示的,因爲這會兒程序根本不知道 DaggerAppComponent 是什麼鬼,你得先編譯一下,然後 Dagger2 會通過自動生成 DaggerAppComponent 來幫你進行注入。這時候你就可以帶着系統提示敲出你要的 DaggerAppComponent 了。
因爲下級的 Component 在進行關聯的時候還需要關聯父 Component,所以我們就把關聯後的 AppComponent 對象抽取成員變量,再提供一個 get 方法給下級進行關聯時調用。
這裏需要說一下: .appModule(new AppModule(this)) 這裏的作用主要是用來傳參的,如果你不需要傳參數,那麼可以寫也可以省略(傳參的用處最後面講)。
public class App extends Application { private AppComponent mAppComponent; @Override public void onCreate() { super.onCreate(); mAppComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); } public AppComponent getAppComponent() { return mAppComponent; } }
4. 接着我們來寫第二級的 Module 和 Component 。
同樣的我們新建一個 AcvtivityModule,寫法跟上面的 Module 一樣,而這一層呢我們要給下一級和 AcvtivityComponent 提供的是 Activity。
需要說的是@PerActivity這個沒見過的註解,這個註解呢並不是Dagger2規定的註解。其實 @PerActivity 的代碼和 @Singleton 是一樣的,只是需要我們自己重新定義一下而已。表示在 @PerActivity 這個生命週期中,只包含一個這樣產品的標籤。
但嚴格來說@Singleton 和 @PerActivity並不是我們設計模式中的單例模式,而是 Dagger2 中爲了保持一個產品在一個 Scope中只有一個對象的標籤。
爲什麼要自定義 @PerActivity 呢?因爲如果使用了 dependencies(同繼承類似,可以理解爲繼承),那麼依賴的一方的 Scope 不能和 父級 相同。
也就是說,AcvtivityComponent 是繼承 AppComponent 的,那麼 AppComponent 已經使用 @Singleton 註解了,如果要是我們也要實現單例的話,則 AcvtivityComponent 和 AcvtivityModule 就不能使用同樣的註解了,因此我們需要自定義一個跟它一樣的註解。一定要注意:你的 Component 要與對應的 Module 使用相同的註解才能編譯過去,不然會報錯。
這一點確實有點麻煩。
@Module public class AcvtivityModule { private Activity activity; public AcvtivityModule(Activity activity) { this.activity = activity; } @PerActivity @Provides public Activity getActivity() { return activity; } }
5. 接着是 AcvtivityComponent,跟 AppComponent 一樣,聲明兩個接口函數以給與其關聯的 activity 或 fragment 或者下一級的Component 提供對象 Acvtivity 和 ToastUtil 。
如果這裏不重寫 ToastUtil getToastUtil() 函數,那麼下一級的就拿不到 ToastUtil 了。這一點呢就有點不像繼承了。
@PerActivity 註解就是上面說的與AcvtivityModule保持一致,而@Component裏面多了dependencies這個屬性。這個dependencies 呢就類似 extends 。
dependencies = {AppComponent.class},意爲 AcvtivityComponent 中部分對象,需要 AppComponent.class 來提供依賴。也就是說父 Component 中的方法暴露給子 Component 部分。這裏指:Acvtivity 和 ToastUtil 。
dependencies 與 module 一樣都是可以指向多個類的,寫法一樣。
@PerActivity @Component(modules = AcvtivityModule.class, dependencies = AppComponent.class) public interface ActivityComponent { Activity getActivity(); ToastUtil getToastUtil(); }
6. 然後就是BaseActivity,它與App裏面的關聯是一樣的,只是因爲有了父Component所以還要關聯AppComponent:.appComponent(((App) getApplication()).getAppComponent()) 。
同樣提供 Component 對象給下一級的關聯。
public class BaseActivity extends AppCompatActivity { private ActivityComponent mActivityComponent; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivityComponent = DaggerActivityComponent.builder() .appComponent(((App) getApplication()).getAppComponent()) .acvtivityModule(new AcvtivityModule(this)) .build(); } public ActivityComponent getActivityComponent() { return mActivityComponent; } }
7. 最後就是具體的實現層了,我們來新建一個 MainModule。
寫法跟上面的 Module 一樣,這次我們提供一個 MainFragment 需要的 UserRepostory 倉庫,裏面有一個獲取 User 的方法。User 就是我們需要的數據模型。當然了不需要倉庫的可以直接返回 User 即可。
@Module public class MainModule { @Provides public UserRepostory getUserRepostory() { return new UserRepostory(); } }
8. 接着就是 MainComponent 。@MainActivityScope 就跟上面的 @PerActivity 一樣,屬於自定義註解。
這裏的 inject 接口函數就是 與 MainActivity 關聯的方法。將 MainActivity 的實例傳進來就可以與它進行關聯。
MainFragmentComponent 呢就類似於是 MainComponent 的一個分支,用於關聯到 MainFragment 裏。這裏的 MainFragmentComponent 就相當於 MainComponent 的子組件,注意:它能擁有 MainComponent 裏所有提供的對象。也就是 MainModule 裏所有提供的對象。
@MainActivityScope @Component(modules = MainModule.class, dependencies = ActivityComponent.class) public interface MainComponent { void inject(MainActivity mainActivity); MainFragmentComponent getMainFragmentComponent(); }
9. 然後我們來看看 MainActivity 。
跟 BaseActivity 一樣寫法。與 MainComponent 進行關聯。
因爲 MainFragment 需要與 MainFragmentComponent 進行關聯。而MainFragmentComponent 是 MainComponent 裏的一個對象。所以我們一樣要把 MainComponent 抽取出來再提供給 MainFragment 。
public class MainActivity extends BaseActivity { private MainComponent mMainComponent; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getSupportFragmentManager().beginTransaction().add(R.id.main_fl, new MainFragment()).commit(); mMainComponent = DaggerMainComponent.builder() .activityComponent(getActivityComponent()) .mainModule(new MainModule()) .build(); mMainComponent.inject(this); } public MainComponent getMainComponent() { return mMainComponent; } }
10. 接着我們先來看看 MainFragmentComponent 。
這裏可以看到 @MainActivityScope 註解,它是可以跟 MainComponent 使用同樣註解,因爲它並沒有使用 dependencies 屬性。所以沒有受到限制。就像我們上面所說的: 開一個分支然後將其本身關聯給它的子組件。只是共享對象。
然後就是 @Component 變成了 @Subcomponent 。
關於 @Subcomponent 的用法,它的作用和 dependencys 相似,這裏表示 FragmentComponent 是一個子組件,那麼它的父組件是誰呢? 提供了獲取 MainFragmentComponent 的組件,如 MainComponent中的 MainFragmentComponent mainFragmentComponent(),是這樣做的關聯。
@MainActivityScope @Subcomponent public interface MainFragmentComponent { void inject(MainFragment mainFragment); }
11. 這裏我們先來看看 MainFragmentContact 。
這裏使用 @Inject 註解 Presenter 構造函數。 當請求一個 Presenter 的實例時,Dagger 會獲取所需的參數,並調用此構造函數創建 Presenter 實例。
public class MainFragmentContact { public interface MainView { void setUserName(String name); void showToast(String msg); } public static class Presenter { private UserRepostory userRepostory; private MainView mainView; private String userName; @Inject public Presenter(UserRepostory userRepostory) { this.userRepostory = userRepostory; userName = this.userRepostory.getUser().getName(); } public void setView(MainView mainView) { this.mainView = mainView; } public void showToastBtnClick() { String msg = "hello " + userName; mainView.showToast(msg); } public void setUserNameBtnClick() { this.mainView.setUserName(userName); } } }
12. 最後就是 MainFragment 。
在 MainFragment 裏呢,我們聲明瞭 MainFragmentContact.Presenter 和 ToastUtil 兩個類型的變量。並且都分別用了 @Inject 註解。
ToastUtil 呢就是上面 AppComponent 提供給 ActivityComponent 然後 ActivityComponent 再提供給 MainComponent ,然後再由MainFragment 與 MainComponent 的子組件(類似它的分支) MainFragmentComponent 進行關聯。這樣一層一層的提供了過來。
MainFragmentContact.Presenter 呢就是上面說的 Presenter 構造函數加了 @Inject 。所以在 MainFragment 使用到的時候Dagger2 就會自動生成代碼 new 一個Presenter 對象並賦值給了過來。
原理是:當 MainFragment 裏面的註解變量 mPresenter 需要用到的時候,因爲 MainFragment 和 MainFragmentComponent 關聯了,dagger2 就會把 MainFragmentComponent 擁有的 MainModule 中的 UserRepostory 實例傳過去調用構造函數創建 mPresenter 的實例並注入到 MainFragment 裏的註解變量 mPresenter 。
也就是說當Component找到以下被 @inject 的構造方法時,會發現憑藉自己無法提供 UserRepostory 參數,然後就會前往它所關聯的Module 看其能否提供參數,然後就是把Component和Module進行關聯了
其他的就是MVP模式與 Presenter 進行交互了。
public class MainFragment extends Fragment implements MainFragmentContact.MainView, View.OnClickListener { @Inject MainFragmentContact.Presenter mPresenter; @Inject ToastUtil mToastUtil; private TextView mUserName; @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).getMainComponent().getMainFragmentComponent().inject(this); } mPresenter.setView(this); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_main, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mUserName = (TextView) view.findViewById(R.id.user_name); view.findViewById(R.id.btn_toast).setOnClickListener(this); view.findViewById(R.id.btn_set_user_name).setOnClickListener(this); } @Override public void setUserName(String name) { mUserName.setText(name); } @Override public void showToast(String msg) { mToastUtil.showToast(msg); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_set_user_name: mPresenter.setUserNameBtnClick(); break; case R.id.btn_toast: mPresenter.showToastBtnClick(); break; default: break; } } }
這裏說下Dagg2的註解創建類實例的兩個方法的優先級:
對於上面的兩種獲取對象方法,一種是通過 Module 裏的函數創建返回,還有就是和上面的 Presenter 一樣 通過在構造函數上添加 @Inject 註解返回。
Component 會首先從 Module 中查找有沒有類實例,若找到就用 Module 創建類實例,並停止查找 @Inject 註解構造函數的類實例。否則纔是從 Inject 註解構造函數中查找類實例。所以創建類實例級別 Module 要高於 Inject 註解構造函數。
最後說一下上面說的 .appModule(new AppModule(this)) 傳參的用處。
我們在進行關聯的時候,module 的實例主要使用來傳參數的,不用傳遞的參數可以忽略不寫。
1. 好,我們來看看 CommonComponent 。
CommonComponent 呢是一個父組件,ParameterComponent 是它的子組件。它這裏沒有綁定 module ,我們看到接口函數 addSub 是需要傳參的,而參數就是我們需要它爲我們將 MvpView 實例傳給 dagger2 的 ParameterModule 。
因爲 ParameterComponent 是一個 Subcomponent 而不是 Component 所以它是不能直接和 Activity 或 Fragment 關聯的,而是要通過父組件獲取它的實例來關聯。
因此也就不能通過 ParameterComponent 的 ( .parameterModule(new ParameterModule(this)) ) 的形式傳遞,所以 ParameterModule 就得從這裏傳進來。
@Component() public interface CommonComponent { ParameterComponent addSub(ParameterModule parameterModule); }
2. 再看看 ParameterComponent 。跟上面的 Subcomponent 一樣。有一個與 Activity 關聯的方法。
@Subcomponent(modules = ParameterModule.class) public interface ParameterComponent { void inject(ParameterActivity parameterActivity); }
3. 我們再來看一下數據模型 Bean 。它是一個帶 @Inject 註解的類,不需要通過 module 就可以完成注入。但它是有參構造,實例化是需要傳參數的。
public class Bean { @Inject public Bean(MvpView mvpView) { } @Override public String toString() { return "我是需要MvpView實例的數據bean"; } }
4. 再來看看我們創建的 ParameterActivity 。
有一個註解變量 mBean 。如果你像之前一樣關聯後就想直接調用它,那麼你拿到的將會是 null,因爲它沒有成功被實例化。那麼爲什麼這次不能跟之前一樣自動實例化再提供過來呢?原因是因爲它在實例化的時候需要一個 MvpView 的對象,可你並沒有給過它。
那麼我們怎麼給呢?你想想,既然我們拿數據都是在 module 裏,那麼我們傳進去的數據也得傳到 module 裏了。這裏我們就通過構造方法傳進去 MvpView 實例。
這樣 dagger2 就可以從 module 裏獲取 MvpView 實例去創建 Bean 的對象並注入返回了。
public class ParameterActivity extends AppCompatActivity implements MvpView { @Inject Bean mBean; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); DaggerCommonComponent.builder().build().addSub(new ParameterModule(this)).inject(this); TextView userName = (TextView) findViewById(R.id.user_name); userName.setText(mBean.toString()); findViewById(R.id.btn_set_user_name).setVisibility(View.GONE); findViewById(R.id.btn_toast).setVisibility(View.GONE); findViewById(R.id.do_next).setVisibility(View.GONE); } }
5. 那麼我們來看看 ParameterModule 。
通過構造函數將 MvpView 實例傳進來,然後在聲明 getMvpView() 方法提供 MvpView 對象,這樣當 dagger2 在創建Bean 實例需要 MvpView 對象的時候就可以從這裏獲取了。
@Module public class ParameterModule { private MvpView mMvpView; public ParameterModule(MvpView mvpView) { mMvpView = mvpView; } @Provides public MvpView getMvpView() { return mMvpView; } }
ok,就是這樣,相信認真看完這篇文章。你也能夠清晰的瞭解Dagger2的基本使用與一些設計思想了。認真起來其實也沒那麼難。希望能幫助想要學習Dagger2的小夥伴。
如有修正的地方,請多多賜教!