美文网首页Android技术知识Android开发Android开发
Dagger 2 在 Android 上的使用(三)

Dagger 2 在 Android 上的使用(三)

作者: 于卫国 | 来源:发表于2019-01-05 15:48 被阅读3次

    本文介绍了Dagger 2 中@Qualifier和@Named注解以及Lazy和Provider的使用。

    本文首发:http://yuweiguocn.github.io/

    《卜算子·咏梅》
    风雨送春归,飞雪迎春到。已是悬崖百丈冰,犹有花枝俏。
    俏也不争春,只把春来报。待到山花烂漫时,她在丛中笑。
    -近代,毛泽东

    注解@Qualifier和注解@Named

    书接上文,在上文中我们使用注解@Binds完成了对People抽象类的其中一个子类的绑定,如果我们想同时注入两个子类该怎样处理?

    public abstract class People {
        abstract String doWhat();
    }
    
    public class Student extends People {
        @Inject
        public Student() {
        }
    
        @Override
        String doWhat() {
            return "study";
        }
    }
    
    
    public class Worker extends People {
    
        @Inject
        public Worker() {
        }
    
        @Override
        String doWhat() {
            return "work";
        }
    }
    

    在Module类中添加对另一个子类Worker的绑定方法,这里我们用到了限定符注解@Named,由于我们提供了多个依赖导致Dagger2无法识别要注入哪一个依赖,使用限定符注解@Named很好地解决了这种问题。

    @Module
    public abstract class PeopleModule {
    
        @Binds
        @Named("study")
        abstract People bindStudent(Student student);
    
        @Binds
        @Named("work")
        abstract People bindWorker(Worker worker);
    
    }
    
    
    @Component(modules = PeopleModule.class)
    public interface PeopleComponent {
    
        void inject(PeopleActivity activity);
    }
    
    

    在提供的实例添加注解@Named,在使用依赖注入的地方添加同样的注解:

    public class PeopleActivity extends Activity {
    
        @Inject
        @Named("study")
        People student;
    
        @Inject
        @Named("work")
        People worker;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            DaggerPeopleComponent.create().inject(this);
            Log.d("debug", student.doWhat());
            Log.d("debug", worker.doWhat());
        }
    }
    

    同样我们也可以使用限定符注解@Qualifier通过自定义注解完成相同的功能。

    使用注解@Qualifier自定义注解:

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Study {
    }
    
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Work {
    }
    

    然后将自定义的注解替换上面注解@Named

    @Module
    public abstract class PeopleModule {
    
        @Binds
        @Study
        abstract People bindStudent(Student student);
    
        @Binds
        @Work
        abstract People bindWorker(Worker worker);
    
    }
    
    public class PeopleActivity extends Activity {
    
        @Inject
        @Study
        People student;
    
        @Inject
        @Work
        People worker;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            DaggerPeopleComponent.create().inject(this);
            setContentView(R.layout.activity_main);
            Log.d("debug", student.doWhat());
            Log.d("debug", worker.doWhat());
        }
    }
    

    由于使用注解@Named需要指定字符串,可能会由于人为疏忽造成错误,所以这里推荐使用限定符注解@Qualifier自定义注解的方法

    Lazy和Provider

    我们可以使用Lazy在注入时不进行初始化,在调用get方法时再初始化实例:

    public class PeopleActivity extends Activity {
    
        @Inject
        @Work
        Lazy<People> worker;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            DaggerPeopleComponent.create().inject(this);
            setContentView(R.layout.activity_main);
            Log.d("debug", worker.get().doWhat());
        }
    }
    

    然后来看一下生成的代码:

    public final class DaggerPeopleComponent implements PeopleComponent {
      ...
      private PeopleActivity injectPeopleActivity(PeopleActivity instance) {
        PeopleActivity_MembersInjector.injectWorker(
            instance, DoubleCheck.lazy((Provider) Worker_Factory.create()));
        return instance;
      }
      ...
    }
    

    这里终于用到了生成的工厂类:

    public final class Worker_Factory implements Factory<Worker> {
      private static final Worker_Factory INSTANCE = new Worker_Factory();
    
      @Override
      public Worker get() {
        return provideInstance();
      }
    
      public static Worker provideInstance() {
        return new Worker();
      }
    
      public static Worker_Factory create() {
        return INSTANCE;
      }
    
      public static Worker newWorker() {
        return new Worker();
      }
    }
    

    这里使用双重检查锁定为我们提供了唯一的实例,分析具体可参见:Java实现单例模式的正确姿势

    /**
     * A {@link Lazy} and {@link Provider} implementation that memoizes the value returned from a
     * delegate using the double-check idiom described in Item 71 of <i>Effective Java 2</i>.
     */
    public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
      private static final Object UNINITIALIZED = new Object();
    
      private volatile Provider<T> provider;
      private volatile Object instance = UNINITIALIZED;
    
      private DoubleCheck(Provider<T> provider) {
        assert provider != null;
        this.provider = provider;
      }
    
      @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
      @Override
      public T get() {
        Object result = instance;
        if (result == UNINITIALIZED) {
          synchronized (this) {
            result = instance;
            if (result == UNINITIALIZED) {
              result = provider.get();
              instance = reentrantCheck(instance, result);
              /* Null out the reference to the provider. We are never going to need it again, so we
               * can make it eligible for GC. */
              provider = null;
            }
          }
        }
        return (T) result;
      }
    
      /**
       * Checks to see if creating the new instance has resulted in a recursive call. If it has, and the
       * new instance is the same as the current instance, return the instance. However, if the new
       * instance differs from the current instance, an {@link IllegalStateException} is thrown.
       */
      public static Object reentrantCheck(Object currentInstance, Object newInstance) {
        boolean isReentrant = !(currentInstance == UNINITIALIZED
            // This check is needed for fastInit's implementation, which uses MemoizedSentinel types.
            || currentInstance instanceof MemoizedSentinel);
    
        if (isReentrant && currentInstance != newInstance) {
          throw new IllegalStateException("Scoped provider was invoked recursively returning "
              + "different results: " + currentInstance + " & " + newInstance + ". This is likely "
              + "due to a circular dependency.");
        }
        return newInstance;
      }
    
      ...
    
      /** Returns a {@link Lazy} that caches the value from the given provider. */
      // This method is declared this way instead of "<T> Lazy<T> lazy(Provider<T> delegate)"
      // to work around an Eclipse type inference bug: https://github.com/google/dagger/issues/949.
      public static <P extends Provider<T>, T> Lazy<T> lazy(P provider) {
        if (provider instanceof Lazy) {
          @SuppressWarnings("unchecked")
          final Lazy<T> lazy = (Lazy<T>) provider;
          // Avoids memoizing a value that is already memoized.
          // NOTE: There is a pathological case where Provider<P> may implement Lazy<L>, but P and L
          // are different types using covariant return on get(). Right now this is used with
          // DoubleCheck<T> exclusively, which is implemented such that P and L are always
          // the same, so it will be fine for that case.
          return lazy;
        }
        return new DoubleCheck<T>(checkNotNull(provider));
      }
    }
    

    相对于Lazy注入,Provider注入每次调用get时都会提供一个新的实例,Provider是由Java提供的接口:

    public class PeopleActivity extends Activity {
    
        @Inject
        @Study
        Provider<People> student;
    
        @Inject
        @Work
        Lazy<People> worker;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            DaggerPeopleComponent.create().inject(this);
            setContentView(R.layout.activity_main);
            Log.d("debug", "1"+student.get().toString());
            Log.d("debug", "2"+student.get().toString());
            Log.d("debug", "3"+worker.get().toString());
            Log.d("debug", "4"+worker.get().toString());
        }
    }
    

    打印输出如下:

    2018-11-12 21:26:26.365 30805-30805/? D/debug: 1io.github.yuweiguocn.test.Student@205713d
    2018-11-12 21:26:26.365 30805-30805/? D/debug: 2io.github.yuweiguocn.test.Student@6fffd32
    2018-11-12 21:26:26.365 30805-30805/? D/debug: 3io.github.yuweiguocn.test.Worker@d03a83
    2018-11-12 21:26:26.365 30805-30805/? D/debug: 4io.github.yuweiguocn.test.Worker@d03a83
    

    对比下生成的代码,唯一的区别就是Lazy使用DoubleCheck保证了实例的唯一性:

    public final class DaggerPeopleComponent implements PeopleComponent {
      ...
    
      private PeopleActivity injectPeopleActivity(PeopleActivity instance) {
        PeopleActivity_MembersInjector.injectStudent(instance, (Provider) Student_Factory.create());
        PeopleActivity_MembersInjector.injectWorker(
            instance, DoubleCheck.lazy((Provider) Worker_Factory.create()));
        return instance;
      }
      ...
    }
    

    总结

    • 当提供多个相同类型的注入依赖时,可以使用限定符注解@Qualifier和注解@Named,为了避免人为疏忽导致错误,推荐使用注解@Qualifier自定义注解完成相应功能
    • 使用Lazy可以在调用get方法时再初始化,其中使用了双重检查锁定保证了实例的唯一性
    • 使用Provider在调用get方法时会提供新的实例

    参考

    相关文章

      网友评论

        本文标题:Dagger 2 在 Android 上的使用(三)

        本文链接:https://www.haomeiwen.com/subject/fkuzxqtx.html