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組裝完成,依賴成功注入。

 

       等等,四大基本組件怎麼只講了兩個?

       因為兩個就已經能基本完成一個簡單的依賴注入了,下一章接着講工廠類的另一種構成方法。