【譯】Dagger2在Android中的使用

Carve_Time發表於2020-01-02

原文

與其他大多數依賴注入框架相比,Dagger2的主要優點之一是其嚴格生成的實現(無反射)意味著它可以在Android應用程式中使用。但是,在Android應用程式中使用Dagger時仍有一些注意事項。

使用Dagger編寫Android應用程式的主要困難之一是很多Android框架類都是由作業系統本身例項化的,例如ActivityFragment,但是如果Dagger可以建立所有注入的物件,則Dagger的效果最好。相反,您必須在生命週期方法中執行成員注入。這意味著許多類最終看起來像:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}
複製程式碼

注入Activity物件

  1. 在您的應用程式元件中安裝AndroidInjectionModule以確保這些基本型別所需的所有繫結都可用。

  2. 首先編寫一個實現AndroidInjector<YourActivity>@Subcomponent和一個繼承AndroidInjector.Builder<YourActivity>@Subcomponent.Builder

@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}
複製程式碼
  1. 定義子元件後,通過定義一個繫結子元件層次結構並將其新增到注入應用程式的元件的模組,將其新增到元件層次結構中。
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule {
  @Binds
  @IntoMap
  @ActivityKey(YourActivity.class)
  abstract AndroidInjector.Factory<? extends Activity>
      bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
}

@Component(modules = {..., YourActivityModule.class})
interface YourApplicationComponent {}
複製程式碼

專業提示:如果您的子元件及其構建器沒有第2步中提到的其他方法或超型別,您可以使用@ContributesAndroidInjector為您生成它們。新增一個抽象模組方法,該方法返回您的活動,使用@ContributesAndroidInjector對其進行註釋,並指定要安裝到子元件中的模組,而不是步驟2和3。如果子元件需要作用域,則也可以將範圍註釋應用於該方法。

@ActivityScope
@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
abstract YourActivity contributeYourActivityInjector();
複製程式碼
  1. 接下來,讓您的Application實現HasActivityInjector並且@Inject一個從activityInjector()方法返回的DispatchingAndroidInjector<Activity>
public class YourApplication extends Application implements HasActivityInjector {
  @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

  @Override
  public void onCreate() {
    super.onCreate();
    DaggerYourApplicationComponent.create()
        .inject(this);
  }

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingActivityInjector;
  }
}
複製程式碼
  1. 最終,在你的Activity.onCreate()方法中,在呼叫super.onCreate();之前呼叫AndroidInjection.inject(this)
public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}
複製程式碼

如何工作

AndroidInjection.inject()Application中獲取一個DispatchingAndroidInjector<Activity>並且傳遞你的activityinject(this)DispatchingAndroidInjector為您的Activity類(它是YourActivitySubcomponent.Builder)查詢AndroidInjector.Factory,建立AndroidInjector(它是YourActivitySubcomponent),並將您的Activity傳遞給inject(YourActivity)

AndroidInjectioninject方法

/**
 * Injects {@code activity} if an associated {@link AndroidInjector} implementation can be found,
 * otherwise throws an {@link IllegalArgumentException}.
 *
 * @throws RuntimeException if the {@link Application} doesn't implement {@link
 *     HasActivityInjector}.
 */
public static void inject(Activity activity) {
  checkNotNull(activity, "activity");
  Application application = activity.getApplication();
  if (!(application instanceof HasActivityInjector)) {
    throw new RuntimeException(
        String.format(
            "%s does not implement %s",
            application.getClass().getCanonicalName(),
            HasActivityInjector.class.getCanonicalName()));
  }
//
  AndroidInjector<Activity> activityInjector =
      ((HasActivityInjector) application).activityInjector();
  checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());

  activityInjector.inject(activity);
}

複製程式碼

注入Fragment物件

Injecting a Fragment is just as simple as injecting an Activity. Define your subcomponent in the same way, replacing Activity type parameters with Fragment, @ActivityKey with @FragmentKey, and HasActivityInjector with HasFragmentInjector.

注入一個Fragment像注入一個Activity一樣簡單。以相同的方式定義你的subcomponent,使用Fragment替換Activity@FragmentKey替換@ActivityKey,HasFragmentInjector替換HasActivityInjector

不像在Activity型別中那樣在onCreate()中注入,而是在onAttach()中注入Fragment

與為Activity定義的模組不同,您可以選擇在哪裡為Fragments安裝模組。你可以讓你的Fragment元件成為另一個Fragment子元件,Activity元件或Application元件的一個子元件 - 這一切都取決於你的Fragment需要的其他繫結。確定元件位置後,使相應的型別實現HasFragmentInjector。例如,如果您的Fragment需要來自YourActivitySubcomponent的繫結,那麼您的程式碼將如下所示:

public class YourActivity extends Activity
    implements HasFragmentInjector {
  @Inject DispatchingAndroidInjector<Fragment> fragmentInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public AndroidInjector<Fragment> fragmentInjector() {
    return fragmentInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @FragmentKey(YourFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }

複製程式碼

基本框架型別

因為DispatchingAndroidInjector在執行時按類查詢適當的AndroidInjector.Factory,所以基類可以實現HasActivityInjectorHasFragmentInjectoretc等等以及呼叫AndroidInjection.inject()。每個子類都需要做的就是繫結一個對應的@Subcomponent。如果您沒有複雜的類層次結構,Dagger會提供一些基本型別,例如DaggerActivityDaggerFragment。Dagger還為同樣的目的提供了一個DaggerApplication你需要做的就是擴充套件它並覆蓋applicationInjector()方法來返回應該注入應用程式的元件。

以下型別也包括在內:

  • DaggerService和DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

注意:DaggerBroadcastReceiver只能在AndroidManifest.xml中註冊BroadcastReceiver時使用。在您自己的程式碼中建立BroadcastReceiver時,請改為使用建構函式注入。

支援庫

對於Android支援庫的使用者,dagger.android.support包中存在並行型別。請注意,儘管支援Fragment的使用者必須繫結AndroidInjector.Factory <?extends android.support.v4.app.Fragment>AppCompat使用者應該繼續實現AndroidInjector.Factory <?extends Activity>而不是<?extends AppCompatActivity>(或FragmentActivity)。

如何獲取它

將以下內容新增到您的build.gradle中:

dependencies {
  compile 'com.google.dagger:dagger-android:2.x'
  compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}
複製程式碼

何時注入

只要有可能,建構函式注入是首選,因為javac將確保沒有欄位在被設定之前被引用,這有助於避免NullPointerException。 當需要成員注射(如上所述)時,傾向於儘早注射。 出於這個原因,DaggerActivity在呼叫super.onCreate()之前立即在onCreate()中呼叫AndroidInjection.inject(),並且DaggerFragment在onAttach()中也是這樣做的,這也可以防止在重新連線片段時出現不一致。

在Activity中的super.onCreate()之前呼叫AndroidInjection.inject()是非常重要的,因為超級呼叫在配置更改期間將前一個活動例項的碎片連線到Fragments,而這又會導致碎片。 為了使片段注入成功,該活動必須已經被注入。 對於ErrorProne的使用者,在super.onCreate()之後呼叫AndroidInjection.inject()是一個編譯器錯誤。

常見問題

AndroidInjector.Factory旨在成為無狀態介面,以便實現者不必擔心管理與將被注入的物件相關的狀態。 當DispatchingAndroidInjector請求一個AndroidInjector.Factory時,它通過一個Provider來這樣做,以便它不明確地保留工廠的任何例項。 由於由Dagger生成的AndroidInjector.Builder實現確實保留了正在被注入的Activity / Fragment /等的例項,因此將範圍應用於提供它們的方法時發生編譯時錯誤。 如果你肯定你的AndroidInjector.Factory沒有為注入物件保留一個例項,你可以通過在模組方法中應用@SuppressWarnings(“dagger.android.ScopedInjectoryFactory”)來消除這個錯誤。

相關文章