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

原文: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中的对象。

@Component
public 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

@Module
public 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中加入下面这些代码:

@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(...)。如果你喜欢创建像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: null
app after injection: info.android15.dagger2example.MyApplication@419430e0
Base injection app: info.android15.dagger2example.MyApplication@419430e0
Real injection pref: android.app.SharedPreferencesImpl@419630d8
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值