Dagger 2 从入门到放弃

1,431 阅读7分钟

Dagger2介绍

为什么我们需要注解依赖注入?

Dependency Injection(注解依赖)是在IOC(Inversion of cnontrol)的基础上实现的,一个类的依赖需要从外部进行获取。换句话说一个类不能直接实例化另一个类,需要从配置文件中获取实例化的类的对象。

如果一个类使用new操作符来实例化另外一个类,那么这两个类直接就产生依赖关系,被称为Hard dependency。

依赖注入的优点

  • 增加类的可复用性,可以独立于其他类进行测试;

Dagger2

Dagger2使用注解的方式来构建依赖图,在工程构建的过程中,使用Javax inject包生成依赖,在程序运行之前就可以就可以对代码中的错误进行检测。

注入模式

  • 构造函数注入:以传递参数的方式;
  • 字段(Field)注入:以变量的方式,不可以注入private类型;
  • 方法注入:以传递参数的方式;

“(dependency consumer)依赖调用者”通过一个“连接器”从“(dependency provider)依赖提供者”调用“依赖对象”

  • Dependency provider:使用注解@Module表示的类,用于提供可以进行注入的对象。类中的方法使用注解@Providers表示该方法的返回对象可以被依赖注入。@Moudle可将引用的类(非自己编码的类)的注入,如果是自己编码的类仅用@Inject即可
  • Dependency consumer:注解@Inject用于定义一个依赖。
  • Connection consumer and producer:使用注解@Component的接口定义module对象中provider与依赖对象之间的连接,接口的实现类由Dagger自动生成。

Dagger2的局限性

  • Dagger2不能自动注入域
  • Dagger2不能注入private类型
  • Dagger2如果需要注入field,需要在使用@Component注解的接口中,定义一个提供变量的方法。

注入结构图

在Android开发中通用的注入方式形成的依赖结构如下图所示: 当AppComponent通过其modules构建完成后,我们就在结构图中有了所有我们需要的实例了。如果我们需要将Activity连接到Dagger的依赖结构图中,构建一个使用@Subcomponent注解的Component即可。

在使用上述方法进行构建时,我们需要向不断向AppModule中添加subComponent,而且子component中相似的代码较多,Dagger2中提供@ContributesAndroidInjector注解来解决这一问题,依赖结构图如下图所示:

代码实现

我们采用第二种结构进行代码的实现

最简方式,向Fragment和Activity中注入UserDao对数据库进行操作。

项目结构

数据库实现

    //User
    @Entity
    public class User {

        @PrimaryKey(autoGenerate = true)
        @ColumnInfo
        private int id;

        @ColumnInfo
        private String name="";
        //省略Getter和Setter
    }
    //UserDao
    @Dao
    public abstract class UserDao {

        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public abstract void insert(User user);

        @Query("select * from user")
        public abstract List<User> getUsers();

    }
    //UserDb
    @Database(entities = {User.class}, version = 1,exportSchema = false)
    public abstract class UserDb extends RoomDatabase {

        public abstract UserDao userDao();

    }

测试用的Bean类

    public class Bean {
        public Bean(){
    }

    public void printInfo(){
        Log.i("++",this.toString());
    }
    }

AppMoudle实现

AppMoudle中用于提供一些全局的单例,如:Retrofit、OkHttp、Room等。

    @Module
    public class AppModule {

        @Provides
        @Singleton
        public Context provideContext(Application application) {
            return application;
        }

    //用于注入DB单例

    //这里只能用Application 而非AppApplication,用user_db而非user.db
    @Singleton
    @Provides
    public UserDb provideDb(Application application) {
        return Room.databaseBuilder(application, UserDb.class, "user.db").build();
    }

    //用于注入UserDao
    @Singleton
    @Provides
    public UserDao provideUserDao(UserDb userDb){
        return userDb.userDao();
    }

    }


MainActivityModule实现

在MainActivityModule提供一些需要在MainActivity中进行注入的实例。

    @Module
    public class MainActivityModule {

        @Provides
        Bean provide(){
            return new Bean();
        }

    }
    

FragmentBuildersModule实现

    @Module
    public abstract class FragmentBuildersModule {

         @ContributesAndroidInjector
          abstract MainFragment contributeDetailFragment();
    }

AppComponent实现

AppComponent使用注解@Component,程序在编译的工程中通过AppComponent形成依赖注入关系图,Component使用modules提供注入的实例。

@Component.Builder在构建的过程中,有时需要向关系图中绑定一些实例,在本项目中绑定的是Application。

注:如果需要在Component中提供Builder,Builder接口中需要添加一个builder()方法,该方法返回Component

    @Singleton
    @Component(modules = {AndroidInjectionModule.class, AppModule.class, ActivityBuildersModule.class})
    public interface AppComponent {

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

        void inject(AppApplication application);

    }

AppApplication实现

.application(this)方法将application实例绑定职Dagger的依赖图标中。Application需要实现HasActivityInjector接口,有Activity需要注入。如果Activity中有Fragment,则Activity中需要实现HasSupportFragmentInjector接口,

    public class AppApplication extends Application implements HasActivityInjector {

       @Inject
        DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;

        @Override
        public void onCreate() {
            super.onCreate();
            DaggerAppComponent
            .builder()
            .application(this)
            .build()
            .inject(this);
        }

        @Override
        public DispatchingAndroidInjector<Activity> activityInjector() {
            return activityDispatchingAndroidInjector;
        }
    }

MainActivity中实现

在Activity中以及Fragment中都需要将依赖进行注入, AndroidInjection.inject(this); AndroidSupportInjection.inject(this);

    public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {

        @Inject
        DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;

        @Inject
        MainFragment mainFragment;

        @Inject
        Context context;

        @Inject
        Bean bean;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            AndroidInjection.inject(this);
            setContentView(R.layout.activity_main);
            getSupportFragmentManager().beginTransaction().replace(R.id.container, mainFragment).commitAllowingStateLoss();
        }

        @Override
        public AndroidInjector<Fragment> supportFragmentInjector() {
            bean.printInfo();
            return dispatchingAndroidInjector;
        }
    }

MainFragment实现

    public class MainFragment extends Fragment {

        @Inject
        UserDao userDao;

        //用于获取DetailFragment实例
        FragmentDetailBinding fragmentDetailBinding;

        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

             AndroidSupportInjection.inject(this);
             fragmentDetailBinding = DataBindingUtil
.inflate(inflater, R.layout.fragment_detail, container, false);
             fragmentDetailBinding.insertDateTv.setOnClickListener(new View.OnClickListener() {
        @Override
         public void onClick(View v) {
                new Thread(new Runnable() {
                     @Override
                     public void run() {
                            User user = new User("++++");
                            userDao.insert(user);
                            Log.i("User插入", "+++");
                    }
                }).start();
        }
});

        fragmentDetailBinding.readDataTv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                                Log.i("User数量:", userDao.getUsers().size() + "");
                        }
                    }).start();
                }
        });
            return fragmentDetailBinding.getRoot();
    }

}

源码地址

本文参考:medium.com/@iammert/ne…

使用DaggerActivity, DaggerFragment, DaggerApplication

在上述代码中,在进行注入时每一个Activity中都需要实现HasFragmentInjector或者HasSupportFragmentInjector接口,再进行注入,在Dagger 2 中对接口的实现和注入进行封装

修改AppComponent, 使得AppComponent继承AndroidInjector<DaggerApplication>,将AndroidInjectionModule修改为AndroidSupportInjectionModule

    @Singleton
    @Component(modules = {AndroidSupportInjectionModule.class, AppModule.class, ActivityBuildersModule.class})
    public interface AppComponent extends AndroidInjector<DaggerApplication> {
            @Component.Builder
            interface Builder {
                @BindsInstance
                Builder application(Application application);
                AppComponent build();
            }
             void inject(AppApplication app);
            @Override
            void inject(DaggerApplication instance);

    }

修改AppApplcation

    public class AppApplication extends DaggerApplication {

        @Override
        protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
            AppComponent appComponent=DaggerAppComponent.builder().application(this).build();
            appComponent.inject(this);
             return appComponent;
        }
    }

修改MainActivity

    public class MainActivity extends DaggerAppCompatActivity {

        @Inject
        DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;

        @Inject
        MainFragment mainFragment;

        @Inject
        Context context;

        @Inject
        Bean bean;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                AndroidInjection.inject(this);
                setContentView(R.layout.activity_main);
            getSupportFragmentManager().beginTransaction().replace(R.id.container, mainFragment).commitAllowingStateLoss();
            bean.printInfo();
        }
    }

源码地址

注入带构造函数的ViewModel

在初始化一个带参数的ViewModel,需要使用ViewModelProvider.Factory的实例进行初始化,如果将一个ViewModelFactory用在不同的ViewModel上,ViewModelFactory需要对各种不同的参数进行处理。

在本项目中使用线程池对插入和读取数据的线程进行管理;使用UserRepo对User的插入和读取进行管理。

在Dagger2中有 Multibindings为对象创建一个Map:

  • Key:表示ViewModel的类型,在本项目中表示为MainFragmentViewModel::class
  • Vaule:表示ViewModel的实例

Dagger2在编译的时候会创建Map,我们将该Map以参数的形式传入到ViewModelFactory中,当我们调用create()方法时,将会从map中取出正确的实例。

AppExecutors创建

使用Javax包中@Singleton注解,表示其为单例模式;对于自定义类直接在构造函数AppExecutors()上添加注解@Inject,可以直接进行注入,无需在Module中提供providers

    @Singleton
    public class AppExecutors {

    private final Executor diskIO;

    public AppExecutors(Executor diskIO){
            this.diskIO=diskIO;
    }

    @Inject
    public AppExecutors(){
         this(Executors.newSingleThreadExecutor());
    }
    public Executor getDiskIO() {
         return diskIO;
    }
}

UserRepo 创建

直接在构造函数添加@Inject进行注入

    @Singleton
    public class UserRepo {

    private final UserDb userDb;

    private final UserDao userDao;

    private final AppExecutors appExecutors;

    @Inject
    public UserRepo(UserDb userDb,UserDao userDao,AppExecutors appExecutors){
        this.userDb=userDb;
        this.userDao=userDao;
        this.appExecutors=appExecutors;
    }

    public void insertUser(final User user){
        appExecutors.getDiskIO().execute(new Runnable() {
        @Override
        public void run() {
            userDao.insert(user);
        }
    });
}

    public void readUser(){
        appExecutors.getDiskIO().execute(new Runnable() {
        @Override
        public void run() {
            int m =userDao.getUsers().size();
            Log.i("+++","++++"+m);
        }
        });
    }
}

ViewModelFactory创建


    @Singleton
    public class ViewModelFactory implements ViewModelProvider.Factory {

    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
    
        @Inject
        public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
            this.creators = creators;
        }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                  if (modelClass.isAssignableFrom(entry.getKey())) {
                         creator = entry.getValue();
                         break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("未知类" + modelClass);
        }
        try {
            return (T)creator.get();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
// return null;
    }
}

ViewModelModule创建

创建ViewModelModule并使用@Binds注解提供注入


    @Module
    public abstract class ViewModelModule {

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
    }

ViewModelKey创建

创建需要进行多重绑定的Map,并绑定ViewModel


    @Target({ ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @MapKey
    public @interface ViewModelKey {
    Class<? extends ViewModel> value();
    }

在ViewModelModule中添加需要注入的ViewModel

通过@Binds @IntoMap @ViewModelKey 注解,提供给前文创建的Map数据,Key为 MainFragmentViewModel.class value为MainFragmentViewModel 实例


    @Module
    public abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(MainFragmentViewModel.class)
    abstract ViewModel bindViewModel(MainFragmentViewModel detailViewModel);
    }

在AppMoudle中添加ViewModelModule


    @Module(includes = {DbModule.class,ViewModelModule.class})
    public class AppModule {

    }

修改MainFragment


    public class MainFragment extends DaggerFragment

    @Inject
    ViewModelProvider.Factory viewModelFactory;

    private MainFragmentViewModel mainFragmentViewModel;


    //用于获取DetailFragment实例
    FragmentDetailBinding fragmentDetailBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        AndroidSupportInjection.inject(this);
        fragmentDetailBinding = DataBindingUtil
.inflate(inflater, R.layout.fragment_detail, container, false);

        fragmentDetailBinding.insertDateTv.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mainFragmentViewModel.insertUser();
    }
    });

    fragmentDetailBinding.readDataTv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mainFragmentViewModel.readUser();
        }
    });
    return fragmentDetailBinding.getRoot();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            mainFragmentViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainFragmentViewModel.class);
    }
    }

源码地址