Dagger2 探索記1——四大基本組件(一)
- 2019 年 10 月 3 日
- 筆記
和很多自主學習的人不一樣,我接觸Dagger 2 框架的原因是剛進公司的時候導師給安排的學習任務,學習方式是組內培訓。
聽到這個消息的我,以為是部門的人輪流給我講課。
後來導師跟我說,組內培訓的意思是,我先自己好好學這個框架,然後給組內的所有人搞個培訓。
沒辦法,在網上看了很多相關博客,浪費了不少時間,終於還是學有所得,也記錄一下我最近的學習進展。
就不多講什麼歷史了,你能看到我這篇博客,想來歷史什麼的科普你都已經被塞到吐了,還是擼代碼學得快。
一 環境配置
在module的build.gradle中添加代碼:
dependencies { ...... //dagger2 implementation 'com.google.dagger:dagger:2.7' annotationProcessor 'com.google.dagger:dagger-compiler:2.7' //butterknife implementation 'com.jakewharton:butterknife:10.0.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0' }
為了後面書寫代碼簡便,將ButterKnife一起配置了。
在Project build.gradle中添加如下代碼(這段代碼是配置ButterKnife使用的,不配置可能就會報TimeOut的錯誤):
allprojects { repositories { google() maven { url "https://oss.sonatype.org/content/repositories/snapshots" } jcenter() } }
到這裡配置就完成了,ButterKnife可以為我們省下很多代碼,因為ButterKnife和Dagger的廠家是一樣的,很多地方也是共通的,就不多解釋原理,拿來就用。
二 源碼分析
我們先創建一個簡單的Tools類:
public class Tools { @Inject public Tools(){} }
不需要任何屬性,只用@Inject標記一個空的構造方法,然後我們使用Ctrl+F9進行編譯。
Dagger2 是通過標記來公式化編寫代碼,減輕我們重複編寫代碼的勞動。
這裡提到個詞“公式化”,其實很好理解。
比如,你要給你給你喜歡的女孩子表白,先跟你寢室兄弟們排練一百遍表白流程。到了女寢樓下,你清一清嗓子,就有人給你打好了燈光,你叫完女孩的名字,身後就有人放飛了粉紅的愛心氣球,你剛說完那羞羞的三個字,周圍就全是大聲呼喊“答應他、答應他……”
女孩十分感動,然後拒絕了你……
咳咳,講偏了!
總之,公式化的東西就是這樣,固定好的流程,你只需要打個特殊的手勢,別人就知道該怎麼做。為什麼?因為流程都是公式化的,固定的,已經跑過千百次了,大家閉着眼睛都能敲出來。在程序里也是這樣,不要重複造輪子。你只需要打個標記,就像上面提到的@Inject,剩下的繁瑣的任務交給喜歡重複勞動的計算機。
那麼,看到@Inject這個標記,計算機又做了什麼呢?
編譯過後會發現build中多了一個文件(你要問我路徑在哪?後面我帶你找,聽我的,慢慢來!),叫做Tools_Factory類,內容如下:
public enum Tools_Factory implements Factory<Tools> { INSTANCE; @Override public Tools get() { return new Tools(); } public static Factory<Tools> create() { return INSTANCE; } }
這就是AS自動生成的代碼。一個工廠類,類如其名,就是個工廠。通過create()方法進行創建,通過get()方法獲得new Tools對象。
下面我就貼四大基本組件的代碼了,先把框架搭起來:
首先是MainActicity:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this);//ButterKnife綁定Activity } @OnClick({R.id.turn_firstactivity}) public void onViewClicked(View view) { switch (view.getId()){ case R.id.turn_firstactivity: startActivity(new Intent(this, FirstActivity.class)); break; } } }
就簡簡單單一個跳轉,跳轉到我們接下來要使用的FirstActivity中。
貼一下activity_main這個layout,平時我看博客最討厭別人不把代碼貼完,我當然不會犯這個錯。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/turn_firstactivity" android:text="四大基本組件"/> </LinearLayout>
接下來是FirstActivity,也就是我們第一個例子的主要Activitypublic class FirstActivity extends AppCompatActivity
@BindView(R.id.fist_text_1) TextView text1; @BindView(R.id.fist_text_2) TextView text2; @Inject Tools tool1; @Inject Tools tool2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first); ButterKnife.bind(this);//綁定View和Activity
} }
代碼很好理解,用ButterKnife來直接綁定View和Activity,節省了很多代碼,而節省的代碼也是前面提到“公式化”的體現。
以及對應的layout的代碼。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/fist_text_1" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/fist_text_2" /> </LinearLayout>
我們再新建接口FirstComponent:
@Component public interface FirstComponent { void inject(FirstActivity activity); }
這個方法名inject可以隨意改寫,但是為了方便後來人閱讀,建議你還是按照約定俗成的規矩來寫,讓後來接手你代碼的人少罵你兩句。
到這一步,我們需要先Ctrl+F9編譯一下了。因為有個文件需要Dagger2自動生成後,我們才能使用。
接下來在FirstActivity中添上代碼:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.first); ButterKnife.bind(this); DaggerFirstComponent.builder() .build() .inject(this); text1.setText(tool1.toString()); text2.setText(tool2.toString()); }
這個多的文件就是DaggerFirstComponent.java,可以Ctrl+鼠標左鍵點擊DaggerFirstComponent跳轉到這個文件查看。
前面找不到自動生成文件在哪的,可以再點擊一下這個像準星一樣的圖標,自動定位到對應的文件夾。
這個文件就是Dagger+Component的名字組合出來的,實際上也是繼承了Component接口對其中的方法進行重寫。
DaggerFirstComponent代碼如下:
public final class DaggerFirstComponent implements FirstComponent { private MembersInjector<FirstActivity> firstActivityMembersInjector; private DaggerFirstComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static FirstComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.firstActivityMembersInjector = FirstActivity_MembersInjector.create(Tools_Factory.create()); } @Override public void inject(FirstActivity activity) { firstActivityMembersInjector.injectMembers(activity); } public static final class Builder { private Builder() {} public FirstComponent build() { return new DaggerFirstComponent(this); } } }
很明顯的Builder建造者模式。
再反觀一下Activity中對DaggerFirstComponent的調用
DaggerFirstComponent.builder() .build() .inject(this);
首先在裏面先初始化一個Builder類,然後調用Builder類中的build()方法對DaggerFirstComponent進行構造。
DaggerFirstComponent構造函數中,有個initialize的初始化函數,給DaggerFirstComponent持有的一個內部成員firstActivityMembersInjector賦值。
注意這行代碼:
this.firstActivityMembersInjector = FirstActivity_MembersInjector.create(Tools_Factory.create());
這又涉及到一個關鍵的文件,記住後綴MembersInjector就行了。
可能有的小夥伴就要問,為啥我不一次性把所有的自動生成文件都列出來?
按照邏輯一步步來,這樣比較好理解。
這也是最後一個了:一個工廠類Factory、一個接口實現類DaggerComponent、加上組裝類MemberInjector(如果你看見第四個,可能是ButterKnife生成的,有興趣可以看看)
為啥要把MemberInject叫做組裝類,聽我慢慢講來。
MemberInject.java源代碼太長了,先只看create()
private final Provider<Tools> tool1AndTool2Provider; public FirstActivity_MembersInjector(Provider<Tools> tool1AndTool2Provider) { assert tool1AndTool2Provider != null; this.tool1AndTool2Provider = tool1AndTool2Provider; } public static MembersInjector<FirstActivity> create(Provider<Tools> tool1AndTool2Provider) { return new FirstActivity_MembersInjector(tool1AndTool2Provider); }
大概意思就是先傳進來一個工廠類Factory.create(),前面講過,工廠類就倆方法:create()和get()。然後把create好的工廠類賦給MemberInject類里的成員變量,大概就是把工廠類構建好,保存起來備用。
到這裡我們就已經build()跑完了。捋一捋現在都幹了啥,DaggerFirstComponent這個類調用了Factory.create(),然後把它給了MemberInject,自己保存了一個MemberInject的實例。到了這步,其實返回的還是個Component,然後繼續執行Component里已有的方法而已。
注意這點,到這裡返回的是個Component!劃重點,後面要考的。
如果你看代碼足夠認真可能會注意到,DaggerFirstComponent中有個create()方法,對應的就是builder().build()。意味着依賴注入的代碼可以簡寫成下面這種形式:
DaggerFirstComponent.create().inject(this);
沒錯,確實可以這樣,但強烈不建議這樣寫。劃重點,強烈不建議!
因為不符合開閉原則,原因後面會講到。
DaggerFirstComponent類繼承了FirstComponent接口,重寫了inject方法:
@Override public void inject(FirstActivity activity) { firstActivityMembersInjector.injectMembers(activity); }
可以看到Inject方法只是給MemberInject的實例傳了個Activity。
MemberInject都被餵了哪些東西?
工廠類create好了送進去,Activity也給它了。
為啥叫它組裝類!看代碼:
instance.tool1 = tool1Provider.get();
instance是Activity的實例,通過Activity找到之前聲明的依賴注入實例tools,然後在工廠類實例tool1Provider中get()一個新的對象賦值給它。
東西都是DaggerFirstComponent給它準備好的,他只負責組裝。
到這裡,整個依賴注入全部完成。
運行結果如下:
注入兩個不同的Tools。
回顧一下,其實邏輯很簡單。
1、在需要注入依賴的Activity中,用@Inject標記所注入的依賴類的實例tools
2、在所需呀注入的類Tools的構造函數Tools()上標記@Inject,編譯會根據此生成Factory工廠類
3、在接口FirstComponent上標記@Component,並寫入Inject方法
4、編譯生成DaggerFirstComponent.java
5、調用DaggerFirstComponent方法將工廠類的實例和Activity的實例都交給MemberInjecter
6、MemberInjecter組裝完成,依賴成功注入。
等等,四大基本組件怎麼只講了兩個?
因為兩個就已經能基本完成一個簡單的依賴注入了,下一章接着講工廠類的另一種構成方法。