Dagger2实战(详细)

Posted DakerYi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dagger2实战(详细)相关的知识,希望对你有一定的参考价值。

提前准备

如果你对Dagger2一点基础都没有,建议你先看看第一篇:Dagger2入门详解

如果想直接看代码,可以 到Github上 Clone一下:源码地址

参考文章

Dependency Injection with Dagger 2

史上最通俗易懂的Android中使用Dagger入门教程

都是套路——Dagger2没有想象的那么难

环境配置

project: build.gradle

dependencies 
        //...

        //dagger2
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    

app: build.gradle

//...

//dagger2
apply plugin: 'com.neenbedankt.android-apt'

android 
    //...


dependencies 
    //...

    //dagger2
    apt 'com.google.dagger:dagger-compiler:2.7'
    compile 'com.google.dagger:dagger:2.7'
    provided 'javax.annotation:jsr250-api:1.0'

开始撸代码

首先我们要不落俗套的借用一张图:

来解释一下这样图,通俗的理解,我们知道 Dagger2 中一个很重要的概念就是 Scope 生命周期,这里的 component(容器) 的框框可以看成一个容器,并且每个component 一般都拥有自己的 Scope, module 可以看成生产物品并放入 component中的工厂(虽然它不叫 factory)。并且框框里面的component 可以使用外层 component 中的产品。

结合图:ApplicationComponent 是一个容器,它的 ApplicationModule 负责生产一些产品。里层的 ActivityComponent 在 ApplicationComponent中,并享有 ApplicationComponent中的所有产品,并且自己也有 ActivityModule 可以生产自己的产品。 然后再里面就是 UserComponent,它拥有ApplicationComponent和ActivityComponent中的所有产品,并自己也有 Module。这里的产品,就是我们可以用@Inject 注入的东西

其中有两个生命周期 @Singleton 和 @PerActivity(自定义),@Singleton并不是我们设计模式中的单例模式,而是Dagger2中为了保持一个产品在一个Scope中只有一个对象的标签。@PerActivity 也一样,表示在 @PerActivity 这个生命周期中,只包含一个这样产品的标签。

Dagger2 本来只是一个依赖注入框架,再简单不过了。但是非要搞出这么复杂的结构是为了什么?其实这是根据Android开发的特点来的。Fragment 依赖 Activity 依赖 Application 其实本来就有这样的结构,如果你按照这种思想来理解,会容易很多。

好了,上代码

首先建立最大的 AppComponent

AppComponent.java

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

    Context getContext();

    ToastUtil getToastUtil();

注意这里的 再 Component中给出的 ToastUtil 是提供给依赖于 AppComponent 的所有 Component的。这里表示所有依赖它的结构都可以使用 toast 功能。

AppModule.java

@Module
public class AppModule 

    private Context context;

    public AppModule(Context context) 
        this.context = context;
    

    @Singleton
    @Provides
    public Context provideContext() 
        return this.context;
    


    @Singleton
    @Provides
    public ToastUtil provideToastUtil(Context context) 
        return new ToastUtil(context);
    

Module中由 @Provides 修饰的表示我这个Component中能提供的产品(供外界注入)。

ToastUtil.java
很简单

public class ToastUtil 

    private Context context;

    public ToastUtil(Context context) 
        this.context = context;
    

    public void showToast(String message) 
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    

自定义 App,并将 AppComponent 初始化:
App.java

public class App extends Application 

    private AppComponent appComponent;

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

        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();

    

    public AppComponent getAppComponent() 
        return this.appComponent;
    

别忘了修改 AndroidManifest.xml 文件 <application android:name=".App"> ...</application>

以上就是最大的生命周期 App,而且内容都是 @Singleton 的。然后我们来写 Acitivyt的。

严格按照图上的来写:

写一个抽象的 ActivityComponent

ActivityComponent.java

@PerActivity
@Component(modules = ActivityModule.class, dependencies = AppComponent.class)
public interface ActivityComponent 

    Activity getActivity();

    ToastUtil getToastUtil();

ActivityModule.java

@Module
public class ActivityModule 

    private final Activity activity;

    public ActivityModule(Activity activity) 
        this.activity = activity;
    

    @PerActivity
    @Provides
    public Activity provideActivity() 
        return this.activity;
    

这里注意两点:
1. ActivityComponent 中 还需要重写一次 ToastUtil getToastUtil();, 上面我们提到,Component中写的是 可提供给依赖自己的Component的东西,并且不能直接继承自 AppComponent。这里只需要提供接口,然后它自己会找到AppComponent中并获得ToastUtil
2. @PerActivity,如果使用了 dependencies,那么依赖的一方的 Scope 不能和 父级 相同,其实@PerActivity的代码和 @Singleton 是一样的,只是需要我们自己重新定义一下而已。

PerActivity.java

@Scope
@Documented
@Retention(RUNTIME)
public @interface PerActivity 

然后写一个 BaseActivity ,它的主要作用其实就是提供 ActivityComponent,因为继承自它的Activity都需要这个容器。

BaseActivity.java

public class BaseActivity extends AppCompatActivity 

    private ActivityComponent activityComponent;

    public ActivityComponent getActivityComponent() 
        return activityComponent;
    

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        activityComponent = DaggerActivityComponent.builder()
                .appComponent(((App) getApplication()).getAppComponent())
                .activityModule(new ActivityModule(this))
                .build();
    

上面完成了 AppComponent 和 一个抽象的 BaseActivity,为什么叫它抽象,因为你不会用它来实例化一个Activity吧,它只是为了给所有 具体的Activity模块提供内容。当然,还有一种写法,就是不用BaseActivity这个东西,直接让具体的每个Activity和Application发生关系。至于怎么写,我们放在后面吧!

具体的MainComponent

MainComponent.java

@MainActivityScope
@Component(dependencies = ActivityComponent.class, modules = MainModule.class)
public interface MainComponent 

    void inject(MainActivity mainActivity);

    MainFragmentComponent mainFragmentComponent();

MainModule.java

@Module
public class MainModule 

    @Provides
    public UserRepository provideUserRepository() 
        return new UserRepository();
    

MainFragment.java

@MainActivityScope
@Subcomponent
public interface MainFragmentComponent 

    void inject(MainFragment mainFragment);

这里注意几点:

  1. void inject(MainActivity mainActivity);void inject(MainFragment mainFragment); 因为要和具体的依赖组件发生关联,所以添加了注入接口。
  2. 关于 @Subcomponent 的用法,它的作用和 dependencys 相似,这里表示 FragmentComponent 是一个子组件,那么它的父组件是谁呢? 提供了获取 MainFragmentComponent 的组件,如 MainComponent中的 MainFragmentComponent mainFragmentComponent();,是这样做的关联。
  3. MainModule中提供了 UserRepository,表示要给数据仓库,这里只是模拟数据。

UserRepository .java

public class UserRepository  

    public UserRepository() 
    

    public User getUser() 
        User user = new User();
        user.name = "yxm";
        return user;
    

User.java

public class User 
    public String name;
  1. 自定义的 Scope,和前面方法一样,直接copy @Singleton 注解中的代码
    MainActivityScope.java
@Scope
@Documented
@Retention(RUNTIME)
public @interface MainActivityScope 

MainActivity和MainFragment怎么注入

MainActivity.java

public class MainActivity extends BaseActivity 

    private MainComponent mainComponent;

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

        mainComponent = DaggerMainComponent.builder()
                .activityComponent(getActivityComponent())
                .mainModule(new MainModule())
                .build();
        mainComponent.inject(this);
    

    public MainComponent getMainComponent() 
        return this.mainComponent;
    

还是最流行的MVP模式:

MainFragmentContact.java

public class MainFragmentContact 
    public interface View 
        void setUserName(String name);

        void showToast(String msg);
    

    public static class Presenter 
        public UserRepository userRepository;

        @Inject
        public Presenter(UserRepository repository) 
            this.userRepository = repository;
        

        private View view;

        public void setView(View view) 
            this.view = view;
        

        public void toastButtonClick() 
            String msg = "hello world";
            view.showToast(msg);
        

        public void userInfoButtonClick() 
            User userData = this.userRepository.getUser();
            this.view.setUserName((userData.name));
        
    

然后是Fragment

MainFragment.java

public class MainFragment extends Fragment implements MainFragmentContact.View 

    @Inject
    MainFragmentContact.Presenter mainPresenter;
    @Inject
    ToastUtil toastUtil;

    private MainFragmentComponent mainFragmentComponent;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);

        if (getActivity() instanceof MainActivity) 
            mainFragmentComponent = ((MainActivity) getActivity()).getMainComponent().mainFragmentComponent();
            mainFragmentComponent.inject(this);

        
        mainPresenter.setView(this);
    

    @Nullable
    @Override
    public android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        android.view.View view = inflater.inflate(R.layout.fragment_main, container, false);
        return view;
    

    @Override
    public void onViewCreated(android.view.View view, Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);
        Button btnToast = (Button) view.findViewById(R.id.btn_toast);
        btnToast.setOnClickListener(new android.view.View.OnClickListener() 
            @Override
            public void onClick(android.view.View view) 
                mainPresenter.toastButtonClick();
            
        );

        Button btnUserData = (Button) view.findViewById(R.id.btn_user_info);
        btnUserData.setOnClickListener(new android.view.View.OnClickListener() 
            @Override
            public void onClick(android.view.View view) 
                mainPresenter.userInfoButtonClick();
            
        );
    

    @Override
    public void setUserName(String name) 
        ((TextView) getView().findViewById(R.id.et_user)).setText(name);
    

    @Override
    public void showToast(String msg) 
        toastUtil.showToast(msg);
    

代码有点长,注意几点:

  1. 与MainComponent关联上
if (getActivity() instanceof MainActivity) 
    mainFragmentComponent = ((MainActivity) getActivity()).getMainComponent().mainFragmentComponent();
    mainFragmentComponent.inject(this);
  1. 内部类注入时,必须使用 static 的,如上的 MainContact 中的 Presenter

  2. 其他的就是MVP方面的知识了,如果不懂,就自己看看呗。然后还有 layout文件,我不信你不会写,哈哈哈

另一种写法

刚刚我们提到,还可以让Activity直接和Application发生关系,怎么写呢?

  1. ActivityComponent 不依赖 AppComponent,所以也不能再提供ToastUtil

ActivityComponent.java

@PerActivity
@Component(modules = ActivityModule.class)
public interface ActivityComponent 
    Activity getActivity();
  1. MainComponent直接依赖Appcomponent

MainComponent.java

@MainActivityScope
@Component(dependencies = AppComponent.class, modules = MainModule.class, ActivityModule.class)
public interface MainComponent 

    void inject(MainActivity mainActivity);

    MainFragmentComponent mainFragmentComponent();
  1. BaseActivity中提供 AppComponent的引用,因为所有要注入AppComponent的Activity都需要这个,所以 写在BaseActivity中,同时 ActivityComponent也不需要了。

AppCompatActivity.java

public class BaseActivity extends AppCompatActivity 

    public AppComponent getAppComponent() 
        return ((App) getApplication()).getAppComponent();
    
  1. 注入的地方,添加 AppComponent,同时添加 ActivityModule和MainModule

BaseActivity.java

public class MainActivity extends BaseActivity 

    private MainComponent mainComponent;

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

        mainComponent = DaggerMainComponent.builder()
                .appComponent(getAppComponent())
                .activityModule(new ActivityModule(this))
                .mainModule(new MainModule())
                .build();
        mainComponent.inject(this);
    

    public MainComponent getMainComponent() 
        return this.mainComponent;
    

好了,其他代码不用动,直接可以运行

总结

如果看完并理解了这篇,你肯定能更加理解Dagger2的设计思想。这种分层的结构,可以让我们的项目结构化更加清晰。不要看着上面的代码很复杂,实现的内容又有限。其实,这个架子搭好了,以后往里面加东西就方便了。

现在就可以在各个层级添加自己想要注入的对象的 provideXXX 方法,然后在Activity或者Fragment中直接注入,十分方便,你可以自己体会体会。

当前我们注入的数据源是实体类,其实完全可以注入一个interface,例如 UserFromNet 和 UserFromLocal 表示来自网络和本地的数据源,通过注解来更换数据源。这也是面相抽象编程的思想。

以上是关于Dagger2实战(详细)的主要内容,如果未能解决你的问题,请参考以下文章

Android实战——Dagger2一场老板与员工的故事会

Dagger2

Android单排上王者系列之Dagger2注入原理解析

dagger2系列之依赖方式

css 如何让外层的div 控制住内层div的宽度?

Jquery怎么制作下拉效果