架構進階,Dagger2的原理及使用詳解

目錄

  • 一:Dagger2是什麼?
  • 二:爲什麼要有Dagger2
  • 三:Dagger2如何使用
  1. 基本的概念
  2. 如何使用Dagger2
  3. 高級用法

(1)構造方法需要其他參數時候
(2) 模塊之間的依賴關係
(3) @Named註解使用
(4) @Singleton註解
(5)自定義Scoped
(6)Subcomponent
(7)lazy 和 Provider

  • 四: MVP + Dagger2
Ps:文末有架構師進階資料和麪試題資料

一:Dagger2是什麼?

是一個依賴注入框架,butterknife也是一個依賴注入框架。不過butterknife,最多叫奶油刀,Dagger2被叫做利器啊,他的主要作用,就是對象的管理,其目的是爲了降低程序耦合。

二:爲什麼要有Dagger2

下面我就手寫了

public class A {
 public void eat() {
 System.out.print("吃飯了");
 }
}

使用的時候我們就要

A a = new A();
a.eat();

如果現在改了,早A的構造方法中必須傳入B對象

 public class A {
 private B b;
 public A(B b) {
 this.b = b;
 }
 public void eat() {
 System.out.print("吃飯了");
 }
}

那麼使用的時候

A a = new A(new B());
a.eat();

可能就有人說了,不就加一個對象麼,這裏只是我舉的一個很簡單的例子,看的感覺很簡單,但是在實際開發中,如果現在改了一個這個構造方法。是不是意味着,整個項目中的都的改,一不小心, 就是BUG 啊

三:Dagger2如何使用

1. 基本的概念

上來給你說,怎麼玩,肯定懵逼,這裏我簡單說一下幾個概念,想有個認知,在往下看,會好很多,Dagger 是通過@Inject使用具體的某個對象,這個對象呢,是由@Provides註解提供,但是呢,這個@Provides只能在固定的模塊中,也就是@Module註解,我們查找的時候,不是直接去找模塊,而是去找@Component

我們反向推導,當我們使用

@Inject
A a

想要獲取a對象的示例的時候,Dagger2 會先去找,當前Activity或者Fragment所連接的橋樑,例如上圖中,連接的只有一個橋樑,實際上可以有多個,這個橋樑,會去尋找他所依賴的模塊,如圖中,依賴了模塊A,和模塊B,然後在模塊中,會去尋找@Providers註解,去尋找A的實例化對象。

2. 如何使用Dagger2

(1) 引入依賴庫

Dagger2官網

compile 'com.google.dagger:dagger:2.11'
 annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
(2) 創建Moudule
//第一步 添加@Module 註解
@Module
public class MainModule {
}
(3)創建具體的示例
//第一步 添加@Module 註解
@Module
public class MainModule {
 //第二步 使用Provider 註解 實例化對象
 @Provides
 A providerA() {
 return new A();
 }
}
(4)創建一個Component
//第一步 添加@Component
//第二步 添加module
@Component(modules = {MainModule.class})
public interface MainComponent {
 //第三步 寫一個方法 綁定Activity /Fragment
 void inject(MainActivity activity);
}
(5)Rebuild Project

然後AS 會自動幫我們生成一個

開頭都是以Dagger開始的

(6)將Component與Activity/Fragment綁定關係
package com.allens.daggerdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.allens.daggerdemo.Bean.A;
import com.allens.daggerdemo.component.DaggerMainConponent;
import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {
 /***
 * 第二步 使用Inject 註解,獲取到A 對象的實例
 */
 @Inject
 A a;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 /***
 * 第一步 添加依賴關係
 */
 //第一種方式
 DaggerMainConponent.create().inject(this);

 //第二種方式
 DaggerMainConponent.builder().build().inject(this);

 /***
 * 第三步 調用A 對象的方法
 */
 a.eat();
 }
}

肯定有小夥伴說了,爲了拿到一個對象,這麼大個彎,太麻煩了。別急慢慢看,路要一步一步走嘛

3. 高級用法

(1)構造方法需要其他參數時候

怎麼說呢,就和最來時的意思一樣,

A a = new A(new B());
a.eat();

這種情況,如何使用Dagger2呢

肯定有小夥伴這麼想

@Provides
 A providerA() {
 return new A(new B());
 }

直接 new 一個B ,這樣的使用方法,是不對的!!!!!!,不對的!!!!!!!,不對的!!!!!!!!!

正確的打開方式

這時候,我們什麼都不用該,只需要在moudule中添加一個依賴就可以了

@Module
public class MainModule {

 /***
 * 構造方法需要其他參數時候
 *
 * @return
 */
 @Provides
 B providerB() {
 return new B();
 }

 @Provides
 A providerA(B b) {
 return new A(b);
 }
}
(2) 模塊之間的依賴關係

模塊與模塊之間的聯繫,

@Module (includes = {BModule.class})// includes 引入)
public class AModule {
 @Provides
 A providerA() {
 return new A();
 }
}

這樣的話,Dagger會現在A moudule 中尋找對象,如果沒找到,會去找module B 中是否有被Inject註解的對象,如果還是沒有,那麼GG,拋出異常

一個Component 應用多個 module

@Component(modules = {AModule.class,BModule.class})
public interface MainComponent {
 void inject(MainActivity activity);
}

dependencies 依賴其他Component

@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainConponent {
 void inject(MainActivity activity);
}
注意 這裏有坑。一下會講解
(3) @Named註解使用

相當於有個表示,雖然大家都是同一個對象,但是實例化對象不同就不如

A a1 = new A();
A a2 = new A();

// a1 a2 能一樣嘛

Module中 使用@Named註解

@Module
public class MainModule {

 private MainActivity activity;

 public MainModule(MainActivity activity) {
 this.activity = activity;
 }

 @Named("dev")
 @Provides
 MainApi provideMainApiDev(MainChildApi mainChildApi, String url) {
 return new MainApi(mainChildApi, activity,"dev");
 }

 @Named("release")
 @Provides
 MainApi provideMainApiRelease(MainChildApi mainChildApi, String url) {
 return new MainApi(mainChildApi, activity,"release");
 }

}

在Activity/Fragment中使用

public class MainActivity extends AppCompatActivity {

 @Named("dev")
 @Inject
 MainApi apiDev;

 @Named("release")
 @Inject
 MainApi apiRelease;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 DaggerMainComponent.builder()
 .mainModule(new MainModule(this))
 .mainChildModule(new MainChildModule())
 .build()
 .inject(this);
 apiDev.eat();
 apiRelease.eat();
 Log.i("TAG","apiDev--->" + apiDev);
 Log.i("TAG","apiRelease--->" + apiRelease);
 }

}

打印Log

07-14 01:46:01.170 2006-2006/? I/TAG: apiDev--->com.allen.rxjava.MainApi@477928f
07-14 01:46:01.170 2006-2006/? I/TAG: apiRelease--->com.allen.rxjava.MainApi@f2b291c
(4) @Singleton註解

單利模式,是不是超級方便,你想然哪個對象單利化,直接在他的Provider上添加@Singleton 就行了

例如

@Singleton
 @Provides
 A providerA(B b) {
 return new A(b);
 }
注意: 第一個坑!!!
如果 moudule所依賴的Comonent 中有被單利的對象,那麼Conponnent也必須是單利的
@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
}

然後 在Activity中使用,直接打印a1 a2 的地址,

@Inject
 A a2;
 @Inject
 A a1;

可以看到Log

12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba

不相信的小夥伴可以吧@Singleton去掉試試

現在我們完成了單利,然後做了一個事情,就是點擊某個按鈕,跳轉到一個新的Activiry,兩邊都引用同樣一個A 對象,打印A 的地址,

說一下,一個Conponent 可以被對個Activity/Fragment 引用,如

@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
 void inject(MainActivity activity);
 void inject(TestAct activity);
}

上面與兩個Activity, MainActivity 和 TestAct ,都引用相同的 對象,答應地址看看

12-30 00:48:17.477 2788-2788/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 00:48:17.517 2788-2788/com.allens.daggerdemo E/TAG: A2---->com.allens.daggerdemo.Bean.A@4f81861

竟然不同,說好的單利呢

注意: 第二個坑,單利對象只能在同一個Activity中有效。不同的Activity 持有的對象不同

那有人就要問了,沒什麼辦法麼,我就想全局只要一個實例化對象啊? 辦法肯定是有的,

(5) 自定義Scoped
/**
 * @作者 : Android架構
 * @創建日期 :2017/7/14 下午3:04
 * @方法作用:
 * 參考Singleton 的寫法
 * Scope 標註是Scope
 * Documented 標記在文檔
 * @Retention(RUNTIME) 運行時級別
 */
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScoped {
}

首先想一下,什麼樣的對象,能夠做到全局單例,生命週期肯定和APP 綁定嘛,這裏我做演示,一個AppAip 我們要對這個對象,全局單利,所以二話不說,先給Application 來個全家桶,

Module

@Module
public class AppModule {

 @Singleton
 @Provides
 AppApi providerAppApi() {
 return new AppApi();
 }
}

Component

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
 AppApi getAppApi();
}

Application

public class MyApp extends Application {

 private AppConponent appComponent;

 @Override
 public void onCreate() {
 super.onCreate();
 appComponent = DaggerAppConpoment.create();
 }

 public AppConponent getAppComponent() {
 return appConponent;
 }
}

最後是如何使用

首先,這個是個橋樑,依賴方式,上文已經說過了

@ActivityScoped
@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainComponent {
 void inject(MainActivity activity);
 void inject(TestAct activity);
}

細心的小夥伴可能已經發現了,只有在上面一個MainComponent添加了一個@ActivityScoped,這裏說明一下,@Singleton是Application 的單利

注意,第三個坑,子類component 依賴父類的component ,子類component的Scoped 要小於父類的Scoped,Singleton的級別是Application

所以,我們這裏的@Singleton 級別大於我們自定義的@ActivityScoped,同時,對應module 所依賴的component ,也要放上相應的Scope

好吧,上面的例子,打印Log.

12-30 02:16:30.899 4717-4717/? E/TAG: A1---->com.allens.daggerdemo.Bean.AppApi@70bfc2
12-30 02:16:31.009 4717-4717/? E/TAG: A2---->com.allens.daggerdemo.Bean.AppApi@70bfc2

一樣啦

爬坑指南(極度重要)

  1. Provide 如果是單例模式 對應的Compnent 也要是單例模式
  2. inject(Activity act) 不能放父類
  3. 即使使用了單利模式,在不同的Activity 對象還是不一樣的
  4. 依賴component, component之間的Scoped 不能相同
  5. 子類component 依賴父類的component ,子類component的Scoped 要小於父類的Scoped,Singleton的級別是Application
  6. 多個Moudle 之間不能提供相同的對象實例
  7. Moudle 中使用了自定義的Scoped 那麼對應的Compnent 使用同樣的Scoped
(6)Subcomponent

這個是系統提供的一個Component,當使用Subcomponent,那麼默認會依賴Component

例如

@Subcomponent(modules = TestSubModule.class)
public interface TestSubComponent {
 void inject(MainActivity activity);
}
@Component(modules = {MainModule.class})
public interface MainConponent {
 TestSubComponent add(TestSubModule module);
}

在TestSubComponent中 我void inject(MainActivity activity);,便是這個橋樑,我是要注入到MainActivity,但是dagger 並不會給我生成一個Dagger開頭的DaggerTestSubComponent 這個類,如果我想使用TestSubModule.class裏面提供的對象,依然還是使用DaggerMainConponent例如

DaggerMainConponent
 .builder()
 .mainModule(new MainModule())
 .build()
 .add(new TestSubModule())
 .inject(this);

可以看到這裏有一個add的方法,真是我在MainConponent添加的TestSubComponent add(TestSubModule module);

(7)lazy 和 Provider
public class Main3Activity extends AppCompatActivity {

 @PresentForContext
 @Inject
 Lazy<Present> lazy;
 @PresentForName
 @Inject
 Provider<Present> provider;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main3);

 AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
 ActivityComponent activityComponent = DaggerActivityComponent.builder()
 .appComponent(appComponent)
 .activityModule(new ActivityModule())
 .build();

 activityComponent.injectActivity(this);
 Present present = lazy.get();
 Present present1 = provider.get();
 }
}

其中Lazy(懶加載)的作用好比component初始化了一個present對象,然後放到一個池子裏,需要的時候就get它,所以你每次get的時候拿到的對象都是同一個;並且當你第一次去get時,它纔會去初始化這個實例.

procider(強制加載)的作用:

1:同上當你第一次去get時,它纔會去初始化這個實例

2:後面當你去get這個實例時,是否爲同一個,取決於他Module裏實現的方式

四: MVP + Dagger2

這是現在主流的設計架構

MVP,這個我會在後面的文章介紹,這裏不做太多解釋

當你瞭解MVP 的時候,你就知道,所有的業務邏輯全在Presenter,

換句話, presenter 持有的對象,控制着你程序的全部邏輯,這在dagger 中,講白了 我們只要將所有的presetner 對象控制就可以了

下面附上目錄結構,當然僅僅作爲參考。dagger 強大的用法還是需要各位自己去體會,下面的項目 是我剛剛學會dagger 時候 寫的一個項目

可以看到 ,我是 將所有的activity 或者 fragment 全部添加在同一個Component中,當然現在的話 不推薦,比如Utils 你可以專門做一個Component,

首先放上我的Module,公司項目,很多東西沒敢放上來,體諒,可以看到 我這裏提供了一個SplashPresenter,也就是啓動頁的Presneter,業務邏輯

@Module
public class ApiModule {

 public ApiModule() {

 }

 @Provides
 @Singleton
 Handler provideHandler() {
 return new Handler();
 }

 @Provides
 @Singleton
 SQLiteDatabase provideSQLiteDatabase() {
 return new DataBaseHelper(MyApp.context, Config.SqlName, null, Config.SqlVersion).getWritableDatabase();
 }

 /**
 * @ User : Android架構
 * @ 創建日期 : 2017/7/13 下午3:24
 * @模塊作用 :
 * <p>
 * ====================================================================================================================================
 * ====================================================================================================================================
 */
 private SplashPresenter splashPresenter;

 public ApiModule(SplashAct splashAct) {
 splashPresenter = new SplashPresenter(splashAct, new SplashModel());
 }

 @Provides
 @Singleton
 SplashPresenter provideSplashPresenter() {
 return splashPresenter;
 }

 .....
}

當我使用的時候,只需要注入即可,如下代碼

public class SplashAct extends BaseActivity implements SplashContract.View {

 @Inject
 SplashPresenter presenter;

 @Inject
 Handler handler;

 @Inject
 ApiService apiService;

 @Override
 protected void onCreate() {
 setContentView(R.layout.activity_splash);
 }

 @Override
 protected void initInject() {
 DaggerApiComponent.builder()
 .apiModule(new ApiModule(this))
 .build()
 .inject(this);
 }

 @Override
 protected void initListener() {
 presenter.getWordsInfo(true, apiService);
 }

 @Override
 public void gotoLogInAct() {
 handler.postDelayed(new Runnable() {
 @Override
 public void run() {
 startActivity(new Intent(SplashAct.this, LogInAct.class));
 finish();
 }
 }, 1500);
 }
}

最後

給大家分享一份移動架構大綱,包含了移動架構師需要掌握的所有的技術體系,大家可以對比一下自己不足或者欠缺的地方有方向的去學習提升;

需要高清架構圖以及圖中視頻資料的可以查看我主頁

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