Dagger2 依赖的接力游戏(七)完结篇 :dagger-android框架使用&解析

文接《Dagger2 依赖的接力游戏(六)》

经过前面的介绍,我们对dagger2的结构和原理已经有了比较清楚的了解,但是dagger2直接拿来在android平台上使用,还会有些问题,因为android平台定义了很多框架组件,是没法直接创建实例的。因此google针对android平台特地封装了一套模板库dagger-android,来帮我们实现框架组件的依赖注入。这篇文章,我们从头开始讲解如何使用这个依赖库,为什么要使用这个依赖库,以及这个依赖库是怎么工作的。

示例介绍


我们会通过一个示例项目,来讲解这篇文章的内容。这个项目实现一个非常简单的随机展示一个汽车对象的功能,像这样:

功能示意图

我们点击一下NEXT CAR按钮,就会展示名称和气缸数目随机的另外一个Car对象。

这个示例,我们分为三个阶段来完成,第一个阶段,我们不使用dagger框架,直接是正常的mvp + repository架构;第二个阶段,我们使用dagger2对前面的代码进行依赖优化;第三个阶段,我们针对dagger2优化过的依赖,讲解依旧存在的一些问题点,并使用dagger-android库进行优化,然后进行原理解析。每个阶段,我们都会进行依赖关系的分析,通过逐步演进的方式,体现优化的必要性。

数据部分

  • Car 和 Engine类
    这部分内容我们复用以前项目的基础类型

  • 定义CarResouce接口用于获取一个随机的Car对象

public interface CarResource {
    Car getRandomCar();
}

结构部分

  • mvp结构
public interface IView {}

public interface IPresenter {
    void takeView(IView baseView);
    void dropView();
}

public interface HomeContract {
    interface IHomeView extends IView {
        void showCar(Car car);
    }
    interface IHomePresenter extends IPresenter {
        void start();
        void nextRandomCar();
    }
}
  • App结构

我们使用Application + Activity + Fragment的常见结构来完成这个示例,Activity作为容器,Fragment作为MVP的实现层。这里先看下布局文件:

  1. Activity布局 activity_home.xml :



    


  1. Fragment布局 fragment_home.xml :



    

    

正常模式


本节示例代码收录在项目的sample-without-dagger2分支

数据部分

数据部分我们直接实现一个CarRepository,并将它做成单例:

  • 代码实现
public class CarRepository implements CarResource {

    private static volatile CarRepository mInstance;

    private CarRepository() {

    }

    public static CarRepository getInstance() {
        if (mInstance == null) {
            synchronized (CarRepository.class) {
                if (mInstance == null) {
                    return new CarRepository();
                }
            }
        }
        return mInstance;
    }

    @Override
    public Car getRandomCar() {
        String name = UUID.randomUUID().toString().substring(0, 4);
        int cylinders = (int) (Math.random() * 10);
        Car car = new Car(new Engine(cylinders));
        car.setName(name);
        return car;
    }
}
  • 问题分析

我们在Repo里直接创建Car对象,对Car和Engine类产生了直接依赖,这部分是可以使用Dagger进行代理创建的。但是这样的话,由于我们的示例过于简单,只有随机产生Car对象这个一个功能,我们Repo层就没有存在的必要了。但是在实际的对象维护中,还会涉及更复杂的操作,比如添加Car,编辑Car,更新Car,删除Car等等,Repo层的封装还是必要的,所以我们在这里保留这部分的逻辑,着重关注Android组件的依赖注入。

我们在CarRepo里还手动实现了单例模式,这个让我们想到dagger的singleton功能,我们可以对它进行优化。因为我们的数据仓库是全局唯一的,我们可以放在AppComponent层来提供它。

MVP实现

  • Activity实现
public class HomeActivity extends AppCompatActivity {


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

        Fragment fragment = new HomeFragment(new HomePresenter(CarRepository.getInstance()));

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container_home, fragment)
                .commit();
    }
}
  • Activity问题分析

    这里的依赖问题是最多的,我们为了装配MVP组合,直接依赖了HomeFragment、HomePresenter、CarRepository三个实现类型,我们需要对这三个类型进行解耦。

    我们分析一下,这里的目的就是获取一个Fragmet,然后加载它,实际上它不需要Fragmet以外的其他任何类型和对象,而之所以这里这么臃肿,正式因为我们采用直接依赖和手动注入的方式,让这里多出了很多不必要的类型依赖。所以我们为它做一个HomeActivityComponent,进行Fragment的注入。

  • Framgment实现

public class HomeFragment extends Fragment implements HomeContract.IHomeView, View.OnClickListener {

    HomeContract.IHomePresenter mHomePresenter;

    private TextView mTvCarInstruction;
    private Button mBtnNextCar;

    public HomeFragment() {

    }

    @SuppressLint("ValidFragment")
    public HomeFragment(HomeContract.IHomePresenter presenter) {
        mHomePresenter = presenter;
    }

    @Override
    public void onResume() {
        super.onResume();
        mHomePresenter.takeView(this);
        mHomePresenter.start();
    }


    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        mTvCarInstruction = view.findViewById(R.id.tv_car_instruction);
        mBtnNextCar = view.findViewById(R.id.btn_next_car);
        mBtnNextCar.setOnClickListener(this);
        return view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mHomePresenter.dropView();
    }

    @Override
    public void showCar(Car car) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Car : ").append("\n")
                .append("Name : ").append(car.getName()).append("\n")
                .append("Engine cylinders : ").append(car.getEngine().getCylinderNumbers());
        mTvCarInstruction.setText(stringBuilder.toString());
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btn_next_car) {
            mHomePresenter.nextRandomCar();
        }
    }
}
  • Fragment问题分析

我们的Fragment依赖的Presenter,是通过构造方法传进来的,可以看到构造方法上面还有@SuppressLint("ValidFragment")注解,否则AndroidStuido会提示你不要使用带参的构造方法来创建Fragment,而使用默认的构造方法 + Fragment.setArguments来实现参数的传递,否则设备配置参数发生变化时(比如屏幕旋转),Activity重新实例化Fragment的时候,会调用默认构造方法,从而导致参数丢失。

所以这里我们有两个选择,一个是在Fragment中直接创建Presenter,这就产生了直接依赖,所以我们采用第二种,使用dagger进行依赖注入。我们可以把Fragment的注入需求放在HomeActivityComponent里来实现,但是这样Fragment和Activity就形成了绑定关系了,不能被其他页面复用,所以我们还得独立给它做一个HomeFragmentComponent。

  • Presenter 实现
public class HomePresenter implements HomeContract.IHomePresenter {

    private HomeContract.IHomeView mView;

    private CarResource mCarResource;

    public HomePresenter(CarResource carResource) {
        mCarResource = carResource;
    }

    @Override
    public void takeView(IView iView) {
        mView = (HomeContract.IHomeView) iView;
    }

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

    @Override
    public void start() {
        nextRandomCar();
    }

    @Override
    public void nextRandomCar() {
        mView.showCar(mCarResource.getRandomCar());
    }
}
  • Presenter问题分析

    Presenter在类型依赖上没啥问题,它唯一依赖的CarResource是外部传入的,但是如果我们通过参数来注入,外部势必就会依赖某一个直接的类型,所以我们也通过dagger来注入。

问题总结

我们在上面分段总结了各个部分的依赖问题及解决方案,总结起来就是使用AppComponent、HomeActivityComponent、HomeFragmentComponent,因为我们要把CarRepository做成单例,所以我们把后面的Component做成前面子组件,否则会创建多个CarRepository的实例。下面我们使用这个方式来改造我们的实现。

Dagger2模式


本节示例代码收录在项目的sample-with-dagger2分支

App注入

AppComponent实现:

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {

    CarResource getCarResource();

    HomeActivityComponent.Builder homeActivityComponent();

    @Component.Builder
    interface Builder {
        AppComponent build();
    }

}

HomeActivityComponent是我们用于注入Activity的组件,这里把它做成子组件,保证它使用AppComponent里的CarResource。然后我们声明Singleton作用域,保证CarResource是一个单例。我们再看看Module实现:

@Module(subcomponents = HomeActivityComponent.class)
public abstract class AppModule {
    @Binds
    abstract CarResource carResource(CarRepository carRepository);
}

Module中我将HomeActivityComponent声明为子组件,并通过Binds将CarResource的需求嫁接到CarRepository实现上。

@Singleton
public class CarRepository implements CarResource {

    @Inject
    public CarRepository() {
    }

    @Override
    public Car getRandomCar() {
        String name = UUID.randomUUID().toString().substring(0, 4);
        int cylinders = (int) (Math.random() * 10);
        Car car = new Car(new Engine(cylinders));
        car.setName(name);
        return car;
    }
}

CarRepository中去掉了单例的实现,使用Singleton来实现,是不是清爽很多?我们看看调用代码:

public class CarApp extends Application {

    AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.builder().build();
    }

    public AppComponent appComponent() {
        return mAppComponent;
    }
}

我们创建一个AppComponent实例,将它维护起来,让其他地方获取。

Activity注入

HomeActivityComponent实现:

@Subcomponent(modules = HomeActivityModule.class)
public interface HomeActivityComponent {

    void inject(HomeActivity activity);

    HomeFragmentComponent.Builder homeFragmentComponent();

    @Subcomponent.Builder
    interface Builder {
        HomeActivityComponent build();
    }
}

因为我们要让Fragment也使用AppComponent中的CarResource,所以我们也把Fragment做成Activity的子组件。我们看看Module实现:

@Module(subcomponents = HomeFragmentComponent.class)
public interface HomeActivityModule {

    @Binds
    Fragment provideHomeFragment(HomeFragment fragment);

    @Binds
    HomeContract.IHomePresenter providePresenter(HomePresenter homePresenter);

}

我们在Activity中并没有用到Presenter,但是这里却提供了Presenter的依赖,这是因为父组件依赖了HomeFragment,而HomeFragment依赖的是HomeContract.IHomePresenter类型,由于父组件自身的依赖必须是完整的,如果没有这个类型绑定,就会编译失败。同样,这里声明了HomeFragmentComponent作为自己的子组件。我们看看Activity中的调用代码:

public class HomeActivity extends AppCompatActivity {

    HomeActivityComponent mComponent;

    @Inject
    Fragment mHomeFragment;

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

        mComponent = ((CarApp) (getApplication())).appComponent()
                .homeActivityComponent().build();

        mComponent.inject(this);

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container_home, mHomeFragment)
                .commit();
    }

    public HomeActivityComponent getComponent() {
        return mComponent;
    }
}

这里和AppComponent中一样,我们要把自己的Component保存起来,以便Framgent中使用。

Fragment注入

FragmentComponent实现:

@Subcomponent
public interface HomeFragmentComponent {

    void inject(HomeFragment homeFragment);

    @Subcomponent.Builder
    interface Builder {
        HomeFragmentComponent build();
    }
}

因为我们的Presenter在父组件中已经绑定过了,所以我们这里不需要Module,我们再看看调用:

public class HomeFragment extends Fragment implements HomeContract.IHomeView, View.OnClickListener {

    @Inject
    HomeContract.IHomePresenter mHomePresenter;

    private TextView mTvCarInstruction;
    private Button mBtnNextCar;

    @Inject
    public HomeFragment() {

    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        HomeActivity homeActivity = (HomeActivity) getActivity();

        homeActivity.getComponent().homeFragmentComponent().build().inject(this);
    }

    //省略相同部分代码
}

最后看看Presenter的注入实现:

public class HomePresenter implements HomeContract.IHomePresenter {

    private HomeContract.IHomeView mView;

    private CarResource mCarResource;

    @Inject
    public HomePresenter(CarResource carResource) {
        mCarResource = carResource;
    }
    //省略相同部分代码
}

问题分析

经过上述的改造,我们已经实现了使用dagger来完成我们的单例和依赖注入,和第一版比起来,我们已经没有了对各种实现类型的直接依赖了,而是使用dagger来注入我们要使用的对象,简洁很多,但是还是存在问题。

对比HomePresenter的依赖注入,我们会发现Fragment和Acitivty的依赖注入需要我们手动调用组件进行注入,而HomePresenter在创建的时候dagger自动将它依赖的对象构建好,并通过构造方法初始化好了。这就暴露出了dagger2一个问题,dagger2在管理我们自己创建的对象的时候,可以通过构造方法自动补全依赖,进而托管我们全部的依赖构造,而在遇到不能自己创建对象的时候,我们只能在对象的某个回调中,对对象进行依赖注入。这就会导致两个问题:

  1. 我们需要创建很多个SubComponent来实现对各个android组件的注入
  2. 我们需要手动获取父组件的Component来构建自己的Component,实现注入过程,当组件多的时候,这会很难维护,并且还会出现类型依赖问题。比如我们的HomeFragment就依赖了HomeActivity类型,导致它无法被其他Activity复用。
  3. 我们定义了组件的依赖层次,同时使用子组件来实现了它们,但是并有Scope来约束它们,这是不安全的,同层次的组件也可能会互相依赖,是有可能会导致对象反向持有,引起内存泄露的。

优化策略

1. AndroidInjector + @ContributesAndroidInjector

针对第一个问题,dagger-android采用了一个策略,它定义了一个模板类AndroidInjector用来帮我们生成组件的注入Component,并使用ContributesAndroidInjector提供自动生成的功能, 这里稍后我们结合例子进行使用和原理讲解。

2. HasXXXInjector + DaggerXXX(XXX代表android各种组件)

第二个问题其实可以拆分成两个部分。

第一部分是类型依赖问题,dagger-android库将我们依赖的Application和Activity等框架组件类型抽象成了HasXXXInjector,避免父类行的暴露和依赖。我们举个Fragment的例子来看看:

@Beta
public interface HasFragmentInjector {

  /** Returns an {@link AndroidInjector} of {@link Fragment}s. */
  AndroidInjector fragmentInjector();
}

它能够获取一个Fragment注入器,我们看看这个AndroidInjector是啥:

public interface AndroidInjector {

  void inject(T instance);

  interface Factory {
    AndroidInjector create(T instance);
  }

  abstract class Builder implements AndroidInjector.Factory {
    @Override
    public final AndroidInjector create(T instance) {
      seedInstance(instance);
      return build();
    }

    @BindsInstance
    public abstract void seedInstance(T instance);

    public abstract AndroidInjector build();
  }
}

这里我去掉了注释,只看核心部分。首先它定义了一个工厂类接口,用于创建自己,然后定义了一个Builder实现了这个接口。如果给它们分别加上@Component和@Component.Builder注解,它是不是就是一个常规的Component了?所以它实际上就是Component的模板,核心方法就是void inject(T instance),是用来注入模板类的。

代入来说,它将我们的Activity抽象成HasFragmentInjector,我们在Fragment的onCreate方法里,获取通过Activity获取AndroidInject对自己进行注入,只要我们在Fragment中获取的是对应我们Fragment的注入器,就可以利用java多态的功能实现子类型的注入。对于其他的组件,也都放在它们的上一层依赖,比如Activity就是依赖Application。至于怎么获取对应的注入器,和我们第二个问题相关。

第二部分是Component手动获取和手动注入问题,dagger-android库对此,采用了帮我们实现一整套,支持依赖注入的,以Dagger开头的Android组件的方式来解决这个问题。这些组件能够帮我们保存子组件的注入器,能够在生命周期中从父组件获取自己的注入器对自己进行注入,这样就避免了我们手动创建Component和注入了。因为DaggerActivity处于中间层,需要承上启下,我们看看它是怎么实现的:

@Beta
public abstract class DaggerActivity extends Activity implements HasFragmentInjector {

  @Inject DispatchingAndroidInjector fragmentInjector;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }

  @Override
  public AndroidInjector fragmentInjector() {
    return fragmentInjector;
  }
}

很简单是不是?

它维护了一个叫做DispatchingAndroidInjector fragmentInjector
的对象,看名字就知道提供Framgent注入器的。然后在onCreate中调用AndroidInjection.inject方法对自己进行注入。我们先看看AndroidInjection提供的注入方法:

  public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    Application application = activity.getApplication();
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }

    AndroidInjector activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());

    activityInjector.inject(activity);
  }

这个类就是整个dagger-android库的核心机制了,它实现了不同组件的注入逻辑,要了解这个框架,这个类必须得看懂来。这里我们只看Activity的注入代码。因为Activity的父组件就是Application了,所以它获取Application对象,并调用activityInjector()方法获取自己的注入器,然后对自己进行注入。我们看看DaggerApplication实现的activityInjector()方法。

@Beta
public abstract class DaggerApplication extends Application
    implements HasActivityInjector,
        HasFragmentInjector,
        HasServiceInjector,
        HasBroadcastReceiverInjector,
        HasContentProviderInjector {

  @Inject DispatchingAndroidInjector activityInjector;
  @Inject DispatchingAndroidInjector broadcastReceiverInjector;
  @Inject DispatchingAndroidInjector fragmentInjector;
  @Inject DispatchingAndroidInjector serviceInjector;
  @Inject DispatchingAndroidInjector contentProviderInjector;


  @Override
  public DispatchingAndroidInjector activityInjector() {
    return activityInjector;
  }
  //省略部分重复代码
}

我们可以看到DaggerApplication为各大组件都维护了一份injector,它是一个DispatchingAndroidInjector对象,并且用组件对应的方法返回这个对象,而这个对象也是一个AndroidInjector模板类的实现。我们看看DispatchingAndroidInjector如何实现对具体类型进行注入的。

  @Override
  public void inject(T instance) {
    boolean wasInjected = maybeInject(instance);
    if (!wasInjected) {
      throw new IllegalArgumentException(errorMessageSuggestions(instance));
    }
  }

它调用了一个maybeInject,如果不成功,就抛异常,我们看看maybeInject方法:

  public boolean maybeInject(T instance) {
    Provider> factoryProvider =
        injectorFactories.get(instance.getClass());
    if (factoryProvider == null) {
      return false;
    }

    @SuppressWarnings("unchecked")
    AndroidInjector.Factory factory = (AndroidInjector.Factory) factoryProvider.get();
    try {
      AndroidInjector injector =
          checkNotNull(
              factory.create(instance), "%s.create(I) should not return null.", factory.getClass());

      injector.inject(instance);
      return true;
    } catch (ClassCastException e) {
      throw new InvalidInjectorBindingException(
          String.format(
              "%s does not implement AndroidInjector.Factory<%s>",
              factory.getClass().getCanonicalName(), instance.getClass().getCanonicalName()),
          e);
    }
  }

这里有点复杂,我们慢点讲。它的核心代码在try&catch里面,即获取一个AndroidInject来实现对我们传进来的组件实例进行注入。为了获取这个实例,它先获取了AndroidInjectFractory,这个东西是用来创建injector的,忘记了翻一下我们上面贴的AndroidInjector实现。然后还没完,这个Factory是它从factoryProvider里根据instance.class类型取出来的。为啥要这么绕,这么复杂?

通过目前的介绍,我们知道dagger-android库通过一套框架,将我们所有组件的注入需求,通过父组件为子组件提供注入器的形式,层层代理,最后全部搜集到了Application层,便于我们管理。这带来的一个反向问题就是,我们在application初始化的时候,所有的AndroidInjector注入器都要实例化,而这些玩意都是我们的Component,它们的实例化有可能是有不小开销的,并且Component应该跟随它服务对象的生命周期创建和销毁。最关键的是,你翻上去看AnddroidInejctor的实现,它的Builder方法里有个seedInstance是需要组件实例的。

所以我们Application创建的时候,即要作为入口注入我们整个App的依赖注入框架,又不能一次性创建这个注入框架,怎么办?加个中间层呗!这就是AndroidInjectFractory,虽然我无法创建AndroidInjector给我的子组件,因为我没有子组件的实例,但是我给你Factory对象,你把自己代入进去创建你要的Injector就好啦!

那么FactoryProvider这层又是为了啥呢?因为我们的组件是很多的,一个Factory是不够的,所以我们要保存到集合里,还记得我们的Multibind介绍么?这里用的是class索引的Map集合,所以它才在用的时候,根据instance.class取出Factory。我们看下这个provider的定义:

  private final Map, Provider>>
      injectorFactories;

  @Inject
  DispatchingAndroidInjector(
      Map, Provider>> injectorFactories) {
    this.injectorFactories = injectorFactories;
  }

看到了吧,和我们解释的是一样的,并且这个Map是通过构造方法,由Dagger传进来的! 这样你回头看我们的DaggerApplication和DaggerActivty代码,发现它所有的DispatchingAndroidInjector都是dagger注入的。所以这些注入器,最终是我们自己调用AppComponent对Application进行的注入时候实现注入的,只不过框架在组件实例化的时候,帮我们完成了注入器转发。

好了,关于第二点的优化我们就解释完了。看到这里,如果还没有懵逼的同学,会发现还有两个问题:

  1. 为啥AndroidInject里有seedInstance方法?
  2. 我们如何实现在Application里使用AppComponent实现注入器的注入?

这个问题我们在下面部分演示过后进行讲解。

3. ActivityScoped和FragmentScoped

第三个问题就比较简单了,我们直接创建两个专用的Scope,让Activity和Fragment组件各自使用一个就可以了,如果还有别的独立的组件,比如Service什么的,可以另外再创建一个。

接下来,我们看使用dagger-android库优化之后的实现。

Dagger2 + dagger-android库 模式


本节示例代码收录在项目的sample-with-dagger-android分支

Application注入

AppComponent定义:

@Singleton
@Component(modules = {AppModule.class,
        AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector {

}

因为我们的CarResource通过组件注入传递给Activity和Fragment了,所以我们不需要暴露任何对象了。我们看到这里依赖了AndroidSupportInjectionModule类作为module,我们看看它是啥:

@Beta
@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
  @Multibinds
  abstract Map, AndroidInjector.Factory>
      supportFragmentInjectorFactories();

  private AndroidSupportInjectionModule() {}
}

它又依赖了AndroidInjectionModule,然后提供了一个空的Fragment注入器工厂的Map集合。如果你打开AndroidInjectionModule的源码,你会发现里面是Android四大组件注入器工厂的空集合。为啥要这个东西呢?因为我们的DaggerApplication声明了要注入这些集合,但是我们App中不一定全都用到了啊!如果不提供这个module,是会报找不到提供方的。这也是我们在MultiBinds章节说的使用空集合作为类型提供,不理解回头再看看吧!

AppModule定义:

@Module
public abstract class AppModule {
    @Binds
    abstract CarResource carResource(CarRepository carRepository);

    @ActivityScoped
    @ContributesAndroidInjector(modules = HomeActivityModule.class)
    abstract HomeActivity homeActivity();
}

这里我们就使用了@ContributesAndroidInjector,它有modules参数,表示用这些Modules生成一个SubComponent,这个SubComponent继承于AndroidInjector,其中T是返回类型声明的Android组件。生成之后,这个SubComponent会自动被当前Module声明为子组件。最后,我们可以在这里使用Scope传递给我们的子组件,其实就相当于我们再第二节中手动实现的子组件。

我们看下生成之后的代码:

@Module(subcomponents = AppModule_HomeActivity.HomeActivitySubcomponent.class)
public abstract class AppModule_HomeActivity {
  private AppModule_HomeActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(HomeActivity.class)
  abstract AndroidInjector.Factory bindAndroidInjectorFactory(
      HomeActivitySubcomponent.Builder builder);

  @Subcomponent(modules = HomeActivityModule.class)
  @ActivityScoped
  public interface HomeActivitySubcomponent extends AndroidInjector {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder {}
  }
}

我们看到它为我们生成了HomeActivitySubcomponent,但是用了一个Module来包装它,因为它要在这个Module里把HomeActivity的注入器,添加到Application中注入器的Map中,可以看到它用了Binds,将AndroidInject.Buidler绑定到HomeActivitySubcomponent.Builder来创建这个Factory,不明白的再去看看AndroidInjector定义。然后使用@IntoMap添加到集合中,这样当我们HomeActivity起来的时候,AndroidInjection类就能从Application实例中取出DispatchAndroidInjector,它再根据HomeActivity.class帮我们拿到这个Component进行注入啦。另外,看看我们的Component里是不是也有ActivityScoped注解呢。

那这个AppModule_HomeActivityModule在哪里被引用了呢?正常情况它应该是AppComponent的Module,但是它是dagger2生成的中间模板,所以就没有被显示声明到Component的依赖中了,但是依赖关系是保留了的。

我们再看看CarApp:

public class CarApp extends DaggerApplication {

    @Override
    protected AndroidInjector applicationInjector() {
        return DaggerAppComponent.builder().build();
    }
}

它只要继承DaggerApplication,然后返回一个自己的注入器就可以了,是不是超级清爽!

Activity注入

Acitivty的Component我们已经在上面看过了,我们看看它的Module:

@Module
public interface HomeActivityModule {

    @FragmentScoped
    @ContributesAndroidInjector
    HomeFragment provideHomeFragment();

    @Binds
    HomeContract.IHomePresenter providePresenter(HomePresenter homePresenter);
}

这里和前面的AppMoudle很相似,就不再解释了,我们的Fragment没有额外的依赖,就没有Module了。最后我们再稍微看下Activity和Fragment的实现:

public class HomeActivity extends DaggerAppCompatActivity {

    @Inject
    HomeFragment mHomeFragment;

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

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container_home, mHomeFragment)
                .commit();
    }
}

public class HomeFragment extends DaggerFragment implements HomeContract.IHomeView, View.OnClickListener {

    @Inject
    HomeContract.IHomePresenter mHomePresenter;

    private TextView mTvCarInstruction;
    private Button mBtnNextCar;

    @Inject
    public HomeFragment() {

    }
   //省略重复部分代码。
}

可以看到已经没有任何依赖和注入代码了,拿着对象直接玩耍咯。

现在我们回头来整理一下遗留的三个问题:

  1. 如何使用@ContributesAndroidInjector帮助我们实现自动生成子组件,它是怎么工作的?
    这个问题经过演示和讲解应该已经很清楚了。

  2. 为啥AndroidInject里有seedInstance方法?
    我也不是很清楚,在这个例子中,我们的AppComponent是没有使用这个方法的,并且在AndroidInjection类的各种类型的组件注入代码里,我们可以看到AndroidInjector和seedInstace传递进来的实例是没有什么逻辑关联的,它唯一的用处就是要求Builder在构建自己的时候,检查是否传进来组件实例,也就是强制要求不能在组件实例范围之外创建,所以只能推测它是用于限制Component实例创建的。

  3. 我们如何实现在Application里使用AppComponent实现注入器的注入?
    实现方法就是使用@ContributesAndroidInjector,从AppComponent开始为各层级android组件创建对应依赖关系的注入器咯。我们在示例中也介绍过了,有时间我会在补充一个完整的时序图来说明一下注入过程,当然,自己看源码是最深入的理解方式了。

总结

本示例中还有一些不完善的地方,比如我们的CarRepository其他地方获取不到,没法进行测试,再比如我们的Fragment在注入的时候已经实例化了,会引起额外的开销,应该使用Lazy来优化一下,再比如我们的Module划分也不是很合理,把Activity绑定和数据提供放在了一起,但是我们的使用方法和原理已经基本讲到了,剩下的就手动实践一把吧。最后我们会发现优化过后,和google提供的sample是一模一样的了,也更能理解sample中的各个细节,都是有它的用意和必要性的。

Dagger2系列的学习,我觉得比提供一个依赖管理工具更具有价值的,是它让我们学习了如何从依赖的角度去审视我们的应用,敦促我们在应用的设计阶段规划出合理的架构。还有它通过factory和provider实现我们对象提供的层层解耦,现身说法地告诉我们设计模式的强大,以及如何应用它们。dagger-android通过模板来实现组件注入能力,也是非常的简约和精彩,实在值得多体会一下。最后,dagger2告诉我们一个终极的道理,我们人类总是拘泥于各种形式,而逻辑实现总是可以自由自在!

我们Dagger2系列的文章到此就结束了,有些地方肯定也没讲到或者没有讲好,希望能够对大家有所帮助。最后,祝大家和dagger2玩的愉快!

你可能感兴趣的:(Dagger2 依赖的接力游戏(七)完结篇 :dagger-android框架使用&解析)