依赖注入是面向对象编程的一种设计模式,其目的是为了下降程序耦合,这个耦合就是类之间的依赖引发的。java
什么是依赖注入android
类一般须要引用其余类, eg: Car 类可能须要People 类,这些类称为依赖项,Car 依赖于People 才能运行git
之前在Android开发中用到的依赖注入不是不少,知道最近看了几个项目包括写了几周Java项目,才注意到以来注入的方式在现有技术中的地位,在Java Spring Boot 中几乎将依赖注入用到了极致,减小了太多的工做量github
优点:编程
public interface ClassBInterface {
void setClassB(ClassB classB);
}
class ClassB {
public void doSomething() {
}
}
class ClassA implements ClassBInterface {
ClassB classB;
@Override
public void setClassB(ClassB classB) {
this.classB = classB;
}
}
复制代码
通常状况下,Android 中主要用构造函数注入或者set 方法注入设计模式
对于Android来说,Dagger2 无非是如今最好的依赖注入框架,Google亲自操刀,静态编译期完成注入,对于性能不受影响,有利于维护,能减小因为对象引用而形成的OOM等问题。api
DI(dependency injection) ,分三部分:缓存
解释一下什么叫依赖markdown
一个类有两个变量,这两个变量就是他的依赖,初始化依赖两种方法,本身初始化,外部初始化就叫依赖注入。网络
咱们要使用一个组件必定是 先了解它提供了什么?其次是和咱们业务相关联的需求是什么?最后是咱们怎么用它
在Dagger 中
在Android 项目的build.gradle中添加
apply plugin: 'kotlin-kapt'
// ...
implementation 'com.google.dagger:dagger:2.11'
kapt 'com.google.dagger:dagger-compiler:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
//java注解
implementation 'org.glassfish:javax.annotation:10.0-b28'
复制代码
通常用于注入属性、方法、构造方法,项目中注入构造方法的使用方式居多,有两个功能
// 注解构造方法
class Navigator @Inject constructor() {
fun navigator() {
println("navigator method")
}
}
复制代码
class MainActivity : AppCompatActivity() {
// 将Navigator 注入到MainActivty 中,使得MainActivity 具备navigator的引用
@Inject lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigator.navigator()
}
}
复制代码
两个@Inject 注解造成了依赖关系,
what?? 不是说@Inject既然事依赖的提供方也是依赖的需求方吗? 难道我使用错了?
别着急,哈哈 咱们不是说依赖关系中还有@Component 是需求方与提供方之间的桥梁。
注解interface
能够说是Dagger2 容器,是注入依赖和提供依赖之间的桥梁,把提供的依赖注入到所须要注入的依赖中
@Component
interface ApplicationComponent {
//提供一个用于注解的方法
fun inject(application: Dagger2Application)
}
复制代码
@Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger" )
public final class DaggerApplicationComponent implements ApplicationComponent {
private MembersInjector<MainActivity> mainActivityMembersInjector;
private DaggerApplicationComponent(Builder builder) {
//...
复制代码
class Dagger2Application: Application() {
val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
.build()
}
override fun onCreate() {
super.onCreate()
appComponent.inject(this)
}
}
复制代码
class MainActivity : AppCompatActivity() {
private val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
(application as Dagger2Application).appComponent
}
@Inject lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 这才是真正的注入
appComponent.inject(this)
navigator.navigator()
}
}
复制代码
别忘了 在AndroidMianfest.xml 中配置application
固然在一个项目中要用一个框架,咱们须要考虑能不能尽量多的覆盖业务场景,那眼下就有一个问题,Dagger给第三方库提供注解
@Module 和@Provides结合为Dagger2提供依赖关系,对上文@Inject第三点的补充,用于不能用@Inject提供依赖的地方,如第三方库提供的类,基本数据类型等不能修改源码的状况。@Provides仅能注解方法,且方法所在类要有@Module注解。注解后的方法表示Dagger2能用该方法实例对象提供依赖。按照惯例,@Provides方法的命名以provide为前缀,方便阅读管理。
eg:
@Module
class ApplicationModule {
}
复制代码
// 这就简单的提供了Retrofit(第三方库)依赖
@Module
class ApplicationModule {
@Provides
fun provideRetrofit(): Retrofit {
return Retrofit.Builder().build()
}
}
复制代码
通过@Inject的教训,咱们首先想到的应该是怎么和DaggerApplicationComponent 这个接口关联起来
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent {
fun inject(application: Dagger2Application)
fun inject(activity: MainActivity)
}
复制代码
val appComponent: ApplicationComponent by lazy(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
// 是这行
.applicationModule(ApplicationModule())
.build()
}
复制代码
首先@Component注解包含了一个Module类,表示Dagger2能够从Module类查找依赖,Dagger2会自动查找Module类有@Provides注释的方法实例依赖,最后完成注入
这就完成了Dagger2 简单的注解,在项目中也能够进行注解的使用了,固然Dagger2 的注解还有不少,能够参考 :下面连接的文章,
看了这么多,个人初衷是Dagger2 下更加简单,代码量少的网络请求框架,前几天总结的一套框架和朋友们聊了一下 以为比较大,有些臃肿
具体思路是将固定的部分放在dagger2中,而后尽量减小可变的代码
很显然Retrofit 的初始化将是必须放在Dagger2的注解中的
@Module
class ApplicationModule(private val application: Dagger2Application) {
@Provides
@Singleton fun provideApplicationContext(): Context = application
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://gank.io/api/v2/data/category/Girl/type/Girl/")
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun createClient(): OkHttpClient {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
builder.addInterceptor(loggingInterceptor)
}
return builder.build()
}
}
复制代码
有一个 @Singleton 注解,字面意思时单例,可是根据Dagger2 的编译方式,@Singleton只是对你们有一个提示做用,由于每次编译都是先检测某个注解有没有被编译,若是有的话是不会再次去编译的,因此不可能出现不是统一对象的引用的
在应用中选择的网络请求框架应该结合本身的需求去选择,可是任何一个框架咱们都须要处理异常状况以及其余意外的错误状况
// 使用密封类 密封类相似于枚举,可是比枚举更加灵活,能够携带参数等优势
sealed class Failure {
object NetworkConnection : Failure()
object ServerError : Failure()
abstract class FeatureFailure : Failure()
}
复制代码
固然也少不了对返回值的处理, 咱们将返回值处理为两种状态,即成功和失败
sealed class Either<out L, out R> {
data class Error<out L>(val a: L) : Either<L, Nothing>()
data class Success<out R>(val b: R) : Either<Nothing, R>()
val isRight get() = this is Success<R>
val isLeft get() = this is Error<L>
fun <L> left(a: L) = Error(a)
fun <R> right(b: R) = Success(b)
fun fold(fnL: (L) -> Any, fnR: (R) -> Any): Any =
when(this) {
is Error -> fnL(a)
is Success -> fnR(b)
}
}
复制代码
任何一个成体系的东西都不是有具体顺序的,由于打开冰箱把大象放进去是有多种方式的,可是目的都是把大象放进冰箱, 因此咱们不须要担忧怎么去构造出来一个框架,咱们只是按照本身的思路将咱们要的东西写出来,而后完善它。因此不要想这一步以后我该作什么
interface ImageApi {
@GET("page/{page}/count/{size}")
fun images(@Path("page") page: Int, @Path("size") size: Int): Call<ImageEntry>
}
复制代码
@Singleton
class ImageService @Inject constructor(retrofit: Retrofit) : ImageApi {
private val imageApi by lazy { retrofit.create(ImageApi::class.java) }
override fun images(page: Int, size: Int) = imageApi.images(page, size)
}
复制代码
这就将Retrofit和 API链接起来了, 那接下来咱们在哪里使用ImageService 呢?很明显这就是网络请求服务的类,
在项目中推荐将网络请求或者说是数据来源都创建一个仓库,以便于集中处理和数据缓存设计
interface ImageRepository {
fun images(page: Int, size: Int): Either<Failure, ImageEntry>
class NetWork @Inject constructor(
private val networkHandler: NetworkHandler,
private val imageService: ImageService
) : ImageRepository {
override fun images(page: Int, size: Int): Either<Failure, ImageEntry> {
return when (networkHandler.isConnected) {
true -> request(
imageService.images(page, size),
{
it
},
ImageEntry.empty()
)
false, null -> Either.Error(Failure.NetworkConnection)
}
}
private fun <T, R> request(
call: Call<T>,
transform: (T) -> R,
default: T
): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Either.Success(transform((response.body() ?: default)))
false -> Either.Error(Failure.ServerError)
}
} catch (e: Throwable) {
Either.Error(Failure.ServerError)
}
}
}
}
复制代码
通过上面的两部操做,咱们的答题框架已经出来了,如今就是写怎么去执行一个网路请求和怎么将请求到的数据展现到页面
咱们将请求直接概括为一个用例,就是专门用于请求的类
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
operator fun invoke(params: Params, onResult: (Either<Failure, Type>) -> Unit = {}) {
val job = GlobalScope.async(Dispatchers.IO) { run(params) }
GlobalScope.launch(Dispatchers.Main) { onResult(job.await()) }
}
class None
}
复制代码
里面咱们用到了协 程,而且暴露出一个run(params) 方法,用来实现具体的请求
具体的接口请求方法
class GetImage @Inject constructor(private val imageRepository: ImageRepository) :
UseCase<ImageEntry, GetImage.Params>() {
override suspend fun run(params: Params): Either<Failure, ImageEntry> = imageRepository.images(params.page, params.size)
data class Params(val page: Int, val size: Int)
}
复制代码
class ImageViewModel @Inject constructor(private val getImage: GetImage) : BaseViewModel() {
private val _image: MutableLiveData<List<Image>> = MutableLiveData()
val image: LiveData<List<Image>> = _image
fun loadImage(page: Int, size: Int) = getImage(GetImage.Params(page, size)) {
it.fold(::handleFailure, ::handleImageList)
}
private fun handleImageList(imageEntry: ImageEntry) {
_image.value = imageEntry.toImage()
}
}
复制代码
"::" kotlin 中的双冒号操做符, 表示把一个方法看成一个参数,传递到另外一个方法中进行使用
参考项目: Android-CleanArchitecture-Kotlin
解读项目: github.com/kongxiaoan/…
对于老外的一些项目是特别优秀的,可是因为他们的全英文对于英文薄弱的人来讲看起来不容易理解,我这就是将看到的这个项目进行了拆分,本身又实现了一遍,从中获取到的一些知识,仍是很是值得的