当前位置: 代码迷 >> 综合 >> Dagger
  详细解决方案

Dagger

热度:63   发布时间:2023-12-06 06:51:56.0

原文:https://github.com/konmik/konmik.github.io/wiki/Snorkeling-with-Dagger-2

Dagger是什么

Dagger是为对象实例提供选择的Java库。你可以使用注解@Inject来标注你的构造函数,替代在构造函数中传递大量参数,并且所有需要的对象被创建和自动分配。

@Inject SharedPreferences pref;

这会通过一个资源代码生成来完成-你注释一些field,Dagger会创建代码来完成这些field。你可以经常在build文件夹中检查这些生成的代码。 

把Dagger作为Java的扩展。在一般面向对象语言中,对象的创建、存储、传递会存在大量的问题,Dagger提供了一种优雅的解决方式。

历史

Dagger被Square开发,项目地址:square.github.io/dagger 
Dagger2是Squared的Dagger的一个分支,现在有Google负责开发并且被用在性能关键项目:此处输入链接的描述

好处:单例

在Android项目中,一个典型的任务就是创建一个单例的SharedPreferences对象,并且在整个Application中共享。它有可能出现在activity、fragment、自定义View中。典型的解决办法是在Application中创建一个SharedPreference,并为它创建一个get方法。然后,你的代码会想这样:

public class MainActivity extends Activity {SharedPreferences pref;Gson gson;ServerAPI api;onCreate(...) {MyApp app = (MyApp)getContext().getApplicationContext();pref = app.getSharedPreferences();gson = app.getGson();api = app.getApi();

有一天你会发现 MyApp 被奇怪的东西充斥着,并且它们跟 MyApp 没有关联。或者你可能发现你到处使用丑陋的单例模式。现在,想想重构的事。你决定将 Gson MyApp 移到 MyNetwor k中,你需要重写多少行代码?

使用Dagger的方式:

public class MainActivity extends Activity {@Inject SharedPreferences pref;@Inject Gson gson;@Inject ServerAPI api;onCreate(...) {MainInjector.inject(this);

现在我们不关心到哪里拿到这些东西,这些东西会为我们创建。Gson可能会被放在MyApp对象里,也可能在MyNetWork.Parsing.Processor的任何地方。我们只要关注Gson!对的, 我们需要在Dagger中定义一次如何获得 Gson ,但是之后我们会通过 @Inject 注解在整个application中共享它。

基础例子

使用@Inject注入构造函数

我们先创建一个最简单的例子。我们将注入一个用来打印所有SharedPreferences的对象PreferencesLogger

public class PreferencesLogger {@Injectpublic PreferencesLogger() {}public void log(Context context) {SharedPreferences pref = context.getSharedPreferences("preferences", 0);Log.v(getClass().getSimpleName(), "Logging all preferences:");for (Map.Entry entry : pref.getAll().entrySet())Log.v(getClass().getSimpleName(), entry.getKey() + " = " + entry.getValue());}
}

@Inject 对Dagger来说是一个用来实例化 PreferencesLogger 的构造器。上面的实现有一点笨但是我们之后会把 SharedPreferences 传递给 PreferencesLogger 的构造函数,那样看起来会好一些。

Injection

下面是我么如何使用PreferencesLogger:

public class MainActivity extends Activity {@Inject PreferencesLogger logger;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);MyApplication.inject(this);logger.log(this);}
}

public class MainActivity extends Activity {@Inject PreferencesLogger logger;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);MyApplication.inject(this);logger.log(this);}
}

生成代码

你是不是好奇MyApplication.inject是什么样子的?但是你现在还不能立刻看到。:) 首先,你要看一张图:

当我们使用Dagger2的时候,我们需要记住它的构成。你要知道@Inject这个构造器,下一个是要知道的是@ComponentComponent是用@Component注解标记的接口,展示了那些我们想注入到Dagger2中的对象。

@Component
public interface AppComponent {void inject(MainActivity activity);
}

@Componnet 对Dagger2的意义是,它应该为注入MainActivity生成代码。 

当我们这些都写了,我们点击Build按钮让Dagger2生成AppComponent的实现。

最后:注入代码
public class MyApplication extends Application {private static AppComponent component;@Overridepublic void onCreate() {super.onCreate();component = DaggerAppComponent.builder().build();}public static void inject(MainActivity target) {component.inject(target);}
}

DaggerAppComponentAppComponent的实现类,我们需要访问AppComponent的实现代码。IDE看不到这个实现类,不能把它标记成红色,但是之后我会展示如何处理这个问题。

现在我们已经有了一个完整可以正常运行的例子了!:) 
对的,Dagger在开始之前还需要做一点事情,(别灰心)思考一下它带来的优势!在我的工程中,平均有50个左右的对象需要通过application去共享。Dagger为我节省重写的代码量是不可思议的。在使用Dagger之前,任何时候都要避免整合代码。现在我整合代码既快又简单,在对象之间没有负面效果或者关联失败的现象。 
我希望你一直坚持Dagger,如果那样,我们可以一起学习更多先进的课题。

探索生成的代码

让我们看一下生成的代码,为了缩减这个指南,我已经去掉所有没用的代码。:)

public final class DaggerAppComponent implements AppComponent {private MembersInjector mainActivityMembersInjector;private DaggerAppComponent(Builder builder) {
(3)     initialize();}public static Builder builder() {
(1)    return new Builder(); // Builder instantiation}private void initialize() {
(4)    this.mainActivityMembersInjector = MainActivity_MembersInjector.create(MembersInjectors.noOp(), PreferencesLogger_Factory.create());}@Overridepublic void inject(MainActivity activity) {
(9)   mainActivityMembersInjector.injectMembers(activity);}public static final class Builder {public AppComponent build() {
(2)        return new DaggerAppComponent(this); // DaggerAppComponent instantiation}}
}public final class MainActivity_MembersInjector implements MembersInjector {private final MembersInjector supertypeInjector;private final Provider loggerProvider;public MainActivity_MembersInjector(MembersInjector supertypeInjector, Provider loggerProvider) {
(7)     this.supertypeInjector = supertypeInjector;
(8)     this.loggerProvider = loggerProvider;}@Overridepublic void injectMembers(MainActivity instance) {
(10)   supertypeInjector.injectMembers(instance);
(11)   instance.logger = loggerProvider.get();}public static MembersInjector create(MembersInjector supertypeInjector, Provider loggerProvider) {
(6)  return new MainActivity_MembersInjector(supertypeInjector, loggerProvider);}
}public enum PreferencesLogger_Factory implements Factory {INSTANCE;@Overridepublic PreferencesLogger get() {
(12)   return new PreferencesLogger(); // @Inject annotated constructor call}public static Factory create() {
(5)    return INSTANCE;}
}

没有生成注释,但是已经非常清晰的展示了这个简单的代码的工作方式。我仅仅列出一些代码混淆而缺失的代码。
public interface Factory  extends javax.inject.Provider {
}public interface Provider  {T get();
}public interface MembersInjector  {void injectMembers(T t);
}public static  MembersInjector noOp() {return (MembersInjector) NoOpMembersInjector.INSTANCE;
}private static enum NoOpMembersInjector implements MembersInjector {INSTANCE;@Overridepublic void injectMembers(Object instance) {}
}

你还有一些事情要做:执行下面代码,跟踪执行的步骤。
DaggerAppComponent.builder().build().inject(MainActivity.this);

这里会发生什么? 

- 调用静态方法DaggerAppComponent.builder()来创建一个Builder实例; 
Builder创建了一个DaggerAPPComponent实例; 
DaggerAPPComponent创建了一个MainActivity_MembersInjector实例; 
MainActivity_MembersInjector使用PreferencesLogger_Factory去实例化PreferencesLogger,并将其注入到MainActivity

好了,到这里,你应该明白了Dagger2的基本工作原理,是时候喝杯茶休息一会,15mins后继续。:D

注入第三方的类

Module的定义

module是程序的一部分,当程序的另一个组成部分Component用到一些功能的时候,它用来提供这些功能。你可以拥有不止一个module,例如你可以创建DatabaseModuleNetworkModuleMainActivityModule等等。component也是同样道理。 
让我们围绕共享一个SharedPreferences创建下一个例子。首先,我们需要告诉Dagger我们如何实例化SharedPreferences。明显我们不能使用@Inject来标记构造函数,所以我们要像这样创建一个Module

@Module
public class AppModule {private static final String PREFERENCES_FILE_NAME = "preferences";private MyApplication app;AppModule(MyApplication app) {this.app = app;}@Singleton@ProvidesSharedPreferences provideSharedPreferences() {return app.getSharedPreferences(PREFERENCES_FILE_NAME, 0);}
}

@Module 对dagger来说,它是用来对象实例化的。 

@Provides意味着他标记的这个方法应该被实例化的对象所调用。 
@Singleton意味着这个对象应该被重新注入其他对象中。 
这里有一张小图表说明了Dagger2是如何使用Module的: 

改写PreferencesLogger

现在我们不想在PreferencesLogger``中实例化SharedPreferences,我们可以在PreferencesLogger的构造函数中声明它,Dagger构造出SharePreferences`的实例。

public class PreferencesLogger {private SharedPreferences pref;@Injectpublic PreferencesLogger(SharedPreferences pref) {this.pref = pref;}public void log() {Log.v(getClass().getSimpleName(), "Logging all preferences:");for (Map.Entry entry : pref.getAll().entrySet())Log.v(getClass().getSimpleName(), entry.getKey() + " = " + entry.getValue());}
}

我们有 两种方式 –作为构造函数的参数和注入标记。作为构造器的参数有更多的可控性–如果你通过构造器注入一个对象,你可以在构造函数中正确的使用它。 

如果你用@Inject SharedPreferences pref的方式注入,它会在执行构造函数之后注入SharedPreference。代码就会这个样子:

public class PreferencesLogger {@Inject SharedPreferences pref;@Injectpublic PreferencesLogger() {}...

这两种方法做的是同一样事情。注意第二种方式中没有 private 修饰词修饰,因为Dagger需要通过这种方式进行注入。

改写AppComponent
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {void inject(MainActivity activity);
}

@Singleton说明了这个component会以一个单例的形式存在。 

(modules = AppModule.class)意味着AppModule会被用于AppComponent的注入。你可以在这里列出多个module,例如:(modules = {AppModule.class, MainActivityModule.class}) 
如果你想在这个component中注入多个对象,你需要向这样写:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {void inject(MainActivity activity);void inject(MainFragment fragment);void inject(MainToolbarView view);
}

改写AppComponent的实例
component = DaggerAppComponent.builder().appModule(new AppModule(this)).build();

就这样,你现在知道如何在Dagger2中使用第三方类了吧。

奇迹

Component更容易的实例化

如果你尝试使用了Dagger2,你会注意到在程序编译无误的情况下,IDE找不到DaggerAppComponent并用红色标记成错误。一切都看起来正常和干净,那这个问题该如何避免呢。 
一个可行的解决办法–使用android-aptplugin。 
在我浏览GitHub的时候,在Mortar我发现了解决这个问题的另外一个有趣办法。我使用buildComponent方法来实例化component并把它复制到我的项目例子中。Dagger2Helper 
第二种方法使用反射,但是它没有产生一点明显的性能影响。在最慢的设备上,在程序运行15ms内就可以获得它们。 
这就是它们的样子:

public class MyApplication extends Application {private static AppComponent component;@Overridepublic void onCreate() {super.onCreate();// component = DaggerAppComponent.builder().appModule(new AppModule(this)).build();component = Dagger2Helper.buildComponent(AppComponent.class, new AppModule(this));}...

更容易注入和继承

给一个稍稍修改我们注入方法实现更容易注入的小贴士。将MyApplication.inject(...)改成MyApplication.getComponent().inject(...)。你可以提取注入器并这样调用MainInjector.getComponent().inject(...)。 
另一种方式依赖于反射,所以我们啰嗦的描述它。 
在Dagger1的旧时代,我们只有一种void inject(Object object)方法。它不是很明显的很快很方便。例如,我们可以在一个基类中写inject(this),它所有的子类不用调用Dagger都可以拿到那些注入。这是非常的方便,所以我决定复制(学习)这样的行为。 
这是个例子:

public class BaseTarget {@Inject protected MyApplication app;public BaseTarget() {Log.v(getClass().getSimpleName(), "app before injection: " + app);MyApplication.getComponent().inject(this);Log.v(getClass().getSimpleName(), "app after injection: " + app);}
}

为了满足这种依赖性,我会在 APPModule 中加入下面这些代码:
@Provides
MyApplication provideApp() {return app;
}

子类就可以不通过直接调用 injet(...) 拿到注入:
public class RealTarget extends BaseTarget {@Inject SharedPreferences pref;public RealTarget() {}public void check() {Log.v(getClass().getSimpleName(), "Base injection app: " + app);Log.v(getClass().getSimpleName(), "Real injection pref: " + pref);}
}

调用 new RealTarget().check() 会有下面输出:
app before injection: null
app after injection: info.android15.dagger2example.MyApplication@419412e0
Base injection app: info.android15.dagger2example.MyApplication@419412e0
Real injection pref: null

就像你看到的,还没有满足子类依赖 SharedPreferences ,因为典型的Dagger2使用模式是你应该在 RealTarget 中调用 inject(...) 。如果你喜欢创建像 BaseActivity BaseCustomView BaseAdapter 等等,这样的方式就不满足。 

现在我会用inject方法重写Dagger1实现更容易的注入。父类:Dagger2Helper:

public class MyApplication extends Application {...public static void inject(Object target) {Dagger2Helper.inject(AppComponent.class, component, target);}
}public class BaseTarget {@Inject protected MyApplication app;public BaseTarget() {Log.v(getClass().getSimpleName(), "app before injection: " + app);// MyApplication.getComponent().inject(this);MyApplication.inject(this);Log.v(getClass().getSimpleName(), "app after injection: " + app);}
}
输出:

app before injection: null
app after injection: info.android15.dagger2example.MyApplication@419430e0
Base injection app: info.android15.dagger2example.MyApplication@419430e0
Real injection pref: android.app.SharedPreferencesImpl@419630d8