原文: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
这个构造器,下一个是要知道的是@Component
。Component是用@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);}
}
DaggerAppComponent
是AppComponent
的实现类,我们需要访问AppComponent
的实现代码。IDE看不到这个实现类,不能把它标记成红色,但是之后我会展示如何处理这个问题。
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);}
}
现在我们已经有了一个完整可以正常运行的例子了!:)
对的,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,例如你可以创建DatabaseModule
、NetworkModule
、MainActivityModule
等等。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会以一个单例的形式存在。
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {void inject(MainActivity activity);
}
(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();
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