Dagger2的使用示例

之前就聽說過 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的小夥伴。

如有修正的地方,請多多賜教!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章