依赖注入是一种广泛用于编程的技术。
1、依赖注入的优势
1.1、重用代码
能够复用一部分公共逻辑,比如如下的start()里面的一些操作,比如创建Engine、创建Car的操作。
1.2、易于重构
1.3、易于测试
class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.start();
}
}
对于2和3的理解,Engine可能有子类,使用的时候才注入(传入)具体的对象,这样对于扩展是开放的,方便重构和测试。反观如果在这个类中直接创建Engine对象或者子类,非常不便于扩展和测试。
上述代码是通过手动实现依赖注入。Hilt、Dagger2是一个在自动实现依赖注入的框架,本文主要介绍Hilt注入框架。
2、Hilt使用
2.1、对象注入,通过构造函数提供注入对象。
public class PersonBean {
String name;
int age;
public PersonBean(String name, int age) {
this.name = name;
this.age = age;
}
@Inject
public PersonBean() {
}
}
2.2、对象注入,通过@Provides和@Moudle提供注入的对象。
@Module
@InstallIn(value = FragmentComponent.class)
public class PersonModule {
@Provides
public static PersonBean getPerson() {
return new PersonBean("one_fragment", 1);
}
}
2.3、接口对象(或者抽象类对象)的注入,通过@Binds和@Moudle获取具体的注入对象。
public interface AnalyticsService {
void analyticsMethods();
}
public class AnalyticsServiceImpl implements AnalyticsService {
...
@Inject
AnalyticsServiceImpl(...) {
...
}
}
@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {
@Binds
public abstract AnalyticsService bindAnalyticsService(
AnalyticsServiceImpl analyticsServiceImpl
);
}
@Binds函数返回类型会告知 Hilt 该函数提供哪个接口的实例。
@Binds 函数参数会告知 Hilt 要提供哪种实现。
2.4、对象注入,通过@EntryPoint 注入。
这种注入方式适合,某些类不支持hilt注入。例如ContentProvider,成员变量就不能通过hilt注入,因为整个hilt注入的起点是在Application的OnCreate,ContentProvider在此之前就初始化了。
@InstallIn(ActivityComponent.class)
@EntryPoint
public interface StudentPoint {
public Student getStudent();
}
@AndroidEntryPoint()
class EnterPointActivity : AppCompatActivity() {
private val TAG = "EnterPointActivity"
@Inject
lateinit var student1 : Student
@Inject
lateinit var cpBean: CpBean
@Inject
lateinit var sound: Sound
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_enter_point)
var point = EntryPointAccessors.fromActivity(this, StudentPoint::class.java)
var student = point.student
}
}
2.5、Component
注入的对象的访问入口是Component,也就是所有注入对象的容器。
Dagger框架中可以手动定义Component和SubComponent和子的SubComponent,导致Component较多,各个Component之间的Scope、moudle的关系,难以维护。
Hilt将Component容器进行了分类。
@Module
@InstallIn(ActivityComponent.class)
public abstract class HiltStudentMoudle {
@Provides
public static CpBean getCpBean() {
return new CpBean("张三", 34);
}
@Binds
public abstract Sound bindSound(SoundImpl sound);
@Provides
@ActivityScoped
public static Student getStudent() {
return new Student("李四", 22);
}
}
上述加入@ActivityScoped注解,在同一个Activity(fragment,view)中,获取的Student对象,就是个单例。
子组件可以使用父组件的绑定作为子组件的依赖项。ViewComponent 可以使用 ActivityComponent 中定义的绑定的依赖项。
3、HiltAndroidApp
@HiltAndroidApp注解与@AndroidEntryPoint注解类似,只不过HiltAndroidApp会触发Dagger代码的生成。@AndroidEntryPoint主要将activity、fragment、view等注入到标准的android component中。
@HiltAndroidApp
public class ExampleApplication extends Application { ... }
4、总结
Hilt与Dagger2对比,Hilt对Component、Scope进行了细分,防止创建过多的Component和SubComponent,注入的依赖对象生命周期由Component生命周期管理,各个Component、module依赖关系更加清晰。