Dagger是什么和我们怎么通过使用它获得收获

来源:互联网 发布:mac pdf怎么签名 编辑:程序博客网 时间:2024/04/29 19:14

原文: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没有关联。或者你可能发现你到处使用丑陋的单例模式。现在,想想重构的事。你决定将GsonMyApp移到MyNetwork中,你需要重写多少行代码?

使用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 {    @Inject    public 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;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        MyApplication.inject(this);        logger.log(this);    }}

生成代码

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

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

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

@Componnet对Dagger2的意义是,它应该为注入MainActivity生成代码。
当我们这些都写了,我们点击Build按钮让Dagger2生成AppComponent的实现。

最后:注入代码

public class MyApplication extends Application {    private static AppComponent component;    @Override    public 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());    }    @Override    public 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;    }    @Override    public 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;    @Override    public 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;    @Override    public 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

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

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

改写PreferencesLogger

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

public class PreferencesLogger {    private SharedPreferences pref;    @Inject    public 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;    @Inject    public 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;    @Override    public 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中加入下面这些代码:

@ProvidesMyApplication 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: nullapp after injection: info.android15.dagger2example.MyApplication@419412e0Base injection app: info.android15.dagger2example.MyApplication@419412e0Real injection pref: null

就像你看到的,还没有满足子类依赖SharedPreferences,因为典型的Dagger2使用模式是你应该在RealTarget中调用inject(...)。如果你喜欢创建像BaseActivityBaseCustomViewBaseAdapter等等,这样的方式就不满足。
现在我会用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: nullapp after injection: info.android15.dagger2example.MyApplication@419430e0Base injection app: info.android15.dagger2example.MyApplication@419430e0Real injection pref: android.app.SharedPreferencesImpl@419630d8
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孩子上学想留级怎么办 孩子口算太慢怎么办 孩子浮躁没耐心怎么办 如果孩子打妈妈怎么办 孩子英语成绩不好怎么办 小孩说话发音不准怎么办 小孩gk发音不准怎么办 大学无英语基础怎么办 孩子应用题理解不透怎么办 嫉妒一个人好痛苦怎么办 爱嫉妒别人怎么办心理 总有人嫉妒我怎么办 从小嫉妒闺蜜怎么办 怕闺蜜比我好怎么办 孩子不会做应用题怎么办 当你嫉妒别人怎么办 单位社保发票丢失怎么办 单位医保发票丢失怎么办 图书明细没有给怎么办 拼音拼不到一起怎么办 孩子不会拼拼音怎么办 小孩子不会拼拼音怎么办 小孩拼音发音不准怎么办 孩子声调不会标怎么办 孩子拼音发音不准怎么办 孩子学拼音不会怎么办? 孩子不会拼生字怎么办? 对数字不敏感怎么办 孩子说话不算数怎么办 儿童做事不认真怎么办 幼儿园小朋友爱打人怎么办 遇到熊孩子家长怎么办 小孩嘴烂了怎么办 孩子杯宠坏了怎么办 小孩子老爱打人怎么办 小孩偏执的性格怎么办 孩子上学爱打人怎么办 宝宝爱动手打人怎么办 幼儿爱打人教师怎么办 中班幼儿爱打人怎么办 幼儿园小班小朋友打人老师怎么办