Android:Dagger2學習之由淺入深

概述

Dagger2是一款使用在Java和Android上的靜態的,運行時依賴注入框架.官方地址:http://google.github.io/dagger/

記得當初剛學習Dagger2的時候看了許多博客,但是感覺上手依然困難,所謂光學不練就是這個意思吧

時至今日,用上此框架的同仁越來越多.分析文章也很多,上手相對要簡單了許多.

學習Dagger2最先要明白的是其各個注解的含義及工作原理,這樣才可以快速的上手和使用.

在這里簡要記錄一下在使用Dagger2過程中的感受和心得體會.

本文示例代碼地址:Dagger2Sample

配置信息

首先貼出此篇博客的所有依賴配置信息,因為dagger2需要依賴 apt,所以也需要引入apt插件,

  • project 下面的build.gradle文件配置
buildscript {
  repositories {
    jcenter()
    //依賴maven庫
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.1.2'

    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    classpath 'me.tatarka:gradle-retrolambda:3.2.5'
  }
}

allprojects {
  repositories {
    jcenter()
    mavenCentral()
  }
}
  • app目錄下的build.gradle文件配置
apply plugin: 'me.tatarka.retrolambda' //使用lanbda表達式
apply plugin: 'com.neenbedankt.android-apt' //apt插件

android {
  //...
  // 注釋沖突
  packagingOptions {
    exclude 'META-INF/services/javax.annotation.processing.Processor'
  }

  // 使用Java1.8
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  testCompile 'junit:junit:4.12'
  compile 'com.android.support:appcompat-v7:23.4.0'

  apt 'com.google.dagger:dagger-compiler:2.0.2'
  compile 'com.google.dagger:dagger:2.0.2'
  provided 'javax.annotation:jsr250-api:1.0'

  compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
  compile 'io.reactivex:rxjava:1.1.0' // RxJava

  compile 'com.squareup.retrofit2:retrofit:2.0.2' // Retrofit網絡處理
  compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' // Retrofit的rx解析庫
  compile 'com.squareup.retrofit2:converter-gson:2.0.2' // Retrofit的gson庫

  compile 'com.jakewharton:butterknife:8.0.1' // 標注
  apt 'com.jakewharton:butterknife-compiler:8.0.1' //視圖注入

}

Dagger2基礎注解

Inject,Component,Module,Provides是Dagger中最基礎的幾個注解,是整個依賴注入的核心,下面我們來看一下各個注解的作用.

Inject and Component

@Inject:

用來標注需要依賴的成員被依賴類的構造函數(如果依賴類同時依賴了其他類,其他類的構造函數也要有@Inject標注),

注意: 使用@Inject標注構造函數,不能標注一個類的多個構造函數


  @Inject public UserModel() {
    this.name = "Hello Dagger2,I`m from Inject";
  }
  @Inject public UserModel(String name) {
    this.name = name;
  }

上面的寫法會報錯:Error:(29, 18) 錯誤: Types may only contain one @Inject constructor.

@Component:

光有@Inject可不行,還需要一個東西將他兩聯系起來才能讓依賴類找到被依賴對象,其中Component就起到了這個作用,在Dagger2中是以接口形式存在,
是用來連接 需要依賴類被依賴類,Component使用injectXX(XX xx)將依賴注入到需要依賴的地方,

基本使用

接下來我們來看一下Dagger2最基本的用法

第一步:編寫JavaBean

/**
 * Created on 16/6/8.下午8:57.
 *
 * @author bobomee
 */
public class UserModel {
  private String name;

  @Inject public UserModel() {
    this.name = "Hello Dagger2,I`m from Inject";
  }

  public String getName() {
    return name;
  }
}

第二步:創建Component

/**
 * Created on 16/6/8.下午8:59.
 *
 * @author bobomee
 */
@Component public interface UserComponent {

  void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static UserComponent init() {
      return DaggerUserComponent.create();
    }
  }
}

第三步:構建依賴,先build一下生成dagger圖譜

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.text) TextView text;

  @Inject UserModel userModel;

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

    //inject dependencies
    UserComponent.Initializer.init().inject(this);

    text.setText(userModel.getName());
  }
}

注意: @Inject成員不能是private的,否則會報:Error:(35, 29) 錯誤: Dagger does not support injection into private fields

Provides and Module

@Provides:

自定義依賴,Dagger2中不僅提供了@Inject標注,他比@Inject更加強大,不僅可以提供本地依賴,還可以提供第三方依賴(第三方庫和Android系統類不可以用@Inject注解構造函數).

注意: @Provides的優先級高于@Inject

@Module:

所有的@Provides都必須包含在@Module內部,相當于簡單工廠,提供了各種依賴@Provides方法.之后再將Module加入到Component管理即可完成依賴注入(Component不僅可以從@Inject找到被依賴類,還可以從@Module找到被依賴類)

注意: 通過modules列出一個Component所有依賴的Module,如果缺失任何一個編譯會報錯

自定義Module使用

第一步:編寫AppModule,提供依賴@Provide方法,其中@Singleton是自定義注解

/**
 * Created on 16/6/8.下午9:30.
 *
 * @author bobomee
 */
@Module public class AppModule {

  private App app;//App為我們自定義的Application

  public AppModule(App app) {
    this.app = app;
  }

  @Provides @Singleton public App provideApp() {
    return app;
  }
}

第二步:創建Component,這里使用了@Singleton,必須和Module中@Provides方法修飾相同,否則編譯報錯

/**
 * Created on 16/6/8.下午9:31.
 *
 * @author bobomee
 */
@Singleton @Component(modules = {
    AppModule.class
}) public interface AppComponent {

//將依賴注入到自定義的Application
  void inject(App app);

  final class Initializer {
    private Initializer() {
    }

    public static AppComponent init(App app) {
      return DaggerAppComponent.builder().appModule(new AppModule(app)).build();
    }
  }
}

第三步:自定義Application中完成依賴

/**
 * Created on 16/6/8.下午9:29.
 *
 * @author bobomee
 */
public class App extends Application {

  private AppComponent appComponent;

  @Inject static App app;

  public static App get() {
    return app;
  }

  @Override public void onCreate() {
    super.onCreate();

//當inject完成后app就不為空了,且和Application生命周期相同,因此//Singleton可以起到全局單例的作用
    buildComponent();
  }

  private void buildComponent() {
    appComponent = AppComponent.Initializer.init(this);
    appComponent.inject(this);
  }

//向外提供appComponent,方便其他依賴appComponent的component構建
  public AppComponent component() {
    return appComponent;
  }
}

第四步:使用依賴

//MainActivity中
 @BindView(R.id.text1) TextView text1;

 text1.setText(App.get().toString());

Component組織方式

  1. 一個應用中,必須包含一個全局的Component(類似于上面的AppComponent),管理整個App的實例.

  2. 一般AppComponent和Application生命周期相同,所以注入到Application中即可.

  3. 因為Application是全局單例的,所以AppModule中創建的實例也是單例的(@Singleton注解就是依照此原理).

  4. 根據具體功能或者單獨的一個頁面定義一個Component.

  5. 某個單獨的Component要用到全局實例的時候,可以通過繼承AppComponent來實現

  6. Component之間的關系有 依賴(dependencies),包含(SubComponent),繼承方式(extends)

Component依賴寫法

接下來來看一下一個典型的dependencies寫法

第一步: 定義注入到MainActivity的Product

/**
 * Created on 16/6/8.下午9:48.
 *
 * @author bobomee
 */
public class Product {

  private String productQualifier;

  public Product(String productQualifier) {
    this.productQualifier = productQualifier;
  }

  public String getProductQualifier() {
    return productQualifier;
  }
}

第二步:定義Component

/**
 * Created on 16/6/8.下午8:59.
 *
 * @author bobomee
 */
@Component public interface UserComponent {

  // unused
  //void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static UserComponent init() {
      return DaggerUserComponent.create();
    }
  }
}
/**
 * Created on 16/6/8.下午10:00.
 *
 * @author bobomee
 */
@ActivityScope @Component(dependencies = UserComponent.class,
    modules = ProductModule.class) public interface ProductComponent extends UserComponent {

  void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static ProductComponent init(UserComponent userComponent) {
      //dependency usercomponent
      return DaggerProductComponent.builder().productModule(new ProductModule()).userComponent(userComponent).build();
    }
  }
}

注意: 此處依賴UserComponent,需要將UserComponent中的注入刪除.

//MainActivity
//inject dependencies
ProductComponent.Initializer.init(UserComponent.Initializer.init()).inject(this);

@Scope:

注解作用域.用于更好的組織Component,和定義Component的粒度.
@Singleton是Dagger2默認實現的,用于管理全局單例(AppComponent中),
@Scope用在Component 和 Module 頭上,
如果要定義一個實例的生命周期在Activity內,則可以定義@ActivityScope(當然名稱隨便起,只要對應即可)

/**
 * Created on 16/6/8.下午9:55.
 *
 * @author bobomee
 */
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {
}
/**
 * Created on 16/6/8.下午10:00.
 *
 * @author bobomee
 */
@ActivityScope @Component(dependencies = UserComponent.class,
    modules = ProductModule.class) public interface ProductComponent extends UserComponent {
//...
}
/**
 * Created on 16/6/8.下午9:49.
 *
 * @author bobomee
 */
@Module public class ProductModule {

  @ActivityScope @Provides Product provideProduct() {
    return new Product("this is a product");
  }
//...
}

依賴迷失之Qualifier

@Qualifier:

限定符,當依賴類中需要被依賴類的兩個不同對象的時候,幫助我們去為相同接口的依賴創建“tags”.
如我們需要兩個不同的level的product,Qualifier會幫助你區分對應的一個,

/**
 * Created on 16/6/8.下午9:57.
 *
 * @author bobomee
 */
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface ProductLevel {
  String value() default "";
}
  • 聲明Module
/**
 * Created on 16/6/8.下午9:49.
 *
 * @author bobomee
 */
@Module public class ProductModule {

//....
  @ActivityScope @ProductLevel("good") @Provides Product provideGoodProduct() {
    return new Product("good product");
  }

  @ActivityScope @ProductLevel("bad") @Provides Product provideBadProduct() {
    return new Product("bad product");
  }
}
  • 注入Inject
@Inject @ProductLevel("good") Product product1;
@Inject @ProductLevel("bad") Product product2;

總結

  • Inject用來標注依賴被依賴的構造函數
  • Provides提供依賴的方法上添加的注解,provide方法需要包含在Module中
  • Module專門提供依賴,類似工廠模式,包含Provides方法
  • Component依賴被依賴的橋梁,(先從Module中找依賴,再從Inject構造函數找)
  • Scope自定義注解,用于標示作用域,命名隨意,對應即可,其中@Singleton是一個系統的模式實現.(管理Module與Component的匹配)
  • Qualifier自定義注解,用于解決一個實例可以被多種方式構建的依賴迷失問題
  • Component有三種組織關系,分為依賴,包含和繼承,用于解決依賴復用與共享問題

依賴注入的過程:
步驟1:查找Module中是否存在創建該類的方法。

步驟2:若存在創建類方法,查看該方法是否存在參數

...........步驟2.1:若存在參數,則按從步驟1開始依次初始化每個參數

...........步驟2.2:若不存在參數,則直接初始化該類實例,一次依賴注入到此結束

步驟3:若不存在創建類方法,則查找Inject注解的構造函數,看構造函數是否存在參數

...........步驟3.1:若存在參數,則從步驟1開始依次初始化每個參數

...........步驟3.2:若不存在參數,則直接初始化該類實例,一次依賴注入到此結束

參考:

這里推薦牛曉偉的三篇博客:

Android:dagger2讓你愛不釋手-基礎依賴注入框架篇

Android:dagger2讓你愛不釋手-重點概念講解、融合篇

Android:dagger2讓你愛不釋手-終結篇

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容