在Android中如何正确使用Dagger2

为什么要依赖注入?

假设:

class Test{
    public Test() {
        SubTest sub= new SubTest();
        ...
    }
}

上例中,每次使用Test时都会在Constructor内调用new SubTest(),而Test须依赖SubTest才能顺利使用,我们无法单独使用Test,这样会产生几个问题:

  • 可重复性:SubTest无法共享。
  • 测试:无法单独测试。
  • 可维护性:难以维护。

更好的设计:

class Test{
    private SubTest sub;
    
    public Test(SubTest  sub) {
        this.sub = sub;
    }
}

这样就可以解决上面的3个问题
这样的设计称做依赖反转(IoC),即一个类若需要用到其他的类则应由外部取代,而不是在内部用新实例化,也是DI框架的核心概念。

Dagger2

Dagger2的起手比较难理解一点,新手需要有一点耐心
dagger2针对安卓出了专门的api,官方地址:https://dagger.dev/dev-guide/android
大概需要这么些类:

在Android中如何正确使用Dagger2_第1张图片
企业微信截图_15936766573291.png

后面会一一说明
源码地址:https://github.com/robinfjb/Android_scaffold_Dagger

1.建立Module:

建立一个类并用@Module注记,表示此模块内部的物件可以让其他类用注入的方式获得。

AppModule
@Module(includes = [ViewModelModule::class,NetworkModule::class])
class AppModule {
    @Singleton
    @Provides
    fun provideContext(app: Application): Context {
        return app
    }
    ...
}
  • 建立方法provideContext并加@Provides标注,返回值为Context 表示在模块内放了Context 供其他类取用
  • @Singleton是会处理成单例。
  • 这样我们就在Module中添加了一个SingletonContext 可以使用了,达到跟原本Application中实现一个单例对象一样的效果。
  • includes表示可以包含其他Module,其中NetworkModule里都是网络相关对象,可查看源码,ViewModelModule后续会说明
ActivityBuildersModule

此Module中指定哪些Activity/Fragment要用Inject的方式取得注入对象
MainActivity为例:

@Module
abstract class ActivityBuildersModule {
    @ContributesAndroidInjector(modules = [MainActivityModule::class])
    abstract fun contributeMainActivity(): MainActivity

    @ContributesAndroidInjector(modules = [NavTestActivityModule::class])
    abstract fun contributeNavTestActivity(): NavTestActivity
}

@ContributesAndroidInjector可以自动生成AndroidInjector.Factory,具体参照https://dagger.dev/dev-guide/android
由于Activity中还有诸多Fragment,所以也需要fragment专属的module,拿MainActivityModule举例:

@Module
abstract class MainActivityModule {
    @ContributesAndroidInjector
    abstract fun contributeHomeFragment(): HomeFragment

    @ContributesAndroidInjector
    abstract fun contributeBindFragment(): BindFragment

    @ContributesAndroidInjector
    abstract fun contributeRoomFragment(): RoomFragment
}

Module的部分到这边就完成了。

2.建立Component:

加上@Component注记,必须要将用到的Module加入Component说明,如此Dagger便会自动产生程序代码让我们能注入那些Module中的项目。

@Singleton
@Component(
        modules = [
            AndroidInjectionModule::class,
            AppModule::class,
            ActivityBuildersModule::class
        ]
)
interface AppComponent {
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(surveyApp: SampleApp)
}

AndroidInjectionModule时dagger包提供的module,可以帮我们做inject
写好后需「Make Project」

3.在Application中初始化:

Make Project完成后Dagger会产生DaggerAppComponent类,然后即可初始化Dagger:

class SampleApp : Application(), HasActivityInjector{
    @Inject 
     lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector
     override fun onCreate() {
        super.onCreate()
        AppInjectorHelper.inject(this)
     }
    override fun activityInjector() = dispatchingAndroidInjector
}

如果在Activity或Fragment中用注入的类,需要在Activity的onCreate方法(在super.onCreate之前),Fragment的onAttach方法中调用AndroidInjection.inject(this)
为了减少代码的侵入性,我们用了AppInjectorHelper这个类:

object AppInjectorHelper {
    fun inject(app: SampleApp) {
        DaggerAppComponent.builder().application(app).build().inject(app)
        app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                handleActivity(activity)
            }

            override fun onActivityStarted(activity: Activity) {}
            override fun onActivityResumed(activity: Activity) {}
            override fun onActivityPaused(activity: Activity) {}
            override fun onActivityStopped(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {}
            override fun onActivityDestroyed(activity: Activity) {}
        })
    }

    private fun handleActivity(activity: Activity) {
        if (activity is Injectable || activity is HasSupportFragmentInjector) {
            AndroidInjection.inject(activity)
        }
        if (activity is FragmentActivity) {
            activity.supportFragmentManager.registerFragmentLifecycleCallbacks(
                    object : FragmentManager.FragmentLifecycleCallbacks() {
                        override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
                            super.onFragmentAttached(fm, f, context)
                            if (f is Injectable) {
                                AndroidSupportInjection.inject(f)
                            }
                        }
                    }, true)
        }
    }
}

interface Injectable {
}

registerActivityLifecycleCallbacks注册每一个Activity的生命周期回调,在onActivity创建的时候调用handleActivity
声明空接口Injectable
Activity如果是Injectable 或者HasSupportFragmentInjector,则会注入
同样的Fragment如果是Injectable,则也会通过AndroidSupportInjection注入

4.在Activity/Fragment中实现:

看下Activity的实现,以MainActivity为例:

class MainActivity : AppCompatActivity(), Injectable, HasSupportFragmentInjector {
    @Inject
    lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector
    override fun supportFragmentInjector(): AndroidInjector = fragmentDispatchingAndroidInjector
    
    ...
}

如果acitivity中有fragment需要用注入,则需要实现HasSupportFragmentInjector对象

再看下fragment的实现:

class HomeFragment : Fragment() , Injectable {
    @Inject
    lateinit var factory: MyViewModelFactory
   override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
       homeViewModel = ViewModelProviders.of(this, factory).get(HomeViewModel::class.java)
    }
...
}

只要实现空接口,以便AppInjectorHelper类运行注入
factory对象就能初始化拿到provide的MyViewModelFactory了,MyViewModelFactory会在下面说明

5.在ViewModel中使用:

  • 我们用到了ViewModle,由于ViewModle需要通过ViewModelProviders来实例化而不是直接调用构造函数,所以在DI的操作上会不太相同,我们会使用Multibindings的方式来封装ViewModelFactory
  • 使用Multibindings产生一个Map>告诉ViewModelFactory要建立哪个ViewModel,令Key为ViewModel的class,而Value就是ViewModel本身,例如Map>
  • Provider表示对象并不在inject的时候就实例化,当使用get()方法时才实例化一个对象,每次get都会产生一个新的对象,因为ViewModel并非单例

1.先定义一个注解类ViewModelKey:

@Documented
@Target(
        AnnotationTarget.FUNCTION
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass)

@MapKey表示此方法的class文件问key

2.建立Module:

@Module
abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(HomeViewModel::class)
    abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}

@Binds是provider的一种简化写法
@IntoMap会产生一个Map>
此处ViewModelKey就将HomeViewModel.class作为key,HomeViewModel对象作为value

3.建立ViewModelProvider.Factory

@Singleton
class MyViewModelFactory @Inject constructor(
        private val creators: Map, @JvmSuppressWildcards Provider>
) : ViewModelProvider.Factory {
    override fun  create(modelClass: Class): T {
        var creator: Provider? = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }
        if (creator == null) {
            throw IllegalArgumentException("unknown model class " + modelClass)
        }
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

creator为上述bindHomeViewModel产生的对象,在create方法中,由class为key找到第一个对象(可能有多个),通过Provider返回这个对象的实例,这样Factory就完成的viewmodel对象的实例化

注意:
这里的MyViewModelFactory 并未在module中声明,也没有实例化,那么HomeFragment 是如何拿到@Inject对象的呢,此处则是Constructor Injection概念:
当我们的注入的Class很多时,Module就会就会变得臃肿,所以对自订的Class我们可以用构造函数@Inject的方式将其加入DI框架中并让Module保持简洁。

class MyViewModelFactory @Inject constructor()

将构造方法声明为@Inject,这样HomeFragment 就可以直接获取到@Inject对象了

这样viewmodel的注入就结束了

5.在Repository中使用:

注意到viewmodel中我们需要一个repository对象,此对象也是注入进来的:

class HomeViewModel @Inject constructor(
        private val repository: HomeRepository
) : ViewModel(){
}

也是用上述提到的Constructor Injection方式:

class HomeRepository @Inject constructor(
        private val apiLibService: ApiLibService
){
    fun getWeather(): LiveData> = apiLibService.weather().asResource()
}

将构造方法注解为@Inject
ApiLibService也是通过注入进来的,但ApiLibService并未采用Constructor Injection方式,而是在NetworkModule这个moduleprovide
那么,什么时候采用provide,什么时候采用Constructor Injection。个人理解:
将普通的类用Constructor Injection来处理,只有像Retrofit这种外部无法获得构造函数才加入Module中,以保持Module的简洁。

以上就是Andriod的Dagger注入的全过程
源码放在此处:https://github.com/robinfjb/Android_scaffold_Dagger

你可能感兴趣的:(在Android中如何正确使用Dagger2)