Using Dagger2 in Android


Dagger2是一個Java和Android的依賴注入框架.
本文介紹Android中dagger2的基本使用.
其中包括@Inject, @Component, @Module@Provides注解的使用.

使用依賴注入的好處

1.使用類和被依賴的對象構造分開,這樣如果我們需要改變被依賴類的構造方法,不必改動每一個使用類.
2.對各種被依賴類的實例,可以只構造一次.
3.當我們需要更換一種實現時,只需要保證接口一致.
4.利於單元測試,我們可以方便地mock依賴類的對象.

優點總結: 創建對象和使用對象分離, 模塊化增強.

Dagger2的使用

Set Up

在項目的build.gradle里加這個:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后app的build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"

    defaultConfig {
        applicationId "com.ddmeng.dagger2sample"
        minSdkVersion 16
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'javax.annotation:jsr250-api:1.0'
    compile 'com.google.dagger:dagger:2.2'
    apt 'com.google.dagger:dagger-compiler:2.2'
}

常用注解

最常使用的主要是以下這幾個注解:

@Component
Annotates an interface or abstract class for which a fully-formed, dependency-injected implementation is to be generated from a set of modules(). The generated class will have the name of the type annotated with @Component prepended with Dagger. For example, @Component interface MyComponent {...} will produce an implementation named DaggerMyComponent.

@Module
Annotates a class that contributes to the object graph.

@Inject
Dagger constructs instances of your application classes and satisfies their dependencies. It uses the javax.inject.Inject annotation to identify which constructors and fields it is interested in.

Use @Inject to annotate the constructor that Dagger should use to create instances of a class. When a new instance is requested, Dagger will obtain the required parameters values and invoke this constructor.

@Provides
Annotates methods of a module to create a provider method binding. The method's return type is bound to its returned value. The component implementation will pass dependencies to the method as parameters.

Dagger2基本使用

最簡單的一個實例

首先寫一個Component

@Component(modules = MyApplicationModule.class)
public interface MyApplicationComponent {
    // this should be an interface or abstract class

    // write like this, and Make Project, then a DaggerMyApplicationComponent class will be generated
}

此時里面的Module內容可以暫時為空:

@Module
public class MyApplicationModule {
}

寫好后make一下,就生成了

package com.ddmeng.dagger2sample.component;

import com.ddmeng.dagger2sample.module.MyApplicationModule;
import dagger.internal.Preconditions;
import javax.annotation.Generated;

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class DaggerMyApplicationComponent implements MyApplicationComponent {
  private DaggerMyApplicationComponent(Builder builder) {
    assert builder != null;
  }

  public static Builder builder() {
    return new Builder();
  }

  public static MyApplicationComponent create() {
    return builder().build();
  }

  public static final class Builder {
    private Builder() {}

    public MyApplicationComponent build() {
      return new DaggerMyApplicationComponent(this);
    }

    /**
     * @deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
     */
    @Deprecated
    public Builder myApplicationModule(MyApplicationModule myApplicationModule) {
      Preconditions.checkNotNull(myApplicationModule);
      return this;
    }
  }
}

需要切換到project視圖下才能看見.
生成的這個實現,名字是在我們自己的Component名前面加了Dagger.
如果我們的類名不是頂級的,即還有外部類,則會以下划線分隔連接.

現在我們的生成的myApplicationModule()方法被標記為@Deprecated,這是因為我的module里面什么都還沒有呢,所以被認為是沒有必要的.

現在我們添加一個要用的LogUtils類. 想要在MainActivity里面用.
寫好LogUtils類,在構造函數上標記@Inject. 這時候就將LogUtils加入了dependency graph中, 相當於作為預備隊員.

想要在MainActivity作為一個字段用,
在Component里面寫一句:
void inject(MainActivity activity);

因為此時還是沒有用到Module,所以在application里面可以直接build,保存component:

public class SampleApplication extends Application {

    private MyApplicationComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerMyApplicationComponent.builder().build();

    }

    public MyApplicationComponent getComponent() {
        return component;
    }
}

在MainActivity使用的時候, 先get到Component, 然后調用inject()方法, 字段就被注入了.

public class MainActivity extends AppCompatActivity {

    @Inject
    LogUtils logUtils;

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

        ((SampleApplication) getApplication()).getComponent().inject(this);
        logUtils.i("tag", "hi, I'm an instance of LogUtils");
    }
}

運行程序后可以看到打出log,證明注入成功.

此時我們看到生成的代碼有三個類:

public final class DaggerMyApplicationComponent implements MyApplicationComponent {
public enum LogUtils_Factory implements Factory<LogUtils> {
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {

可以通過查看調用棧來看調用關系.

單例@Singleton

如果我們想讓工具類是單例,只需要在上面的基礎上,在類名前加上@Singleton.

此時對應的Component也需要加上@Singleton.否則編譯會不通過.
加好之后,可以打印hashCode()看出, 標記了@Singleton的這個對象,不論被注入幾次,都是同一個對象.

在我們的例子中, 可以讓FileUtils作為一個單例被注入:

@Singleton
public class FileUtils {

    @Inject
    public FileUtils() {
        Log.i(LogUtils.TAG, "new FileUtils: " + hashCode());
    }

    public void doSomething() {
        Log.i(LogUtils.TAG, "do sth with FileUtils " + hashCode());
    }
}

查看生成的代碼,可以看見DaggerMyApplicationComponent為單例的類多保存了一個字段:
private Provider<FileUtils> fileUtilsProvider;

它在init的時候被初始化為:
this.fileUtilsProvider = ScopedProvider.create(FileUtils_Factory.create());

包了一層之后,在ScopeProvider里實現了單例:

  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

@Module@Provides的使用

上面的注入都是用@Inject, 在構造函數和要使用的字段上標記.
有些情況下@Inject是不能滿足需求的.

But @Inject doesn’t work everywhere.

  1. Interfaces can’t be constructed. 接口類型不能直接被構造.
  2. Third-party classes can’t be annotated. 第三方的類不能改動它的代碼.
  3. Configurable objects must be configured! 需要配置的對象需要被配置.

這些情況下, @Inject不夠用啦, 這時候就要用@Provides標記的方法.
方法的返回值返回了它滿足的依賴, 它實際返回的對象可以是返回值接口的實現,或者是返回值類型的子類.
@Provides方法也可以有依賴, 即它的參數.
Dagger會注入它的參數值, 如果它的參數值不能被注入, 則編譯會失敗.
注意這個尋找參數注入的過程是在@Component級別的, 只要這個Component里面有這個參數類型的注入, 即便可能是在另一個Module, 就會自動采用.

所有的@Provides方法都需要放在@Module里面.
按照命名習慣(By convention), 一般@Provides標記的方法都有一個provide前綴, 而module類都有一個Module后綴.
例子:

@Module
public class MyApplicationModule {

    private Context context;

    public MyApplicationModule(Context context) {
        this.context = context;
    }

    @Provides
    @Singleton
    public Context providesContext() {
        return context;
    }

    // Inject interface, return implementation class instance
    @Provides
    public HttpUtil provideHttpUtil() {
        Log.i(LogUtils.TAG, "provideHttpUtil");
        return new MyHttpUtil();
    }

    // Inject class from third-party, or Android framework service
    // This provide method need a parameter, Dagger will obtain the parameter value (injected it)
    // If the parameter is not injectable, then compilation failed
    @Provides
    @Singleton
    ConnectivityManager provideConnectivityManager(Context context) {
        return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    }
}

Dagger2中幾種常用注解總結

@Inject

@Inject的用法分為三種

  • 構造函數上的@Inject:

如果構造函數是有參數的, 則它的所有參數都會自動從dependency graph中找到並注入.
同時構造的這個類也被作為dependency graph的一部分.
但是我們在一個類中最多只能用@Inject標記一個構造方法.

  • 字段上的@Inject: 從dependency graph中找到並注入字段.

這里需要手動調用
(SampleApplication) getApplication()).getComponent().inject(this);
類似的方法, 在這個方法被調用之前, 字段都是null.
注意這里的字段不能是private的.

  • public方法上的@Inject:

所有方法的參數都會由dependency graph提供.
方法注入在構造函數之后立即調用, 意味着我們可以用一個構建好的this對象.

@Module@Provides

@Module標記了提供依賴的類, 其中包含了一些@Provides標注的方法, 返回值即依賴.

@Component

@Component標記的接口負責將所有的事情聯系起來, 可以看做是@Module@Inject之間的橋梁.
我們可以定義我們用的依賴來自哪些Module或者Component.

在Component里可以定義哪些依賴是公有的 (提供返回值為某種依賴的無參數方法) , 也可以定義我們的component可以去哪里inject對象 (void inject()方法, 參數是去注入的地方) .

@Component可以有自己的子Component, 也可以有lifecycle.

先就這么多吧, 更多更高級的使用可以期待下文, 也可以參見后面的參考資料.

本文地址: Using Dagger2 in Android
本文Demo: dagger2-sample

參考資料:

dagger2 repo
dagger2 website
User Guide

Dagger 2.0文檔
dagger 2的sample

這里有些guides:
Code Path Guides: DI with dagger2
Dagger2

這里有一系列關於Dagger2的文章還挺好的:
Froger_mcs dev blog
dagger 1 to dagger 2 migration
Introduction to DI
Dagger2 API
Inject everything - ViewHolder and Dagger2 (with Multibinding and AutoFactory example)
Custom Scope

作者的例子:
Github Client


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM