Как настроить инъекцию зависимостей с помощью кинжала для вещей, отличных от действий и фрагментов?

Я начал настраивать инъекцию зависимости, используя Кинжал следующим образом. Пожалуйста, примите во внимание исправление моей реализации, поскольку у меня могут быть ошибки! Реализация следует за андроидным примером, представленным проектом. Ниже вы можете увидеть, как я успешно добавил инъекцию зависимостей для Activities и Fragments . Я стараюсь держать его в покое, поэтому я решил добавить Timber в качестве замены регистратора для использования журналов Android .

 import android.app.Application; import java.util.Arrays; import java.util.List; import dagger.ObjectGraph; import com.example.debugging.LoggingModule; public class ExampleApplication extends Application { private ObjectGraph mObjectGraph; protected List<Object> getModules() { return Arrays.asList( new AndroidModule(this), new ExampleModule(), new LoggingModule() ); } private void createObjectGraphIfNeeded() { if (mObjectGraph == null) { Object[] modules = getModules().toArray(); mObjectGraph = ObjectGraph.create(modules); } } public void inject(Object object) { createObjectGraphIfNeeded(); mObjectGraph.inject(object); } } 

В настоящее время AndroidModule не используется нигде, но может оказаться полезным, когда необходим Context и LayoutInflater например, в CursorAdapters .

 import android.content.Context; import android.view.LayoutInflater; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; /** * A module for Android-specific dependencies which require a {@link Context} * or {@link android.app.Application} to create. */ @Module(library = true) public class AndroidModule { private final ExampleApplication mApplication; public AndroidModule(ExampleApplication application) { mApplication = application; } /** * Allow the application context to be injected but require that it be * annotated with {@link ForApplication @Annotation} to explicitly * differentiate it from an activity context. */ @Provides @Singleton @ForApplication Context provideApplicationContext() { return mApplication; } @Provides @Singleton LayoutInflater provideLayoutInflater() { return (LayoutInflater) mApplication .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } } 

Я не уверен, какие поставщики приложений будут здесь. Сейчас я остаюсь с журналом .

 import dagger.Module; @Module( complete = false ) public class ExampleModule { public ExampleModule() { // TODO put your application-specific providers here! } } 

Я подготовил LoggingModule который обеспечивает доступ к Timber .

 package com.example.debugging; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; import com.example.BuildConfig; import com.example.activities.BaseFragmentActivity; import com.example.activities.DetailsActivity; import com.example.fragments.BaseListFragment; import com.example.fragments.ProfilesListFragment; import timber.log.Timber; @Module(injects = { // Activities BaseFragmentActivity.class, DetailsActivity.class, // Fragments BaseListFragment.class, ProfilesListFragment.class }) public class LoggingModule { @Provides @Singleton Timber provideTimber() { return BuildConfig.DEBUG ? Timber.DEBUG : Timber.PROD; } } 

Базовый класс для Activities вводит себя в граф объектов …

 package com.example.activities; import android.os.Bundle; import com.actionbarsherlock.app.SherlockFragmentActivity; import javax.inject.Inject; import com.example.ExampleApplication; import timber.log.Timber; public abstract class BaseFragmentActivity extends SherlockFragmentActivity { @Inject Timber mTimber; @Override protected void onCreate(Bundle savedInstanceState) { // ... super.onCreate(savedInstanceState); ((ExampleApplication) getApplication()).inject(this); } } 

… и любые преимущества суб-класса от Timber уже присутствуют.

 package com.example.activities; import android.os.Bundle; import com.example.R; public class DetailsActivity extends BaseFragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_details); mTimber.i("onCreate"); // ... } } 

То же самое для Fragments : базовый класс выполняет грязную работу …

 package com.example.fragments; import android.os.Bundle; import com.actionbarsherlock.app.SherlockListFragment; import javax.inject.Inject; import com.example.ExampleApplication; import timber.log.Timber; public abstract class BaseListFragment extends SherlockListFragment { @Inject Timber mTimber; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ((ExampleApplication) getActivity().getApplication()).inject(this); } } 

… и дополнительный класс от своего суперкласса.

 package com.example.fragments; import android.os.Bundle; public class ProfilesListFragment extends BaseListFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // TODO This might be a good example to inject resources // in the base class. But how? setEmptyText(getResources() .getString(R.string.profiles_list_no_content)); mTimber.i("onActivityCreated"); // ... } } 

Все идет нормально. Но как можно внедрить Timber в BaseCursorAdapter , BaseContentProvider , BaseSQLiteOpenHelper , BaseService , BaseAsyncTask и static вспомогательные методы?

Устаревший пример Android от Кристофера Перри указывает, как вставить Адаптер в ListFragment, но не как вводить Context , Resources , LayoutInflater , Cursor в (Cursor)Adapter или просто Timber .


Рекомендации:

  • Кинжал
  • Простой андроид
  • Джесси Уилсон – Кинжал: Инъектор быстрой зависимости для Android и Java
  • Эрик Берк – Android-приложение «Анатомия»

Ознакомьтесь с примерами Энди Денни для инъекций в разных сценариях:

https://github.com/adennie/fb-android-dagger

Некоторые моменты, в которые я ввожу:

  • Подклассы Activity , Service и Fragment : in onCreate
  • Подклассы BroadcastReceiver (включая, например, AppWidgetProvider ): in onReceive

Tl; dr В этом вопросе многое происходит, но мне может помочь мой Gist . Если вы можете получить Context где-нибудь, вы можете ввести его на основе объекта ObjectGraph поддерживаемого вашим классом Application .


Редактировать JJD :

ObjectGraph в Gist можно интегрировать следующим образом:

 public class ExampleApplication extends Application implements ObjectGraph.ObjectGraphApplication { /* Application Lifecycle */ @Override public void onCreate() { // Creates the dependency injection object graph _object_graph = ObjectGraph.create(...); } /* ObjectGraphApplication Contract */ @Override public void inject(@Nonnull Object dependent) { _object_graph.inject(dependent); } /** Application's object graph for handling dependency injection */ private ObjectGraph _object_graph; } 

 public abstract class BaseFragmentActivity extends SherlockFragmentActivity { @Inject Timber _timber; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); ObjectGraph.inject(this); } } 

 public abstract class BaseListFragment extends SherlockListFragment { @Inject Timber _timber; @Override public void onActivityCreated(Bundle icicle) { super.onActivityCreated(icicle); ObjectGraph.inject(this); } } 

Аналогичные работы для BaseCursorAdapter , BaseContentProvider и BaseService .

Мы должны были делать то же самое (например, вводить логгер везде). Мы закончили создание очень маленькой статической обертки (доступной везде), которая обертывает класс, который вводится через кинжал статически.

 package com.example.secret; import javax.inject.Inject; import com.example.interfaces.Logger; public class LoggerProvider { @Inject static Logger logger; public LoggerProvider() { } public static Logger getLogger() { return logger; } 

}

Logger реализует ваш интерфейс ведения журнала. Чтобы ввести его на уровне приложения, вам необходимо:

  graph = ObjectGraph.create(getModules().toArray()); graph.injectStatics(); 

Код здесь: https://github.com/nuria/android-examples/tree/master/dagger-logger-example

Инъекция Context , Resources и LayoutInflater (передача контекста приложения при его обновлении в приложении).

 @Module(complete = false) public class AndroidServicesModule { private final Context context; public AndroidServicesModule(@ForApplication Context context) { this.context = context; } @Provides @Singleton Resources provideResources() { return context.getResources(); } @Provides @Singleton LocationManager provideLocationManager() { return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } @Provides @Singleton LayoutInflater provideLayoutInflater() { return (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Provides @Singleton Resources provideResources() { return context.getResources(); } @Provides @ForApplication Context provideContext() { return context; } } 

Конечно, вы должны, вероятно, определить контекст с аннотацией, указав, что это контекст приложения (например, @ForApplication ).

Если вам нужен эквивалент @InjectResource от @InjectResource(int) я ничего не могу придумать. Butterknife кажется правильным lib, чтобы добавить это. См. Здесь

Эти конструкции можно добавить к классу ExampleApplication :

 private static ExampleApplication INSTANCE; @Override public void onCreate() { super.onCreate(); INSTANCE = this; mObjectGraph = ObjectGraph.create(getModules()); mObjectGraph.injectStatics(); mObjectGraph.inject(this); } public static ExampleApplication get() { return INSTANCE; } 

После этого вы можете ввести любой объект (обозначенный this ) одной простой строкой:

 ExampleApplication.get().inject(this) 

Это нужно вызывать в конструкторе для объектов, созданных вручную или onCreate (или аналоговых) для тех, которые управляются системой Android.