Dagger2实战(详细)

作者: DakerYi | 来源:发表于2016-11-23 10:56 被阅读449次

    提前准备

    如果你对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'
    }
    

    开始撸代码

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

    不落俗套的一张图.png

    来解释一下这样图,通俗的理解,我们知道 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 表示来自网络和本地的数据源,通过注解来更换数据源。这也是面相抽象编程的思想。

    相关文章

      网友评论

      • RamboMing:不错,照楼主思路敲了一遍,但是MainFragment是如何吸附到MainActivity中?还是用FragmentManager吗?
        DakerYi:好久没看了:sweat::sweat::sweat:,fragments周期应该依附于它所在activity,自己去体会吧
      • 93bd00a71476:Dagger2入门详解中提到“”此外为什么GithubModule会这样设计,有没有更加单方法?试想当有多种 ApiService 需要用到的时候,OkhttpClient中的超时设置需要不同的时候,Retrofit 实例的 Converter需要不同的时候我们该如何应对?大家可以思考一下,我也在思考。” 请问博主有好的思路吗?

      本文标题: Dagger2实战(详细)

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