依赖

有两个类A,B。当A使用B的时候,就是A依赖B。

public class A {
    B b;
    public A(B b) {
        this.b = b;
    }
}
复制代码
public class B {}
复制代码

反过来,B是对A的依赖。

A -> Dependent on -> B
B -> Dependency for -> A
复制代码

对于Android Studio就是下列形式:

compile 'com.android.support:appcompat-v7:25.3.1'
复制代码

Android app 的代码依赖于 appcompat 库函数,appcompat 库函数是 app 的代码的依赖。

依赖注入

如何为一个类或者项目提供依赖被称为注入。依赖注入的意思就是我们如何提供我们的 app 所依赖的类或库称为依赖注入。

主要有三种方式实现依赖注入:

  1. 构造函数注入
  2. 方法注入
  3. 字段注入

Application

提供网络请求的工具类:

public class GitHubServiceGenerator {

    private static GitHubService gitHubService;

    private GitHubServiceGenerator() {
    }

    public static GitHubService gitHubService(String baseUrl) {

        if (gitHubService == null) {

            HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(
                    message -> Log.i("Retrofit Network", message))
                    .setLevel(HttpLoggingInterceptor.Level.BODY);

            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(httpLoggingInterceptor)
                    .readTimeout(5, TimeUnit.MINUTES)
                    .connectTimeout(5, TimeUnit.MINUTES).build();

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create()
                    .client(okHttpClient)
                    .build();

            gitHubService = retrofit.create(GitHubService.class);
        }
        return gitHubService;
    }
}
复制代码

从上面可以得出 Retrofit 依赖 baseUrl,Factories,client。



OkHttpClient 依赖 httpLoggingInterceptor。



最后 GitHubService 对象依赖 Retrofit 来生成。



APP:

public class App extends Application {

    private static App app;

    private GitHubRepository gitHubRepository;

    @Override
    public void onCreate() {
        super.onCreate();
        app = this;
        gitHubRepository = GitHubRepository.getInstance(GitHubServiceGenerator.gitHubService("https://api.github.com"));
    }


    public static App getApp() {
        return app;
    }

    public GitHubRepository getGitHubRepository() {
        return gitHubRepository;
    }
}
复制代码

从上面的代码可以看到 Application 依赖 GitHubRepository。GitHubRepository 依赖 GitHubService。



通过 Application 我们可以获得 GitHubRepository 进而获取 GitHub 的数据。



所以 App 现在是我们应用的最上层 component 。Application 类具有全局作用域。所以 GitHubRepository 应该被注入到 App 中作为全局作用域。

HomeActivity

public class HomeActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private ProgressBar progressBar;
    private GitHubRepository gitHubRepository;
    private HomeAdapter homeAdapter;
    private RecyclerView.LayoutManager layoutManager;
    private Disposable disposable;

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

        gitHubRepository = App.getApp().getGitHubRepository();

        initViews();
        initRecyclerView();
        loadData();
        homeAdapter.getClickSubject().subscribe(gitHubUser -> DetailActivity.start(this,
                gitHubUser.getLogin(), gitHubUser.getAvatarUrl()));

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (disposable != null && !disposable.isDisposed())
            disposable.dispose();
    }

    private void initViews() {
        recyclerView = (RecyclerView) findViewById(R.id.MainActivity_recycler_view);
        progressBar = (ProgressBar) findViewById(R.id.MainActivity_progress_bar);
    }

    private void initRecyclerView() {
        layoutManager = new LinearLayoutManager(this);
        homeAdapter = new HomeAdapter(new ArrayList<>());
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(homeAdapter);
    }


    private void loadData() {
        gitHubRepository.getUsers()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        gitHubUsers ->
                        {
                            homeAdapter.add(gitHubUsers);
                            progressBar.setVisibility(View.GONE);
                        },

                        error -> Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(),
                        () -> {
                        },
                        disposable -> this.disposable = disposable);
    }
}
复制代码

根据上面的类,大概可以得出几个适合使用依赖注入的场合:

  1. 有 new 关键字出现的地方。
  2. 类的所有字段都适合。
  3. 代码中出现静态类的地方。例如:App.getApp().getGitHubRepository();。

首先分析类的依赖图:



类 HomeActivity 依赖 GitHubRepository ,然后类 GitHubRepository 依赖于 App 。所以类 HomeActivity 依赖 App。

HomeViewHolder

final class HomeViewHolder extends RecyclerView.ViewHolder {

    private final View parentView;
    private final Observer<GitHubUser> clickObserrver;
    private final TextView nameTextView;
    private final ImageView picImageView;

    public HomeViewHolder(View itemView, Observer<GitHubUser> clickObserver) {
        super(itemView);
        nameTextView = (TextView) itemView.findViewById(R.id.RowHome_name_text_view);
        picImageView = (ImageView) itemView.findViewById(R.id.RowHome_user_image_view);
        parentView = itemView;
        this.clickObserrver = clickObserver;
    }

    public void bind(GitHubUser gitHubUser) {

        Glide.with(picImageView.getContext())
                .load(gitHubUser.getAvatarUrl())
                .into(picImageView);
        nameTextView.setText(gitHubUser.getLogin());
        parentView.setOnClickListener(v -> clickObserrver.onNext(gitHubUser));
    }
}
复制代码

类 HomeViewHolder 通过构造方法和普通方法依赖于外部。例如:parentView,clickObserrver,gitHubUser,Glide。

首先分析类的依赖图:



有许多可以使用依赖注入的理由,其中之一就是单元测试。如果使用合适的依赖实现我们的代码将会很方便写出好的单元测试。例如:

public void bind(GitHubUser gitHubUser) {
    Glide.with(picImageView.getContext())
            .load(gitHubUser.getAvatarUrl())
            .into(picImageView);
    nameTextView.setText(gitHubUser.getLogin());
    parentView.setOnClickListener(v -> clickObserrver.onNext(gitHubUser));
}
复制代码

代码中的 Glide 类是通过代码直接获取的,它属于隐藏的依赖,并没有通过其他方式提供。这是单元测试中的一个痛点。我们可以通过实现适当的依赖来解决,例如使用构造方法依赖注入。

final class HomeViewHolder extends RecyclerView.ViewHolder {

    ...
    private final Glide glide;
    ...

    public HomeViewHolder(View itemView, Observer<GitHubUser> clickObserver, Glide glide) {
        ....
        this.glide = glide;
    }

    public void bind(GitHubUser gitHubUser) {
        glide.with(picImageView.getContext())
                .load(gitHubUser.getAvatarUrl())
                .into(picImageView);
        ....
    }
}
复制代码

这样就可以通过 Mock Glide 类传递给构造方法实现单元测试。当然也可以通过方法注入来实现。

final class HomeViewHolder extends RecyclerView.ViewHolder {

    ...
    public void bind(GitHubUser gitHubUser, Glide glide) {
        glide.with(picImageView.getContext())
                .load(gitHubUser.getAvatarUrl())
                .into(picImageView);
    ...
    }
}
复制代码

HomeAdapter

类 HomeAdapter 依赖 HomeViewHolder。

public class HomeAdapter extends RecyclerView.Adapter<HomeViewHolder> {

    private LayoutInflater layoutInflater;
    private Subject<GitHubUser> clickSubject;
    private List<GitHubUser> gitHubUsers;

    public HomeAdapter(List<GitHubUser> gitHubUsers) {
        if (gitHubUsers == null)
            throw new IllegalArgumentException("List<GitHubUser> required");
        this.gitHubUsers = gitHubUsers;
        this.clickSubject = PublishSubject.create();
    }

    @Override
    public HomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (layoutInflater == null)
            layoutInflater = LayoutInflater.from(parent.getContext());
        return new HomeViewHolder(layoutInflater.inflate(R.layout.row_home, parent, false), clickSubject); // Getting error
    }

    @Override
    public void onBindViewHolder(HomeViewHolder holder, int position) {
        holder.bind(gitHubUsers.get(position));
    }

    @Override
    public int getItemCount() {
        return gitHubUsers.size();
    }

    public Observable<GitHubUser> getClickSubject() {
        return clickSubject;
    }

    public void add(List<GitHubUser> gitHubUsers) {
        if (gitHubUsers == null) {
            throw new NullPointerException("Required List<GitHubUser> but getting Null");
        }
        this.gitHubUsers.addAll(gitHubUsers);
        notifyDataSetChanged();
    }
}
复制代码

因为类 HomeViewHolder 已经将类 Glide 改为构造方法注入,所以下面代码将会报错。



首先理清楚类 HomeAapter 的依赖关系。



在类 HomeAapter 中没有提供类 Glide,可以通过构造方法注入的方式提供。

public class HomeAdapter extends RecyclerView.Adapter<HomeViewHolder> {

    private final Glide glide;
    ...

    public HomeAdapter(List<GitHubUser> gitHubUsers, Glide glide) {
        ...
        this.glide = glide;
    }
    @Override
    public HomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ...
        return new HomeViewHolder(layoutInflater.inflate(R.layout.row_home, parent, false), clickSubject, glide);
    }
    ...
}
复制代码

更新类 HomeAapter 的依赖关系。



回到类 HomeActivity 的依赖关系中。我们需要在类 HomeAapter 的构造方法中添加类 Glide 的依赖。



如果在这边直接创建 Glide glide = …,那当其他地方需要 Glide 的时候就需要再次使用 Glide glide = …,因此会造成重复代码。



更好的方法是将从类 App 中获得类 Glide 。这样就可以始终获得单一的 Glide 对象。

public class App extends Application {

    private static App app;
    private GitHubRepository gitHubRepository;
    private Glide glide;

    @Override
    public void onCreate() {
        ...
        glide = Glide.get(this);
    }
    public static App getApp() { ... }
    public GitHubRepository getGitHubRepository() { ... }

    public Glide getGlide() {
        return glide;
    }
}
复制代码

接着修改类 HomeActivity。

public class HomeActivity extends AppCompatActivity {

    ...
    private Glide glide;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        glide = App.getApp().getGlide();
        ...
    }

    @Override
    protected void onDestroy() { ... }
    private void initViews() { ... }
    private void initRecyclerView() {
        ...       
        homeAdapter = new HomeAdapter(new ArrayList<>(), glide);
        ...
    }
    private void loadData() { ... }
}
复制代码

下面是类 HomeActivity 重构之前的依赖关系。



下面是类 HomeActivity 重构之后的依赖关系。



下面是类 DetailActivity 重构前的代码:

public class DetailActivity extends AppCompatActivity {

    public static void start(Context context, String userName, String imageUrl) { ... }
    private static void validate(String userName, String imageUrl) { ... }

    private ImageView picImageView;
    private RecyclerView repositoriesRecyclerView;
    private ProgressBar progressBar;
    private RecyclerView.LayoutManager layoutManager;
    private DetailAdapter detailAdapter;
    private GitHubRepository gitHubRepository;
    private Disposable disposable;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) { ... }
    @Override
    protected void onDestroy() { ... }
    private void initViews() { ... }
    private void initRecyclerView() { ... }
    private void loadData(String username) { ... }

    private void loadImage(String imageUrl) {
        Glide.with(this)
                .load(imageUrl)
                .into(picImageView);
    }
}
复制代码

下面是类 DetailActivity 重构后的代码:

public class DetailActivity extends AppCompatActivity {

    public static void start(Context context, String userName, String imageUrl) { ... }
    private static void validate(String userName, String imageUrl) { ... }

    ....
    private Glide glide;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        gitHubRepository = App.getApp().getGitHubRepository();
        glide = App.getApp().getGlide();
        ...
    }

    @Override
    protected void onDestroy() { ... }
    private void initViews() { ... }
    private void initRecyclerView() { ... }
    private void loadData(String username) { ... }

    private void loadImage(String imageUrl) {
        glide.with(this)
                .load(imageUrl)
                .into(picImageView);
    }
}
复制代码

不要重复代码,而是使用注释处理器生成代码。编译器将会把对应的注解声称对应的代码。

例如使用

@BindView(R.id.title) TextView title;
复制代码

当编译器读取到 @BindView 时,将会生成模版代码,我们不用使用 findViewById() 然后进行强转。这种方法的一个好处是一切都在编译时处理,所以在运行时不会出现额外问题。

Component:整体的一部分或元素。Android 中 View, Activity, Fragment 或者 Network Repository 都有一个 Component。它将提供所有的依赖。同时,一个 Component 也可能依赖于另一个 Component。

Module:可用于构建更复杂结构的一组标准化部件或独立单元中的每一个,例如家具或建筑物。在Android中,我们可以说每个组件都包含提供所有依赖项的工厂或工厂类。我们将所有这些工厂放在一个地方,该类将成为该 Component 的一个 Module,它提供了创建一些复杂UI或算法的对象。



Component 总是作为一个整体或完整的功能,这将使我们能够通过提供其依赖性来实现一些有意义的功能。

现在要获得完整的 Component 功能,Component 可能依赖于某些其他 Component,也可能不依赖,但 Component 始终包含始终知道如何生成依赖关系的类,例如为 recycled view 创建 Adapter。此创建代码始终在 module 中可用。每个 Component 总是包含一个或多个 module,以实现一些有用的功能。



作为 Component 和 Module 的总结:

Component -> Maybe Dependent on -> Component or Components
Component -> always Include -> one or more modules
复制代码

module 里面包含什么,主要为一些协同工作的逻辑单元或函数,或者可以说这些函数相互依赖。模块中提供了所有这些小型单元或功能。或者我们可以说这个模块中将提供所有这些可能独立或可能依赖于其他小单元的小单元或功能。

HomeActivity

public class HomeActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private ProgressBar progressBar;
    private GitHubRepository gitHubRepository;
    private HomeAdapter homeAdapter;
    private RecyclerView.LayoutManager layoutManager;
    private Disposable disposable;
    private Glide glide;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        gitHubRepository = App.getApp().getGitHubRepository();
        glide = App.getApp().getGlide();
        initViews();
        initRecyclerView();
        loadData();
        homeAdapter.getClickSubject().subscribe(gitHubUser -> DetailActivity.start(this,
                gitHubUser.getLogin(), gitHubUser.getAvatarUrl()));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (disposable != null && !disposable.isDisposed())
            disposable.dispose();
    }

    private void initViews() {
        recyclerView = (RecyclerView) findViewById(R.id.MainActivity_recycler_view);
        progressBar = (ProgressBar) findViewById(R.id.MainActivity_progress_bar);
    }

    private void initRecyclerView() {
        layoutManager = new LinearLayoutManager(this);
        homeAdapter = new HomeAdapter(new ArrayList<>(), glide);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(homeAdapter);
    }

    private void loadData() {
        gitHubRepository.getUsers()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(gitHubUsers ->
                        {
                            homeAdapter.add(gitHubUsers);
                            progressBar.setVisibility(View.GONE);
                        },
                        error -> Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(),
                        () -> {
                        },
                        disposable -> this.disposable = disposable);
    }
}
复制代码

类 HomeActivity 的依赖关系图如下。



我们可以认为 HomeActivityComponent 将提供所有这些依赖。我们已经知道每个 Component 应该包括至少一个 module。因此首先创建一个 HomeActivityModule。现在是时候弄清楚什么是应该作为 HomeActivityModule 一部分的小单位。

根据上图可以知道所有的依赖

RecyclerView recyclerView;
    ProgressBar progressBar;
    GitHubRepository gitHubRepository;
    HomeAdapter homeAdapter;
    RecyclerView.LayoutManager layoutManager;
    Disposable disposable;
    Glide glide;
复制代码

现在我们的模块将负责为组件提供所有这些字段。 此外,模块应该知道如何创建这些字段对象。 现在我们首先要分析任何单位或字段是否依赖于任何其他字段。

  1. Glide:独立单位
  2. ProgressBar:独立单位
  3. GitHubRepository:取决于许多其他小模块,如Retrofit,OkHttp等。 因此,我将把它作为一个单独的组件。
  4. HomeAdapter:独立单位
  5. RecyclerView.LayoutManager:独立单位
  6. Disposable:独立单位
  7. RecyclerView:依赖于 HomeAdapter 和 LayoutManager

Dagger2 中包括了 @Component,@Module,@Provide 等注解用来生成模版代码。

HomeActivity

最终确定的第一件事是,我们应该有一个HomeActivityComponent.class。



通过图示就可以在 Dagger2 中创建一个 Component。

接下来需要创建一个 HomeActivitryModule.class,它将成为 Component 的一部分。



@Compment(module = HomeAcitvityModule.class)它的意思是 HomeActivityComponent 包含一个 HomeActivityModule,模块通过使用 @Module 告诉 Dagger2 它是一个模块,如上图所示。

每次在 Dagger2 中添加新内容时,都需要使用Command + F9键编译代码。

将会生成下面的代码。



我们的应用程序代码只知道 Component。这意味着当我需要任何依赖,我将从组件中获取。现在的问题是为什么不是 Module 提供,因为他们知道如何创建依赖。Module 始终知道如何创建 Component 所需的对象或依赖项。这意味着我们的应用程序只知道 Component 即可。从不与 Module 交互,因此我们的应用程序代码永远不会知道 Module 的存在。然后,Component 与 Module 交互,作为我们代码的依赖项。

通过 HomeActivity 的依赖图中,我们可以知道我们需要向 Component 获取的依赖项。

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

    RecyclerView getRecyclerView();
    ProgressBar getProgressbar();
    GitHubRepository getGitHubRepository();
    HomeAdapter getHomeAdapter();
    RecyclerView.LayoutManager getLayoutManager();
    Disposable getDisposable();
    Glide getGlide();
}
复制代码

现在如果编译代码,将在 Gradle 控制台上出错。上面已经从应用程序代码的角度满足了依赖关系,但是当 Component 要求对 Module 的依赖时,Module 将会混淆,因为我们实现的 Module 中没有代码可以创建该依赖。



现在 Component 中太多依赖了,先改为。

@Component(modules = HomeActivityModule.class)
public interface HomeActivityComponent {
    RecyclerView.LayoutManager getLayoutManager();
}
复制代码

现在需要将 LayoutManager 从 Module 提供给 Component。但在实现之前,我们需要检查当前如何在应用程序代码中创建此对象。

private void initRecyclerView() {
    layoutManager = new LinearLayoutManager(this);
    ...
}
复制代码

现在重构我们的代码。

@Module
public class HomeActivityModule {

    @Provides
    public RecyclerView.LayoutManager layoutManager(){
        return new LinearLayoutManager(context);
    }
}
复制代码

@Provide 注解用于在背后创建与 Component 的关系。



如图示知道我们的 Module 也依赖于 Context。 为此,我们可以在 Module 的构造函数中传递 Context。 所以现在是时候在 Module 中实现构造函数了。

@Module
public class HomeActivityModule {

    private final Context context;

    public HomeActivityModule(Context context) {
        this.context = context;
    }

    @Provides
    public RecyclerView.LayoutManager layoutManager(){
        return new LinearLayoutManager(context);
    }
}
复制代码

我们如何在应用程序代码中使用此 component。

public class HomeActivity extends AppCompatActivity {
    ...
    private HomeActivityComponent homeActivityComponent;

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

        homeActivityComponent = DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this))
                .build();
        ...
    }
      .....
}
复制代码

如果编译后没有错误。 Dagger2 将生成一些实用程序类,将使用它们来管理 component 依赖项。Dagger2 总是在组件名称的开头添加 Dagger,就像上面显示的那样(DaggerHomeActivityComponent)。此外,Dagger2 生成所有方法,这些方法是为我们创建一个合适的依赖图所必需的,就像需要调用一个 builder 方法,我需要在其中提供一个 homeActivityModule 对象,最后我需要调用 build 方法。

现在可以通过 homeActivityComponent 来获得 layoutManager 对象。

private void initRecyclerView() {
//      layoutManager = new LinearLayoutManager(this);
        layoutManager = homeActivityComponent.getLayoutManager();
        ...
    }
复制代码

现在将 HomeAdapter 依赖移动到 Component 中。

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

    RecyclerView.LayoutManager getLayoutManager();

    HomeAdapter getHomeAdapter(); // New dependency which I am adding now in Component
}
复制代码



如图所示,在 Module 创建 HomeAdpter 对象需要提供 glide 对象。这边仿照提供 Context 对象的方式来提供 Glide 对象。将 Glide 对象从构造方法中注入。

@Module
public class HomeActivityModule {

    private final Context context;
    private final Glide glide; // Remeber that is not a proper approach. Later we will refactor this also.

    public HomeActivityModule(Context context, Glide glide) {
        this.context = context;
        this.glide = glide;
    }

    @Provides
    public RecyclerView.LayoutManager layoutManager() {
        return new LinearLayoutManager(context);
    }

    @Provides
    public HomeAdapter homeAdapter() {
        return new HomeAdapter(new ArrayList<>(), glide);
    }
}
复制代码

编译代码后,修改类 HomeActivity,可以通过 HomeActivityComponent 来获取 Adapter 对象了。

public class HomeActivity extends AppCompatActivity {

      ...
//  private Glide glide; // Dependency moved into Component. So removed from our Class

    private HomeActivityComponent homeActivityComponent;

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

        homeActivityComponent = DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide())) // Bad approach for the time being
                .build();
       ....
    }
      ....
    private void initRecyclerView() {
//      layoutManager = new LinearLayoutManager(this);
        layoutManager = homeActivityComponent.getLayoutManager();

//      homeAdapter = new HomeAdapter(new ArrayList<>(), glide); Removed from HomeActivity and moved into Component.
        homeAdapter = homeActivityComponent.getHomeAdapter(); // Here we are getting our dependency from Component.
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(homeAdapter);
    }
    ....
}
复制代码

目前为止,Dagger2 并没有给我们提供太多的好处。但是,至少现在代码遵从了 开闭原则。如果想将 LayoutManager 的布局从 LinearLayout 更改为 GridLayout。只需要在 Module 文件中进行更改,之后一切都会正常工作。

@Module
public class HomeActivityModule {

     ....
//    @Provides
//    public RecyclerView.LayoutManager layoutManager() {
//        return new LinearLayoutManager(context);
//    }

    @Provides
    public RecyclerView.LayoutManager layoutManager() {
        return new GridLayoutManager(context,2);
    }
   ...
}
复制代码

应用程序的代码只与 Component 交互。任何代码中需要的依赖都要靠 Component 来提供。现在这意味着 应用程序的代码 中将不再出现创建对象的代码,而只使用会 Component 创建的对象。所以 Dagger2 已经添加了一个抽象层。

Component 总是向 Module 发出命令以来获取需要依赖的对象。所以 Module 只知道如何创建依赖。这意味着如果我们改变某些对象的创建,那么只有我们的 Module 代码将被改变,应用程序的代码将不会变化。

HomeActivity

重构 GitHubRepository

public class HomeActivity extends AppCompatActivity {
    ...
    private GitHubRepository gitHubRepository;
    ...

    private HomeActivityComponent homeActivityComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        homeActivityComponent = DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide()))
                .build();

        gitHubRepository = App.getApp().getGitHubRepository();
        ...
    }


    private void loadData() {
        gitHubRepository.getUsers()
        ...
    }
}
复制代码

当前代码中从类 App 中获取 GitHubRepository。因此,GitHubRepository 间接依赖于 App。现在将这个依赖移动到 Component 。

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

    RecyclerView.LayoutManager getLayoutManager();

    HomeAdapter getHomeAdapter();

    GitHubRepository getGitHubRepository();
}
复制代码

我们已经知道应用程序代码中需要的所有依赖项。对于程序中的所有依赖,总是可以通过 Component 获取。可以直接在 Component 中添加一个方法获取 GitHubRepository。所以项目代码,Component,Module 之间的关系如下。

AppCode -> Component
Component -> Module
Module 知道如何创建需要的对象
复制代码

现在只完成了第一步,AppCode -> Component。记住,如果此时直接编译,将会报错。



第二步就是在 Module 中书写生成依赖的代码。

@Module
public class HomeActivityModule {
    ...

    @Provides
    public GitHubRepository gitHubRepository() {
        return App.getApp().getGitHubRepository(); // This is not a good way instead this is not a way. :P 
    }
}
复制代码

这样就完成了 AppCode -> Component -> Module 流程。是时候在项目代码中使用 Component 的这个新依赖项了。

public class HomeActivity extends AppCompatActivity {
    ...
    private GitHubRepository gitHubRepository;
    ...
    private HomeActivityComponent homeActivityComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
//      gitHubRepository = App.getApp().getGitHubRepository(); // Before refactoring as a Hidden Dependency
        gitHubRepository = homeActivityComponent.getGitHubRepository(); // After refactoring
        ...
    }
       ...
}
复制代码

现在,除了像 Progressbar 和 RecyclerView 这样的UI视图,我们的所有依赖项都被移动到 HomeActivityComponent 中。至于UI视图,根据团队喜好进行管理。

接下来将引入 @Inject 注解。

当前的 Component 的结构如下。

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

    RecyclerView.LayoutManager getLayoutManager();

    HomeAdapter getHomeAdapter();

    GitHubRepository getGitHubRepository();
}
复制代码

现在如果需要提供20个依赖项,就需要在 Component 中提供20个方法来获取。使用 @Inject 注解可以很好解决这个问题。

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

    void inject(HomeActivity homeActivity);

//    RecyclerView.LayoutManager getLayoutManager(); removed
//    HomeAdapter getHomeAdapter(); removed
//    GitHubRepository getGitHubRepository(); removed
}
复制代码

每个 Component 都有一个 inject 方法,有一个参数,可以是一个 activity, fragment, view 或任何你正在编写 Component 的类。现在如果你运行代码,仍然会报错。



现在需要重构代码

public class HomeActivity extends AppCompatActivity {

    ....
    @Inject
    GitHubRepository gitHubRepository;
    @Inject
    HomeAdapter homeAdapter;
    @Inject
    RecyclerView.LayoutManager layoutManager;


//    private HomeActivityComponent homeActivityComponent; // Removed

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide()))
                .build().inject(this); // Added
//        homeActivityComponent = DaggerHomeActivityComponent.builder()
//                .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide()))
//                .build(); // Removed

//      gitHubRepository = App.getApp().getGitHubRepository();
//        gitHubRepository = homeActivityComponent.getGitHubRepository();  // Removed
        ...
    }
    ...
    private void initRecyclerView() {
//      layoutManager = new LinearLayoutManager(this);  
//        layoutManager = homeActivityComponent.getLayoutManager(); // Removed

//        homeAdapter = new HomeAdapter(new ArrayList<>(), glide);
//        homeAdapter = homeActivityComponent.getHomeAdapter(); // Removed
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(homeAdapter);
    }
    ....
}
复制代码

现在假设需要增加一个新的依赖到代码中。首先先创建这个类。

public class FormatString {
}
复制代码

将它作为依赖增加到类 HomeActivity 。按照流程进行操作:AppCode->Compoent->Module。

AppCode:



Component:

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

    void inject(HomeActivity homeActivity);
}
复制代码

Component 保持相同的状态。在 AppCode 端修改变量的形式。



现在完成了 AppCode -> Component 阶段。接着完成 Component -> Module 阶段。

@Module
public class HomeActivityModule {
    ....
    @Provides
    public FormatString formatString(){
        return new FormatString();
    }
}
复制代码

这样就完成了。在不修改 Component 文件的条件下,提供了更多的依赖。另外一个好处就是如果依赖类的 Construction 方法改变,将不会影响到 HomeActivty。例如,当需要给 FormateString 构造函数增加一个参数时。

public class FormatString {
    public FormatString(String s) {
    }
}
复制代码

只需要修改 Module,并不需要修改 AppCode。

@Module
public class HomeActivityModule {
    ....
    @Provides
    public FormatString formatString(){
        return new FormatString("Hello Guys"); 
    }
}
复制代码

@Inject 工作原理。

首先从 Component 开始。

@Component(modules = HomeActivityModule.class)
public interface HomeActivityComponent {
    void inject(HomeActivity homeActivity);
}
复制代码

inject 方法的返回值为 void,可以理解为希望 Dagger2 将所有依赖注入类 HomeActivity 中。接着所有那些依赖于 Component 的字段或对象都附加了关键字 @Inject 并移除私有说明符。

public class HomeActivity extends AppCompatActivity {
    ...
    @Inject
    GitHubRepository gitHubRepository;
    @Inject
    HomeAdapter homeAdapter;
    @Inject
    RecyclerView.LayoutManager layoutManager;
    @Inject
    FormatString formatString;
    ...
}
复制代码

最后需要在需要被注入的类 HomeActivity 中使用 Dagree2 进行 Component 的初始化。

public class HomeActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide()))
                .build().inject(this);
       ...
    }
     ...
}
复制代码

这样就使用 inject(this); 将类 HomeActivty 作为上下文提供给 Dagger2。

当我们编译的时候,Dagger2 将使用类 HomeActivity 作为上下文,解析出有多少字段使用了 @Inject 修饰。当解析完毕将会到 Module 中使用提供的方法生成所有的对象。

这里有个问题,Dagger2 如何知道哪个使用 @Inject 修饰字段需要哪个对象引用。首先,Dagger2 将会获取类 HomeActivity 当中的 @Inject 字段,因为在类 HomeActivity 当中使用了 inject(this); 方法进行上下文的注入,此时,Dagger2 将会要求对象类型之间的关系,此时将会从 Module 中获取相应信息,因为在 Module 中,我们使用了 @Provide 关键字提供了返回的类型,Dagger2 将会自行匹配。

@Module
public class HomeActivityModule {

    private final Context context;
    private final Glide glide;

    public HomeActivityModule(Context context, Glide glide) {
        this.context = context;
        this.glide = glide;
    }

    @Provides
    public RecyclerView.LayoutManager layoutManager() {
        return new LinearLayoutManager(context);
    }

    @Provides
    public HomeAdapter homeAdapter() {
        return new HomeAdapter(new ArrayList<>(), glide);
    }

    @Provides
    public GitHubRepository gitHubRepository() {
        return App.getApp().getGitHubRepository();
    }

    @Provides
    public FormatString formatString(){
        return new FormatString("Hello Guys");
    }
}
复制代码

生成的 Component 代码:



Dagger2 工作流程:



在使用 Dagger2 的时候,可以帮助将代码中的依赖从 app 代码中转移到 Module 中,接着上面实现的 Module 实例。

@Module
public class HomeActivityModule {
    ...
    @Provides
    public HomeAdapter homeAdapter() {
        return new HomeAdapter(new ArrayList<>(), glide);
    }

    @Provides
    public GitHubRepository gitHubRepository() {
        return App.getApp().getGitHubRepository();
    }
    ...
}
复制代码

上面提供 GithubRepository 的方式不太正确。现在将对他进行重构。

首先涉及 Dagger2 的代码部分如下



根据上面的代码可以看到,包含了 Class,Component 和 Module。HomeActivityComponent 并没有什么可以修改的。HomeActivityModue 中包含了 App.getApp().getGitHubRepository(); 代码,HomeActivity中包含了 App.getApp().getGlide() 代码。HomeActivityModue 和 HomeActivity 直接依赖了 App 类了。应该转换了使用 Dagger2 来提供依赖,而不是 App。

首先重构 App 类。

public class App extends Application {

    private static App app;

    private GitHubRepository gitHubRepository;
    private Glide glide;

    @Override
    public void onCreate() {
        super.onCreate();
        app = this;
        gitHubRepository = GitHubRepository.getInstance(GitHubServiceGenerator.gitHubService("https://api.github.com"));
        glide = Glide.get(this);
    }
    
    public static App getApp() {
        return app;
    }

    public GitHubRepository getGitHubRepository() {
        return gitHubRepository;
    }

    public Glide getGlide() {
        return glide;
    }
}
复制代码

类 Application 对于单进程 Android 程序来说是单例来的,所以在这里声明的对象都是单例的。首先在这里代码初始化了 GitHubRepository, Glide 对象,然后需要一个 Component 来给 App 类提供依赖。



创建 Component 提供两个方法分别提供 GitHubRepository, Glide 对象。

@Component
public interface AppComponent {
    GitHubRepository getGitHubRepository();
    Glide getGlide();
}
复制代码

根绝流程 AppCode -> Component -> Module。接下来需要创建 Module 来为 Component 提供依赖。



上述代码只是将创建对象的代码从 App 中移动到 AppMopdule 中。当中,Glide 需要 App 的上下文来进行初始化。这边可以通过构造器方式注入。

@Module
public class AppModule {
    private final Context appContext;
    public AppModule(Context appContext) {
        this.appContext = appContext;
    }
    @Provides
    public GitHubRepository gitHubRepository() {
        return GitHubRepository.getInstance(GitHubServiceGenerator
                .gitHubService("https://api.github.com"));
    }
    @Provides
    public Glide glide() {
        return Glide.get(appContext);
    }
}
复制代码

同样 GitHubRepository 需要一个 URL 进行初始化,也可以通过构造器进行注入。

@Module
public class AppModule {

    private final Context appContext;
    private final String url;

    public AppModule(Context appContext, String url) {
        this.appContext = appContext;
        this.url = url;
    }

    @Provides
    public GitHubRepository gitHubRepository() {
        return GitHubRepository.getInstance(GitHubServiceGenerator
                .gitHubService(url));
    }

    @Provides
    public Glide glide() {
        return Glide.get(appContext);
    }
}
复制代码

对于 GitHubRepository 类还可以进行进一步优化。现将 App 中的报错解决。

public class App extends Application {

    private static App app;

//    private GitHubRepository gitHubRepository;
//    private Glide glide;
    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        app = this;
        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this, "https://api.github.com"))
                .build();
//        gitHubRepository = GitHubRepository.getInstance(GitHubServiceGenerator.gitHubService("https://api.github.com"));
//        glide = Glide.get(this);

    }

    public static App getApp() {
        return app;
    }

    public AppComponent getAppComponent() {
        return appComponent;
    }

    //    public GitHubRepository getGitHubRepository() {
//        return gitHubRepository;
//    }
//
//    public Glide getGlide() {
//        return glide;
//    }
}
复制代码

移除注释之后:

public class App extends Application {

    private static App app;
    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        app = this;
        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this, "https://api.github.com"))
                .build();
    }

    public static App getApp() {
        return app;
    }

    public AppComponent getAppComponent() {
        return appComponent;
    }
}
复制代码

此时,之前直接使用 App 获取 GitHubRepository, Glide 对象的地方将会报错。



这边不进行 DetailActivity 类的重构,因为重构方式与 HomeActivity 一致。也就是剩下两个报错。



在 Dagger2 中有一个观念,那就是

Component -> Maybe Dependent on -> Component or Components
复制代码

根据上面的重构,我们知道 GitHubRepository, Glide 可以通过 AppComponent 来获得。所以如果要在 HomeActivity 中获得 GitHubRepository, Glide,可以将 AppComponent 作为 HomeComponent 的一个依赖。

@Component(modules = HomeActivityModule.class, dependencies = AppComponent.class)
public interface HomeActivityComponent {
    void inject(HomeActivity homeActivity);
}
复制代码

上面的代码就是如何给一个 Component 提供另一个 Component 依赖。如果想要提供多个依赖,可以使用逗号区隔。

dependencies = {AppComponent.class, AComponent.class, BComponent.class, }
复制代码

此时可以将 HomeActivitytModule 中的构造器注入中的 Glide 参数移除了。

Before Refactoring:

@Module
public class HomeActivityModule {
    private final Context context;
    private final Glide glide;

    public HomeActivityModule(Context context, Glide glide) {
        this.context = context;
        this.glide = glide;
    }
    ...
    @Provides
    public HomeAdapter homeAdapter() {
        return new HomeAdapter(new ArrayList<>(), glide);
    }
    ...
}

After Refactoring:

@Module
public class HomeActivityModule {
    private final Context context;
    public HomeActivityModule(Context context) {
        this.context = context;
    }
    ...
    @Provides
    public HomeAdapter homeAdapter() {
        return new HomeAdapter(new ArrayList<>(), glide); // Currently glide is giving error
    }
    ...
}
复制代码

此时可以将 HomeActivity 中 HomeModule 的构造器注入中的 Glide 对象移除了。

public class HomeActivity extends AppCompatActivity {

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

        // Before Refactoring 
        // DaggerHomeActivityComponent.builder()
        //        .homeActivityModule(new HomeActivityModule(this, App.getApp().getGlide()))
        //        .build().inject(this);

        // After Refactoring 
        DaggerHomeActivityComponent.builder()
                .homeActivityModule(new HomeActivityModule(this))
                .build().inject(this);
        ...        
    }
    ...
}
复制代码

现在错误集中在 HomeActivityModule 中了。



此时作如下处理。



此时,编译不会出错,但是运行会出错。



错误信息解释了,HomeActivityComponent 依赖于 AppComponent,但是当 HomeActivity 初始化 Component 的时候,并没有提供相关依赖。

public class HomeActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        // Before Refactoring
//        DaggerHomeActivityComponent.builder()
//                .homeActivityModule(new HomeActivityModule(this))
//                .build().inject(this);

        // After Refactoring
        DaggerHomeActivityComponent.builder()
                .appComponent(App.getApp().getAppComponent())
                .homeActivityModule(new HomeActivityModule(this))
                .build().inject(this);
       ...
    }
    ...
}
复制代码


如图所示,HomeActivityComponent 还没有依赖于 AppComponent。此时,GitHubRepository 和 Glide 将无法提供,添加 AppComponent 之后。

现在添加了 AppComponent 依赖后,Dagger2 知道如何获取 GitHubRepository 和 Glide。

对应的 Component 类代码。



Dagger2 有一个 SubComponent 概念。最开始的时候,我们直接在 HomeActivityComponent 中使用方法注入所需的对象。



重构之后的代码如下。



AppComponent 的调用链如下。



首先,AppComponent 有两个方法,然后 AppComponent 在 App 终被初始化,最后 HomeActivityComponent 使用了 AppComponent 作为依赖。



问题来了,为什么不使用如下方法初始化 AppComponent。



因为会报错。



下面引入 SubComponent 的概念。

原理

Parent Component 总是总是声明方法来为依赖它的 Component 来提供对象。所以 Inject 方式并不适合 Parent Component。

在上面的例子中就是,AppComponent 作为 Parent Component ,然后 HomeActivityComponent 依赖 AppComponent。

可以抽象化为下面这样。



同样道理,如果需要 ChildChildComponent 将 ChildComponent 作为 Parent Component。那么就需要在 ChildComponent 声明想要注入到 ChildChildComponent 类中的方法。否则 Dagger2 将不清楚如何提供依赖。



SubComponents

假如没有引入 Dagger2,App 类的代码如下所示。



接着创建 AppComponent 对象。它是一个空类,然后是 AppModule,它将提供 GitHubRepository 和 Glide 的依赖。



接下来创建 HomeActivityComponent 和它的模块。



此时运行项目将会报错。



因为此时 AppComponent 是一个空实现。将 HomeAcitivityComponent 修改为使用注解 SubComponent 实现的方式。



当将 @Component 修改 @SubComponent。此时,@SubComponent 并没有 dependencies 关键字。说明 SubComponent 无法从 Parent Component 获得依赖。



Dagger2 总是为 Component 创建一个单独类。例如 DaggerAppComponent, DaggerHomeActivityComponent,但是并不会为 SubComponents 创建。此时运行程序将会报错,因为,类 HomeActivity 中的 GitHubRepository 将会报错空指针,以前是从 Parent Component 注入的,现在将 @Component 修改为 @SubComponent 之后,将不再适用。

此时需要修改作为 Parent Component 的 AppComponent。将 GitHubRepository 成功注入 HomeActivity。



SubComponent 只能有一个 Parent Component。并且只提供一个参数的方法。

HomeSubComponent plus(HomeActivityModule module);
复制代码

使用 SubComponent 之前的版本。



使用 SubComponent 之后的版本。



SubComponent 的特点:

  1. SubComponent 只能有一个 Parent Component,在那边声明 SubComponent。
  2. @SubComponent 没有提供 dependencies 关键字。
  3. Dagger2 不会为 SubComponent 生成单独的类。所以,SubComponent 需要 ParentComponent 类使用 plus(..) 来进行依赖注入。

Component Vs SubComponent

Component



  1. AppComponent 包含两个方法声明。
  2. AppComponent 在 App 中初始化。
  3. HomeActivityComponent 依赖于 AppComponent。
  4. HomeActivity 中初始化 DaggerHomeActivityComponent,将 AppComponent 对象作为组成的一部分。

SubComponent:



  1. AppComponent 包含了 SubComponent
  2. AppComponent 在 App 中初始化。
  3. SubComponent 不清楚它的 ParentComponent,它只是通过包含 Module 提供自己的依赖项。
  4. HomeActivity 中通过 Parent Component 注入 SubComponent。

图解



使用 Components 时,我们使用 dependencies 属性,稍后作为组合对象提供依赖的属性。这就是为什么不能在 AppComponent 中使用 inject 方法注入所需要的依赖。AppComponent 作为一个组合对象在 HomeAcirtivytComponent 内。所以 HomeActivityComponent 不清楚 AppComponent 可以委托的依赖请求。正如 Glide 和 GitHubRepository 需要在 AppComponent 中声明两个方来来提供,因为如果不提供,HomeActivityComponent 将无法访问这些依赖。

使用 SubComponents 时,所有 SubComponents 都知道 AppComponent 中包含哪些依赖,因为 SubComponents 包含在 AppComponent 中。他们知道 AppComponent 能够提供哪些依赖。

Dagger2 生成代码比较



DaggerHomeActivityComponent 的代码如上。



DaggerAppComponent 在 SubComponent 策略下的代码如上。对于代码中的 plus() 方法。是在 HomeActivity 中初始化 HomeSubComponent 的。

@Override
protected void onCreate(Bundle savedInstanceState) {
    ....

    App.getApp().getAppComponent()
            .plus(new HomeActivityModule(this))
            .inject(this);
    ....
}
复制代码

总之,SubComponent 作为 AppComponent 的一部分,可以直接获取它提供的依赖。而不需要另外提供其他的方法。

字段注入方式



上图展示了 Dagger2 如何将 HomeAdapter 作为一个字段注入到 HomeActivity。Dagger2 生成的模版代码如下。



构造器注入方式



为类 HomeAdapter 的构造方法增加注解 @Inject。并从类 HomeActivityModule 中移除对应的 @Provide 方法。在 HomeActivity 中将 HomeAdapter 作为字段注入,方式为构造方法注入。Dagger2 生成的模版代码如下。



字段注入方式与构造器注入方式的差别为:

  1. 字段注入方式中是通过在 Module 中通过 @Providing 提供 Adapter。
  2. 构造器注入方式中通过在 Adapter 的构造方法中添加 @Inject 注解。
  3. 字段注入方式中直接使用 module 来提供依赖,构造器注入方式中生成了 MembersInjector 用于实现注入。

方法注入方式



由日志看出 Dagger2 确实注入了 Glide 对象。并且不用通过显示的调用方法。