据说你还不会用Dagger2?Dagger2 For Android最佳实践教程

本文首发于个人我的博客java

点击进入原文连接android

前言

Dagger2是如今很是火的一个依赖注入框架,目前由Google维护,在Github上面已经有12K star了。Dagger2的入门门槛实际上是比较高的,据了解,目前有不少Android工程师对Dagger2还不甚了解,没有用上Dagger2或者是用法有问题,本文的主旨就是让Android工程师快速掌握Dagger2而且优雅简洁地使用Dagger2。这里为你们奉上一份Dagger2 在Android上的最佳实践教程。git

注意: Dagger2框架的上手难度是比通常的框架更难一些的,因此在练习的时候应该尽可能减小干扰因素,尽可能少引入其它复杂的第三方库,最佳作法是只依赖Android基础库和Dagger2 For Android须要的库。

其它技术文章推荐

给你一个全自动的屏幕适配方案(基于SW方案)!—— 解放你和UI的双手程序员

Gradle自动实现Android组件化模块构建github

技术教程Demo地址(本文的Demo也在里面哟)👍编程

你的支持,是我前进的动力,若是个人项目对您有帮助的话,能够点下star👍

依赖注入

什么是依赖注入?

维基百科上面的介绍是:在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。 传递服务给客户端,而非容许客户端来创建或寻找服务,是本设计模式的基本要求。设计模式

简单来讲依赖注入就是将实例对象传入到另外一个对象中去。缓存

依赖注入的实现

维基百科的说法很是抽象,其实在日常编码中,咱们一直都在使用以来注入。依赖注入主要有如下几种方式。微信

  • 构造函数注入
public class Chef{
    Menu menu;
    public Man(Menu menu){
        this.menu = menu;
    }
}
  • setter方法注入
public class Chef{
    Menu menu;
    public setMenu(Menu menu){
        this.menu = menu;
    }
}
  • 接口注入
public interface MenuInject{
    void injectMenu(Menu menu);
}

public class Chef implements MenuInject{
    Menu menu;
    
    @Override
    public injectMenu(Menu menu){
        this.menu = menu;
    }
}
  • 依赖注入框架
public @Inject class Menu{
    ...
}

public class Chef{
    @Inject
    Menu menu;
}

从上面的例子能够看出,依赖注入其实就是咱们每天都在用的东西。架构

Dagger2实现依赖注入

为何要使用Dagger2?

从上面这个简单的例子来看,为了实现依赖注入,好像不必引入第三方的框架。在只有一我的开发,而且业务像上面这么简单的时候,确实是不必引入Dagger2。可是若是多人同时开发,而且业务很是复杂呢?例如,咱们这里的Menu须要初始化,而菜单也要依赖具体的菜式的呢?若是只是一个地方用到的话,仍是能接受的。若是项目中有不少地方同时用到呢?若是这个菜单要修改呢?有经验的开发者可能会想到使用单例模式。可是若是项目中有不少类型的结构的话,那么咱们就须要管理很是多的单例,而且单例可能也须要依赖其它对象。在这种状况下若是有变动需求或者是更换维护人员,都会使简单的改动变得很是繁琐,而且容易致使各类各样的奇怪BUG。因此这里咱们就须要引入第三方的依赖注入工具,让这个工具来帮助咱们实现依赖注入。

Dagger2就是咱们须要的第三方依赖注入工具。Dagger2较其它依赖注入工具备一个优点,就是它是采用静态编译的方式编译代码的,会在编译期生成好辅助代码,不会影响运行时性能,这一点很是适合用于移动端。

Dagger2的使用方式

Dagger是经过Component来确认需求与依赖对象的,能够说Component是他们之间的纽带。若是各位用过Dagger2或者了解过Dagger2的教程的话,那么必定知道,Dagger2的使用方式是十分繁琐的,每一个须要用到依赖注入的地方都须要经过编写DaggerxxxComponent的模版代码来实现依赖注入。要写很是多的模版代码,大大增长了系统的复杂度。笔者在使用Dagger 2.17的时候,发现Google对Dagger 2进行了优化,如今使用Dagger实现依赖注入要写的代码其实很是少,而且复杂度已经有了很大程度的下降了。在这里,笔者就不介绍旧的使用方式了,使用过Dagger2的同窗能够对比这两种方式的差别,没有使用过的直接学习新的使用方式就能够了。

Dagger2最简单的使用方式就是下面这种:

public class A{
    @Inject
    public A(){
        
    }
}

public class B{
    @Inject A a;
    ...
}

这种方法是最简单的,没什么难度。可是在实际的项目中咱们会遇到各类各样的复杂状况,例如,A还须要依赖其它的类,而且这个类是第三方类库中提供的。又或者A实现了C接口,咱们在编码的时候须要使用依赖致使原则来增强咱们的代码的可维护性等等。这个时候,用上面这种方法是没办法实现这些需求的,咱们使用Dagger2的主要难点也是由于上面这些缘由致使的。

仍是用上面的例子来解释,假设须要作一个餐饮系统,须要把点好的菜单发给厨师,让厨师负责作菜。如今咱们来尝试下用Dagger2来实现这个需求。

首先,咱们须要引入Dagger For Android的一些列依赖库:

implementation 'com.google.dagger:dagger-android:2.17'
    implementation 'com.google.dagger:dagger-android-support:2.17' // if you use the support libraries
    implementation 'com.google.dagger:dagger:2.17'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'

而后咱们实现Chef类和Menu类

Cooking接口

public interface Cooking{
    String cook();
}

Chef

public class Chef implements Cooking{

    Menu menu;

    @Inject
    public Chef(Menu menu){
        this.menu = menu;
    }

    @Override
    public String cook(){
        //key菜名, value是否烹饪
        Map<String,Boolean> menuList = menu.getMenus();
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String,Boolean> entry : menuList.entrySet()){
            if (entry.getValue()){
                sb.append(entry.getKey()).append(",");
            }
        }

        return sb.toString();
    }
}

Menu

public class Menu {

    public Map<String,Boolean> menus;

    @Inject
    public Menu( Map<String,Boolean> menus){
         this.menus = menus;
    }
    
    Map<String,Boolean> getMenus(){
        return menus;
    }

}

如今咱们写一个Activity,做用是在onCreate方法中使用Chef对象实现cooking操做。咱们先来看看不使用Dagger2和使用Dagger2的代码区别。

MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        Map<String, Boolean> menus = new LinkedHashMap<>();
        menus.put("酸菜鱼", true);
        menus.put("土豆丝", true);
        menus.put("铁板牛肉", true);
        Menu menu = new Menu(menus);
        Chef chef = new Chef(menu);
        System.out.println(chef.cook());
    }
}

DaggerMainActivity

public class DaggerMainActivity extends DaggerActivity {
    @Inject
    Chef chef;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG,chef.cook());
    }
}

能够看到,在使用Dagger2的时候,使用者的代码会变得很是简洁。可是,Dagger 2还须要一些列的辅助代码来实现依赖注入的。若是用过Dagger2就知道要实现依赖注入的话,须要写十分多模版代码。那么咱们可不能够用更简单的方式使用Dagger2呢?今天笔者就来介绍一下在Android上使用Dagger2的更简洁的方案。

咱们先来看看在DaggerMainActivity上实现依赖注入还须要哪些代码。

CookModules

@Module
public class CookModules {

    @Singleton
    @Provides
    public Map<String, Boolean> providerMenus(){
        Map<String, Boolean> menus = new LinkedHashMap<>();
        menus.put("酸菜鱼", true);
        menus.put("土豆丝", true);
        menus.put("铁板牛肉", true);
        return menus;
    }
}

ActivityModules

@Module
abstract class ActivityModules {

    @ContributesAndroidInjector
    abstract MainActivity contributeMainActivity();
}

CookAppComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ActivityModules.class,
        CookModules.class})
public interface CookAppComponent extends AndroidInjector<MyApplication> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<MyApplication>{}

}

MyApplication

public class MyApplication extends DaggerApplication{

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerCookAppComponent.builder().create(this);
    }
}

Dagger2 For Android 使用要点分析

  1. CookModules
    CookModule很简单,它的目的就是经过@Providers注解提供Menu对象须要的数据。由于Menu是须要依赖一个Map对象的,因此咱们经过CookModules给它构造一个Map对象,并自动把它注入到Menu实例里面。
  2. ActivityModules
    ActivityModules的主要做用就是经过@ContributesAndroidInjector来标记哪一个类须要使用依赖注入功能,这里标记的是ManActivity,因此MainActivity能经过@Inject注解来注入Chef对象。
  3. CookAppComponent
    CookAppComponent至关于一个注射器,咱们前面定义的Modules就是被注射的类,使用@Inject注入对象的地方就是接收者类。
  4. MyApplication
    MyAppliction的特色是继承了DaggerAppliction类,而且在applicationInjector方法中构建了一个DaggerCookAppComponent注射器。

这就是Dagger 2在Android中的使用方案了,在这里咱们能够看到,接收这类(MainActivity)中的代码很是简单,实现依赖注入只使用了:

@Inject
Chef chef;

在接收类里面彻底没有多余的代码,若是咱们要拓展能够SecondsActivity的话,在SecondsActivity咱们要用到Menu类。

那么咱们只须要在ActivityModules中增长:

@ContributesAndroidInjector
abstract SecondsActivity contributeSecondsActivity();

而后在SecondsActivity注入Menu:

@Inject
Menu menu;

能够看到,对于整个工程来讲,实现使用Dagger2 For Android实现依赖注入要写的模版代码其实很是少,很是简洁。只须要进行一次配置就能够,不须要频繁写一堆模版代码。总的来讲,Dagger2形成模版代码增长这个问题已经解决了。

Demo地址:目录下的Dagger2Simple就是Demo地址,上面的例子为Dagger2Simple中的simple Modules

Dagger2的优点

在这里咱们总结下使用Dagger2带来的优势。

  1. 减小代码量,提升工做效率
    例如上面的例子中,咱们构建一个Chef对象的话,不使用Dagger2的状况下,须要在初始化Chef对象以前进行一堆前置对象(Menu、Map)的初始化,而且须要手工注入到对应的实例中。你想像下,若是咱们再加一个Restaurant( 餐馆 )对象,而且须要把Chef注入到Restaurant中的话,那么初始化Restaurant对象时,须要的前置步骤就更繁琐了。
    可能有人会以为,这也没什么啊,我不介意手工初始化。可是若是你的系统中有N处须要初始化Restaurant对象的地方呢?使用Dagger2 的话,只须要用注解注入就能够了。
  2. 自动处理依赖关系
    使用Dagger2的时候,咱们不须要指定对象的依赖关系,Dagger2会自动帮咱们处理依赖关系(例如Chef须要依赖Menu,Menu须要依赖Map,Dagger自动处理了这个依赖关系)。
  3. 采用静态编译,不影响运行效率
    由于Dagger2是在编译期处理依赖注入的,因此不会影响运行效率在必定的程度上还能提升系统的运行效率(例如采用Dagger2实现单例,不用加锁效率更高)。
  4. 提升多人编程效率
    在多人协做的时候,一我的用Dagger2边写完代码后,其它全部组员都能经过@Inject注解直接注入经常使用的对象。加快编程效率,而且能大大增长代码的复用性。

上面咱们介绍完了Dagger2 For Android的基本用法了。可能有些读者意犹未尽,以为这个例子太简单了。那么咱们来尝试下构建一个更加复杂的系统,深度体验下Dagger2 For Android的优点。如今咱们在上面这个例子的基础上拓展下,尝试开发一个简单的点餐Demo来深度体验下。

Dagger2应用实战

如今咱们来看下如何使用Dagger2来开发一个简单的Demo,这里笔者开发的Demo是一个简单的点餐Demo。这个Demo的功能很是简单,提供了菜单展现、菜单添加/编辑/删除和下单功能。而下单功能只是简单地把菜品名用Snackbar显示到屏幕上。

Demo展

操做展现

Demo地址:目录下的Dagger2Simple就是Demo地址,上面的例子为Dagger2Simple中的order Modules

代码目录

这个Demo采用经典的MVP架构,咱们先来简单分析下Demo的细节实现。

  1. 使用SharedPreferences提供简单的缓存功能(存储菜单)。
  2. 使用Gson把列表序列化成Json格式数据,而后以String的形式保存在SharedPreferences中。
  3. 使用Dagger2实现依赖注入功能。

这样基本就实现了一个简单的点菜Demo了。

Dagger在Demo中的应用解释

当咱们使用SharedPreferences和Gson实现缓存功能的时候咱们会发现,项目中不少地方都会须要这个SharedPreferences和Gson对象。因此咱们能够得出两个结论:

  1. 项目中多个模块会用到一些公共实例。
  2. 这些公共实例应该是单例对象。

咱们看看是如何经过使用Dagger2提供全局的Modules来实现这类型对象的依赖注入。

CookAppModules

@Module
public abstract class CookAppModules {

    public static final String KEY_MENU = "menu";
    private static final String SP_COOK = "cook";

    @Singleton
    @Provides
    public static Set<Dish> providerMenus(SharedPreferences sp, Gson gson){
        Set<Dish> menus;
        String menuJson = sp.getString(KEY_MENU, null);
        if (menuJson == null){
            return new LinkedHashSet<>();
        }
        menus = gson.fromJson(menuJson, new TypeToken<Set<Dish>>(){}.getType());
        return menus;
    }

    @Singleton
    @Provides
    public static SharedPreferences providerSharedPreferences(Context context){
        return context.getSharedPreferences(SP_COOK, Context.MODE_PRIVATE);
    }

    @Singleton
    @Provides
    public static Gson providerGson(){
        return new Gson();
    }

    @Singleton
    @Binds
    public abstract Context context(OrderApp application);

}

在这里以dishes模块为例子,dishes中DishesPresenter是负责数据的处理的,因此咱们会在DishesPresenter注入这些实例。

DishesPresenter

public class DishesPresenter implements DishesContract.Presenter{

   private DishesContract.View mView;

   @Inject
   Set<Dish> dishes;

   @Inject
   Gson gson;

   @Inject
   SharedPreferences sp;

   @Inject
   public DishesPresenter(){

   }

   @Override
   public void loadDishes() {
       mView.showDishes(new ArrayList<>(dishes));
   }

   @Override
   public String order(Map<Dish, Boolean> selectMap) {
       if (selectMap == null || selectMap.size() == 0) return "";
       StringBuilder sb = new StringBuilder();

       for (Dish dish : dishes){
           if (selectMap.get(dish)){
               sb.append(dish.getName()).append("、");
           }
       }
       if (TextUtils.isEmpty(sb.toString())) return "";

       return "烹饪: " + sb.toString();
   }

   @Override
   public boolean deleteDish(String id) {
       for (Dish dish : dishes){
           if (dish.getId().equals(id)){
               dishes.remove(dish);
               sp.edit().putString(CookAppModules.KEY_MENU, gson.toJson(dishes)).apply();
               return true;
           }
       }
       return false;
   }


   @Override
   public void takeView(DishesContract.View view) {
       mView = view;
       loadDishes();
   }

   @Override
   public void dropView() {
       mView = null;
   }
}

上面的代码能很好地体验Dagger2的好处,假如咱们项目中有比较复杂的对象在不少地方都会用到的话,咱们能够经过这种方式来简化咱们的代码。

Dishes模块的UI是由Activity加Fragment实现的,Fragment实现了主要的功能,而Activity只是简单做为Fragment的外层。它们分别是:DishesActivity和DishesFragment

DishesActivity依赖了DishesFragment对象,而在DishesFragment则依赖了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter对象。

咱们先来分别看看DishesActivity与DishesFragment的关键代码。

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

    @Inject
    DishesFragment mDishesFragment;
    
    ...
}

DishesFragment

public class DishesFragment extends DaggerFragment implements DishesContract.View{

    RecyclerView rvDishes;

    @Inject
    DishesAdapter dishesAdapter;

    @Inject
    RecyclerView.LayoutManager layoutManager;

    @Inject
    DishesContract.Presenter mPresenter;
    
    @Inject
    public DishesFragment(){

    }
    
 }

DishesFragment经过Dagger2注入了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter,而这些实例是由DishesModules提供的。

DishesModules

@Module
public abstract class DishesModules {

    @ContributesAndroidInjector
    abstract public DishesFragment dishesFragment();

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }
    
    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}

这里咱们先说明下这几个注解的做用。

  • @ContributesAndroidInjector
    你能够把它当作Dagger2是否要自动把须要的用到的Modules注入到DishesFragment中。这个注解是Dagger2 For Android简化代码的关键,下面的小节会经过一个具体例子来讲明。
  • @Module
    被这个注解标记的类能够看做为依赖对象的提供者,能够经过这个被标记的类结合其它注解来实现依赖关系的关联。
  • @Provides
    主要做用就是用来提供一些第三方类库的对象或提供一些构建很是复杂的对象在Dagger2中相似工厂类的一个角色。
  • @Binds
    主要做用就是肯定接口与具体的具体实现类,这样说得比较抽象,咱们仍是看看例子吧。
    在DishesFragment中有这么一句代码:

    @Inject
    DishesContract.Presenter mPresenter;

    咱们知道DishesContract.Presenter是一个接口而这个接口可能有不少不一样的实现类,而@Binds的做用就是用来肯定这个具体实现类的。以看看PresenterModules的代码:

    @Module
    public abstract class PresenterModules {
        @Binds
        abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);
    
        ...
    }

    从这句代码能够看出,使用@Inject注入的DishesContract.Presenter对象的具体实现类是DishesPresenter。

Dagger2 For Android是如何注入依赖的?

咱们在用Dagger2的时候是经过一些模版代码来实现依赖注入的( DaggerXXXComponent.builder().inject(xxx) 这种模版代码),可是在Demo中的DishesFragment根本没看到相似的代码啊,那么这些对象是何时注入到DishesFragment重的呢?

答案就是@ContributesAndroidInjector注解

咱们先来看看Dagger2是经过什么方式来实现自动把依赖注入到DishesActivity中的。

ActivityModules

@Module
public abstract class ActivityModules {

    @ContributesAndroidInjector(modules = DishesModules.class)
    abstract public DishesActivity contributesDishActivity();

    @ContributesAndroidInjector(modules = AddEditModules.class)
    abstract public AddEditDishActivity contributesAddEditDishActivity();

}

没错,就是@ContributesAndroidInjector这个注解,modules就表明这个DishesActivity须要依赖哪一个Modules。这篇教程咱们不解释它的具体实现原理,你只须要知道@ContributesAndroidInjector的做用就能够了。

咱们之前使用Dagger2的时候,须要些不少Component来辅助咱们实现依赖注入,而如今咱们整个App中只须要写一个Component就能够了。@ContributesAndroidInjector注解会帮助咱们生成其它须要的Component,而且自动处理Component之间的关系,自动帮咱们使用生成的Component来注入依赖。

咱们先看看咱们如今整个模块中惟一存在的Component是怎么使用的。

OrderAppComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        LayoutManagerModules.class,
        CookAppModules.class,
        PresenterModules.class,
        ActivityModules.class})
public interface OrderAppComponent extends AndroidInjector<OrderApp>{

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<OrderApp>{
    }

}

OrderApp

public class OrderApp extends DaggerApplication {


    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerOrderAppComponent.builder().create(this);
    }
}

为了加深你们对@ContributesAndroidInjecto注解r的理解,咱们稍微修改下DishesModules

@Module
public abstract class DishesModules {

    //@ContributesAndroidInjector
    //abstract public DishesFragment dishesFragment();

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }

    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

    //@Inject
    DishesFragment mDishesFragment;

    Toolbar toolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dishes);

        DishesFragment dishesFragment
                = (DishesFragment) getSupportFragmentManager().findFragmentById(R.id.content_fragment);

        if (dishesFragment == null){
            mDishesFragment = new DishesFragment();//新增代码
            dishesFragment = mDishesFragment;
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), dishesFragment, R.id.content_fragment);
        }
        initView();

    }
    ...
}
//DaggerFragment改成Fragment
public class DishesFragment extends Fragment implements DishesContract.View{
}

这个时候,咱们运行的时候会发现,DishesFragment中的依赖注入失败了,运行时会抛出空指针异常,没注入须要的数据。致使这个缘由是由于咱们在这里使用new来建立DishesFragment实例的,为何使用new的时候会Dagger2没有帮咱们注入实例呢?

当咱们使用@Inject来注入DishesFragment的时候,Dagger2会自动帮咱们判断DishesFragment所依赖的对象(@Inject注解标记),若是能直接注入的对象则直接注入到Fragment中,不然则从DishesModules中寻找是否有须要的对象,有的话则注入到DishesFragment中。而咱们使用new来建立DishesFragment时Dagger2没法经过DishesModules来查找对象,由于咱们没有声明DishesFragment与DishesModules的联系,DishesFragment也没有自动注入注解的标记( 没有实现HasSupportFragmentInjector )。因此Dagger2没法判断它们依赖关系也没办法自动帮DishesFragment自动注入依赖。

若是咱们坚持要使用new的方式来依赖DishesFragment的话,则能够经过@ContributesAndroidInjecto注解来实现它们之间的关联。具体实现方式以下:

DishesModules

@Module(includes = PresenterModules.class)
public abstract class DishesModules {

    @ContributesAndroidInjector
    abstract public DishesFragment dishesFragment(); //增长这个抽象方法

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }

    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}

DishesFragment继承于DaggerFragment

public class DishesFragment extends DaggerFragment implements DishesContract.View{
    ...
}

改为这样,咱们经过new方法来建立DishesFragment的时候也能实现经过注解进行依赖注入了,为何会这样呢?由于@ContributesAndroidInjector的做用时帮咱们生成须要的Subcomponent,而后在DaggerFragment经过 DispatchingAndroidInjector<Fragment> 对象来实现依赖注入( 底层原理和咱们使用DaggerXXXComponent手动实现依赖注入差很少 )。咱们能够看看DishesModules中被@ContributesAndroidInjector注解的方法生成的代码。

@Module(subcomponents = DishesModules_DishesFragment.DishesFragmentSubcomponent.class)
public abstract class DishesModules_DishesFragment {
  private DishesModules_DishesFragment() {}

  @Binds
  @IntoMap
  @FragmentKey(DishesFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment> bindAndroidInjectorFactory(
      DishesFragmentSubcomponent.Builder builder);

  @Subcomponent
  public interface DishesFragmentSubcomponent extends AndroidInjector<DishesFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DishesFragment> {}
  }
}

能够看出,编生成的代码符合咱们上面的结论。

Dagger2 For Android使用要点

咱们如今来总结下,简化版的Dagger实现依赖注入的几个必要条件:

  1. 第三方库经过Modules的@provides注解来提供依赖
  2. 提供一个全局惟一的Component,而且Modules中须要天际AndroidSupportInjectionModule类,它的做用时关联需求与依赖之间的关系
  3. Application须要继承DaggerApplication类,而且在applicationInjector构建并返回全剧惟一的Component实例
  4. 其它须要使用依赖注入的组建都须要继承Dagger组件名字类,而且须要在相应的Modules中经过@ContributesAndroidInjector注解标记须要注入依赖的组建。

上面四个步骤就是使用Dagger2实现依赖注入的要点了,总的来讲,复杂度比以前的方法简单了很是多,要写的模版代码也减小了很是多。

通常来讲,上面的知识点已经足够让咱们在项目中正常使用Dagger2了,可是在使用中还会遇到一些其它的问题,Dagger2也提供了解决方法。若是但愿进一步了解的话,能够继续阅读下文。

Dagger2拓展

@Scope

Scope字面的意思是做用域,在咱们使用Dagger2的时候常常会用到@Singleton这个注解,这个注解的意思的做用是提供单例对象。而咱们在使用@Singleton这个注解的时候,会同时@Provides和@Component,为何要这样作呢?由于@Scope的做用范围其实就是单例的做用范围,这个范围主要是经过Component来肯定的。

因此@Scope的做用就是以指定Component的范围为边界,提供局部的单例对象。咱们能够以上面的例子为例验证这个论点论点。

咱们在DishesActivity中增长一句代码,做用时注入DishesPresneter对象。

@Inject
DishesContract.Presenter mPresenter;

从上面的代码中,咱们知道DishesFragment中也用一样的方式来注入过DishesPresneter对象,那么它们有什么区别的,咱们经过调试功能来看下。

能够看出,DishesActivity和DishesFragment中的DishesPresenter不是同一个实例,它们的内存地址是不同的。若是咱们在PresenterModules的dishesPresenter方法中加上@Singleton

@Singleton
@Binds
abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);

能够预见,DishesActivity和DishesFragment中的DishesPresenter会变成同一个实例,在这个例子中@Singleton的做用是提供全局的单例( 由于OrderAppComponent这个全局惟一的Component也被标注成@Singleton )。这种用法比较简单,这里再也不深刻。而比较难理解的就是自定义Scope了,下面咱们经过一个例子来加深你们对自定义Scope的理解。

@DishesScoped

@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface DishesScoped {
}

为了使测试效果更明显,咱们稍微修改下Order这个Demo。

DishesModules

@Module
public abstract class DishesModules {
   ...
    @DishesScoped  // 添加注解
    @Binds
    abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);
   ...

}

ActivityModules

@Module
public abstract class ActivityModules {

    @DishesScoped  // 添加注解
    @ContributesAndroidInjector(modules = DishesModules.class)
    abstract public DishesActivity contributesDishActivity();
}

而后如今咱们来运行Demo,看下DishesActivity和DishesFragment中的DishesContract.Presenter的对象:

能够看出,它们是同一个对象,这验证了咱们上面的结论。这里又个小问题就是,咱们以前说@Scope是经过Component来肯定做用边界的,可是上面这个例子中,并无对任何Component类使用@Dishes注解啊?那么这里是如何确认边界的呢?

咱们能够看看Dagger生成的类ActivityModules_ContributesDishActivity,这个类是根据ActivityModules中的contributesDishActivity方法生成的。

@Module(subcomponents = ActivityModules_ContributesDishActivity.DishesActivitySubcomponent.class)
public abstract class ActivityModules_ContributesDishActivity {
  private ActivityModules_ContributesDishActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(DishesActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      DishesActivitySubcomponent.Builder builder);

  @Subcomponent(modules = DishesModules.class)
  @DishesScoped   //看这里
  public interface DishesActivitySubcomponent extends AndroidInjector<DishesActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DishesActivity> {}
  }
}

谜底揭晓,当咱们为contributesDishActivity添加上@DishesScoped注解后,自动生成的DishesActivitySubcomponent类被@DishesScoped注解了。因此@DishesScoped是经过DishesActivitySubcomponent来确认做用范围的,这也符合上面的结论。

@Scope的实现原理

@Scope实现单例的原理其实很简单,咱们能够看下加了@DishesScoped后Dagger为咱们生成的注入辅助代码。在这里咱们只看关键方法:

private void initialize(final DishesActivitySubcomponentBuilder builder) {
      this.dishesFragmentSubcomponentBuilderProvider =
          new Provider<DishesModules_DishesFragment.DishesFragmentSubcomponent.Builder>() {
            @Override
            public DishesModules_DishesFragment.DishesFragmentSubcomponent.Builder get() {
              return new DishesFragmentSubcomponentBuilder();
            }
          };
      this.dishesPresenterProvider =
          DishesPresenter_Factory.create(
              DaggerOrderAppComponent.this.providerMenusProvider,
              DaggerOrderAppComponent.this.providerGsonProvider,
              DaggerOrderAppComponent.this.providerSharedPreferencesProvider);
      this.dishesPresenterProvider2 = DoubleCheck.provider((Provider) dishesPresenterProvider);   //这句代码是实现单例的关键。
    }

能够看到,咱们的dishesPresenterProvider2这个对象的初始化是经过双锁校验的方式来实现单例的,因此这个对象是一个单例对象。而其它没有使用@Spoce注解的类则没有使用双锁校验的方式实现初始化,Dagger经过@Scope实现单例的原理其实很是简单。关于@Spoce的介绍就到这里了,若是须要深刻的话,能够进一步查看Dagger2生成的辅助代码。

@Qualifier和@Named注解

除了做用域的问题以外咱们还会常常会遇到一个问题,总所周知,Dagger2是自动判断依赖关系的,若是咱们的代码中须要使用同一个类生成两个或多个不一样的对象呢?例如咱们的LinearManager,咱们如今想用Dagger提供一个横向的Manager,若是直接写在项目中是会报错的,由于Dagger没法判断须要注入/依赖的对象是哪一个。以下面的代码:

LayoutManagerModules

@Module
public class LayoutManagerModules {

    @Provides
    public LinearLayoutManager providesLinearLayoutManager(Context context){
        return new LinearLayoutManager(context);
    }
    
    @Provides 
    public LinearLayoutManager providesHorizonalLinearLayoutManager(Context context){
        return new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
    }

}

这段代码确定是会报错的,若是咱们想实现这个功能的话,这个时候咱们就须要用到@Qualifier或者@Named注解了。

咱们先用@Named来实现上面这个需求。

LayoutManagerModules

@Module
public class LayoutManagerModules {

    @Named("vertical")
    @Provides
    public LinearLayoutManager providesLinearLayoutManager(Context context){
        return new LinearLayoutManager(context);
    }

    @Named("horizontal")
    @Provides
    public LinearLayoutManager providesHorizonalLinearLayoutManager(Context context){
        return new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
    }


}

DishesModules

public class DishesFragment extends DaggerFragment implements DishesContract.View{

    RecyclerView rvDishes;

    @Inject
    DishesAdapter dishesAdapter;

    @Named("horizontal")
    @Inject
    LinearLayoutManager layoutManager;
}

在注入的时候,咱们经过 @Named("horizontal")就能控制实际是注入哪一个LayoutManager了。在定义依赖的时候@Name注解要配合@Providers,而在使用的时候配合@Inject来使用。

@Qualifier

@Qualifier的做用和@Named是同样的,@Name也被@Qualifier注解。在使用@Named的时候须要加上咱们定义的key因此略显麻烦,咱们能够经过自定义@Qualifier注解来解决这个问题。而自定义@Qualifier注解的方式和自定义@Spoce是同样的,很是简单,这里不做深刻介绍了。

Dagger2还提供了例如懒加载等功能,使用起来都是比较简单的,这里限于篇幅就不做进一步介绍了。有兴趣的读者能够查阅源码或者看官方文档来体验下。

小结

Dagger2 For Android是一款很是适合移动端使用的依赖注入框架。它提供了静态编译的方式来实现依赖注入,性能很是好。而且最新版本的Dagger 2.17对Android提供了很是友好的支持,如今使用Dagger2的时候,咱们不须要再手写注入代码,这一切Dagger2都帮咱们自动实现了。总的来讲,Dagger2是很是适合于应用到咱们的项目中的。而且Dagger2实现依赖注入的方式很是有趣,能掌握这项技术的话,对咱们的提高是很是大的,但愿各位读者在阅读了本文后可以去体验一下。

若是这篇文章对你有帮助的话,能够关注下笔者其它的文章,欢迎你们在个人github上面点star哦。

给你一个全自动的屏幕适配方案(基于SW方案)!—— 解放你和UI的双手

Gradle自动实现Android组件化模块构建

技术教程Demo地址(本文的Demo也在里面哟)👍

你的支持,是我前进的动力,若是个人项目对您有帮助的话,能够点下star👍

微信公众号:

若是但愿第一时间收到个人技术文章更新的同窗,能够扫描下面二维码关注个人我的公众号:代码以外的程序员

或收藏个人我的博客:TANG BLOG

相关文章
相关标签/搜索