ジェイミー・サンソンの オリジナル記事の翻訳に注目してください
Android 9 Pieの前にアクティビティを作成する
依存性注入(DI)は、さまざまな理由ですべての開発形態に使用される一般的なモデルです。 Daggerプロジェクトのおかげで、Androidの開発で使用されるテンプレートとして採用されています。 Android 9 Pieに対する最近の変更により、DIに関しては、特に新しいAppComponentFactory
クラスAppComponentFactory
、より能力がAppComponentFactory
。
DIは、最新のAndroid開発に関して非常に重要です。 これにより、クラス間で使用されるサービスへのリンクを取得する際のコードの総量を削減でき、一般的にアプリケーションをコンポーネントにうまく分割できます。 この記事では、Android開発で使用される最も一般的なDIライブラリであるDagger 2に焦点を当てます。 これがどのように機能するかについての基本的な知識をすでに持っていると想定されますが、すべての微妙な点を理解する必要はありません。 この記事がちょっとした冒険であることは注目に値します。 これは興味深いことであり、それだけです。しかし、執筆時点では、Android 9 Pieはプラットフォームのバージョンパネルにも表示されませんでした。したがって、このトピックは少なくとも数年間は日常の開発に関連しないでしょう。
今日のAndroidでの依存性注入
簡単に言えば、DIを使用して、依存クラス、つまり作業を行うクラスに「依存」クラスのインスタンスを提供します。 Repositoryパターンを使用してデータ関連のロジックを処理し、Activityでリポジトリを使用してユーザーにデータを表示するとします。 複数の場所で同じリポジトリを使用したい場合があるため、依存性注入を使用して、さまざまなクラス間で同じインスタンスを共有しやすくします。
まず、リポジトリを提供します。 モジュールでProvides
関数を定義し、これが実装したいインスタンスであることをDaggerに認識させます。 リポジトリには、ファイルとネットワークを操作するためのコンテキストインスタンスが必要であることに注意してください。 アプリケーションコンテキストを提供します。
@Module class AppModule(val appContext: Context) { @Provides @ApplicationScope fun provideApplicationContext(): Context = appContext @Provides @ApplicationScope fun provideRepository(context: Context): Repository = Repository(context) }
次に、 Repository
を使用するクラスの実装を処理するComponent
を定義する必要がありRepository
。
@ApplicationScope @Component(modules = [AppModule::class]) interface ApplicationComponent { fun inject(activity: MainActivity) }
最後に、リポジトリを使用するようにActivity
を設定できます。 ApplicationComponent
インスタンスを別の場所に作成したとします。
class MainActivity: AppCompatActivity() { @Inject lateinit var repository: Repository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // application.applicationComponent.inject(this) // } }
以上です! Daggerを使用して、アプリケーション内で依存関係の注入を設定するだけです。 これを行うにはいくつかの方法がありますが、これは最も簡単なアプローチのようです。
現在のアプローチの何が問題になっていますか?
上記の例では、2つの異なるタイプの注入を見ました。一方は他方よりも明白です。
あなたが見逃したかもしれない最初のものは、コンストラクタへの埋め込みとして知られています。 これは、クラスのコンストラクターを介して依存関係を提供するためのメソッドです。つまり、依存関係を使用するクラスは、インスタンスの起源を認識しません。 これは、注入ロジックをModule
クラスに完全にカプセル化するため、依存性注入の最も純粋な形式と見なされます。 この例では、このアプローチを使用してリポジトリを提供しました。
fun provideRepository(context: Context): Repository = Repository(context)
そのためには、 provideApplicationContext()
関数で提供したContext
必要でした。
2番目にわかりやすいのは、クラスフィールドの実装です 。 このメソッドは、ストアを提供するためにMainActivity
使用されました。 ここでは、 Inject
アノテーションを使用して、フィールドを注入の受信者として定義します。 次に、 onCreate
関数でonCreate
ApplicationComponent
フィールドに依存関係を注入する必要があることを伝えます。 コンポーネントへの明示的な参照があるため、コンストラクターへの埋め込みほどきれいに見えません。つまり、埋め込みの概念が依存クラスに浸透しているためです。 Androidフレームワーククラスのもう1つの欠陥。最初に行うことは依存関係の提供であることを確認する必要があるためです。 これがライフサイクルの間違った時点で発生した場合、まだ初期化されていないオブジェクトを誤って使用しようとする可能性があります。
理想的には、クラスフィールドの実装を完全に取り除く必要があります。 このアプローチは、それを知らないクラスでの実装に関する情報をスキップし、ライフサイクルで問題を引き起こす可能性があります。 私たちはそれをより良くしようとする試みを見てきましたし、AndroidのDaggerはかなり信頼できる方法ですが、最終的にはコンストラクターに埋め込みを使用することができればより良いでしょう。 現在、システムによって作成されているため、「アクティビティ」、「サービス」、「アプリケーション」などの多くのフレームワーククラスにこのアプローチを使用することはできません。 現時点では、フィールドにクラスを導入することにこだわっているようです。 それにもかかわらず、Android 9 Pieは興味深いものを準備しています。
Android 9 Pieでの依存性注入
記事の冒頭で述べたように、Android 9 PieにはAppComponentFactoryクラスがあります。 ドキュメントはかなり少なく、開発者のWebサイトに次のように掲載されています。
マニフェスト要素の作成を制御するために使用されるインターフェイス。
興味深いです。 ここの「マニフェスト要素」は、アクティビティ、サービス、アプリケーションクラスなど、 AndroidManifest
ファイルにリストするクラスを指します。 これにより、これらの要素の「作成を制御」することができます...それでは、アクティビティを作成するためのルールを設定できますか? なんて嬉しい!
もっと深く掘り下げましょう。 AppComponentFactory
を拡張し、 instantiateActivity
メソッドをオーバーライドすることから始めinstantiateActivity
。
class InjectionComponentFactory: AppComponentFactory() { private val repository = NonContextRepository() override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return when { className == MainActivity::class.java.name -> MainActivity(repository) else -> super.instantiateActivity(cl, className, intent) } } }
次に、 アプリケーションタグ内のマニフェストでコンポーネントファクトリを宣言する必要があります 。
<application android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:name=".InjectionApp" android:appComponentFactory="com.mypackage.injectiontest.component.InjectionComponentFactory" android:theme="@style/AppTheme" tools:replace="android:appComponentFactory">
最後に、アプリケーションを起動することができます...それは動作します! NonContextRepository
、MainActivityコンストラクターを通じて提供されます。 優雅に!
いくつかの予約があることに注意してください。 ここではContext
使用できません。 Context
が存在する前であっても、関数の呼び出しが発生するためです-これは混乱を招きます! コンストラクターがApplicationクラスを実装するようにさらに進むことができますが、Daggerがこれをさらに簡単にする方法を見てみましょう。
会う-短剣マルチバインド
この記事の範囲外であるため、ボンネットの下でのDagger複数バインディング操作の詳細には触れません。 知っておく必要があるのは、コンストラクターを手動で呼び出すことなく、クラスコンストラクターを埋め込む優れた方法を提供することです。 これを使用して、フレームワーククラスをスケーラブルな方法で簡単に実装できます。 それがすべて加算される方法を見てみましょう。
最初にアクティビティを設定して、次に進むべき場所を見つけましょう。
class MainActivity @Inject constructor( private val repository: NonContextRepository ): Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // } }
これは、依存性注入についてほとんど言及されていないことをすぐに示しています。 表示されるのは、コンストラクターの前のInject
アノテーションのみです。
次に、コンポーネントとDaggerモジュールを変更する必要があります。
@Component(modules = [ApplicationModule::class]) interface ApplicationComponent { fun inject(factory: InjectionComponentFactory) }
@Module(includes = [ComponentModule::class]) class ApplicationModule { @Provides fun provideRepository(): NonContextRepository = NonContextRepository() }
大きな変更はありません。 コンポーネントファクトリを実装するだけでよいのですが、マニフェスト要素をどのように作成しますか? ここではComponentModule
が必要です。 見てみましょう:
@Module abstract class ComponentModule { @Binds @IntoMap @ComponentKey(MainActivity::class) abstract fun bindMainActivity(activity: MainActivity): Any @Binds abstract fun bindComponentHelper(componentHelper: ComponentHelper): ComponentInstanceHelper } @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) @Retention(AnnotationRetention.RUNTIME) @MapKey internal annotation class ComponentKey(val clazz: KClass<out Any>)
ええ、まあ、ほんのいくつかの注釈。 ここで、 Activity
をマップに接続し、このマップをComponentHelper
クラスに実装して、このComponentHelper
を提供しComponentHelper
(すべて2つのBinds
命令で)。 Daggerは、 MainActivity
アノテーションのおかげでMainActivity
をインスタンス化する方法を知っているため、プロバイダーをこのクラスに「バインド」し、必要なコンストラクター依存関係を自動的に提供します。 ComponentHelper
次のとおりです。
class ComponentHelper @Inject constructor( private val creators: Map<Class<out Any>, @JvmSuppressWildcards Provider<Any>> ): ComponentInstanceHelper { @Suppress("UNCHECKED_CAST") override fun <T> resolve(className: String): T? = creators .filter { it.key.name == className } .values .firstOrNull() ?.get() as? T } interface InstanceComponentHelper { fun <T> resolve(className: String): T? }
簡単に言えば、これらのクラスのサプライヤーのクラスマップが作成されました。 クラスを名前で解決しようとすると、このクラスのプロバイダー(ある場合)を見つけ、それを呼び出してこのクラスの新しいインスタンスを取得し、それを返します。
最後に、新しいヘルパークラスを使用するには、 AppComponentFactory
を変更する必要があります。
class InjectionComponentFactory: AppComponentFactory() { @Inject lateinit var componentHelper: ComponentInstanceHelper init { DaggerApplicationComponent.create().inject(this) } override fun instantiateActivity(cl: ClassLoader, className: String, intent: Intent?): Activity { return componentHelper .resolve<Activity>(className) ?.apply { setIntent(intent) } ?: super.instantiateActivity(cl, className, intent) } }
コードを再度実行します。 それはすべて動作します! なんて嬉しい。
コンストラクター実装の問題
そのようなタイトルはあまり印象的ではないかもしれません。 ほとんどのインスタンスをコンストラクターに挿入することで通常モードに埋め込むことができますが、標準的な方法で依存関係のコンテキストを提供する明確な方法はありません。 しかし、AndroidのContext
はすべてです。 設定、ネットワーク、アプリケーション構成などにアクセスするために必要です。 多くの場合、依存関係は、ネットワークや設定などのデータ関連サービスを使用するものです。 これを回避するには、依存関係を純粋な関数に書き換えるか、 Application
クラスのコンテキストインスタンスですべてを初期化しますが、これを行う最適な方法を判断するには、さらに多くの作業が必要です。
このアプローチのもう1つの欠点は、スコープの定義です。 Daggerでは、クラスの関係を適切に分離した高性能な依存性注入を実装するための重要な概念の1つは、オブジェクトグラフのモジュール性とスコープの使用です。 このアプローチはモジュールの使用を禁止しませんが、スコープの使用を制限します。 AppComponentFactory
は、標準フレームワーククラスとはまったく異なる抽象化レベルで存在します。プログラムからリンクを取得することはできないため、異なるスコープのActivity
に依存関係を提供するように指示する方法はありません。
実際にスコープの問題を解決するには多くの方法がありますが、その1つはFragmentFactory
を使用して、スコープを持つコンストラクタにFragmentFactory
を埋め込むことです。 詳細は説明しませんが、フラグメントの作成を制御する方法があり、スコープの面ではるかに大きな自由が得られるだけでなく、下位互換性もあることがわかりました。
おわりに
Android 9 Pieでは、コンストラクターインジェクションを使用して、「Activity」や「Application」などのフレームワーククラスに依存関係を提供する方法を導入しました。 Dagger Multi-bindingを使用すると、アプリケーションレベルで依存関係を簡単に提供できることがわかりました。
すべてのコンポーネントを実装するコンストラクターは非常に魅力的であり、コンテキストインスタンスで適切に動作するようにすることさえできます。 これは有望な未来ですが、API 28以降でのみ利用可能です。0.5%未満のユーザーにリーチしたい場合は、試してみてください。 それ以外の場合は、数年経ってもそのような方法が適切であるかどうかを確認してください。