Android _ 从 Dagger2 到 Hilt 玩转依赖注入(一)
public class UserRemoveDataSource {@Injectpublic UserRemoveDataSource() {}}
你需要用 @Inject 注解修饰依赖项的构造方法,同时,它的依赖项 UserLocalDataSource 和 UserRemoteDataSource 也需要增加 @Inject 注解。
以上代码在构建后会自动生成代码:
DaggerApplicationComponent.java
1、实现 ApplicationComponent 接口 public final class DaggerApplicationComponent implements ApplicationComponent {private DaggerApplicationComponent() {}
2、创建依赖项实例 @Overridepublic UserRepository userRepository() {return new UserRepository(new UserLocalDataSource(), new UserRemoteDataSource());}
3、构建者模式 public static Builder builder() {return new Builder();}
public static ApplicationComponent create() {return new Builder().build();}
public static final class Builder {private Builder() {}
public ApplicationComponent build() {return new DaggerApplicationComponent();}}}
可以看到,最简单的依赖注入模板代码已经自动生成了。使用时,你只需要通过 ApplicationComponent 这个入口就可以获得 UserReopsitory 实例:
ApplicationComponent component = DaggerApplicationComponent.create();
UserRepository userRepository = component.userRepository();
3.2 @Inject 字段注入
有些类不是使用构造器初始化的,例如 Android 框架类 Activity 和 Fragment 由系统实例化,此时就不能再使用 3.1 节 中使用的构造器注入,可以改为字段注入,并手动调用方法请求注入。
构造器注入:(X)public class MyActivity {@Injectpublic MyActivity(LoginViewModel viewModel){...}}
字段注入:class MainActivity : AppCompatActivity() {@Injectlateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {DaggerApplicationComponent.create().inject001(this)super.onCreate(savedInstanceState)...}}public class LoginViewModel {private final UserRepository userRepository;
@Injectpublic LoginViewModel(UserRepository userRepository) {this.userRepository = userRepository;}}
在 Activity 或 Fragment 中使用时,需要注意组件的生命周期:
在 super.onCreate() 中的恢复阶段,Activity 会附加绑定的 Fragment,这些 Fragment 可能需要访问 Activity。为保证数据一致性,应在调用 super.onCreate() 之前在 Activity 的 onCreate() 方法中注入 Dagger。
在使用 Fragment 时,应在 Fragment 的 onAttach() 方法中注入 Dagger,此操作可以在调用 super.onAttach() 之前或之后完成。
3.3 @Singleton / @Scope
@Singleton / @Scope:声明作用域,可以约束依赖项的作用域周期
@Singletonpublic class UserRepository {...}
@Component@Singletonpublic interface ApplicationComponent {...}
在 ApplicationComponent 和 UserRepository 上使用相同的作用域注解,表明两者处于同一个作用域周期。这意味着,同一个 Component 多次提供该依赖项都是同一个实例。你可以直接使用内置的 @Singleton,也可以使用自定义注解:
@Scope@Documented@Retention(RUNTIME)public @interface Singleton {}
@Scope@Retention(RetentionPolicy.RUNTIME)public @interface MyCustomScope {}
提示: 使用 @Singleton 或 @MyCustomScope,效果是完全一样的。
以上代码在构建后会自动生成代码:
public final class DaggerApplicationComponent implements ApplicationComponent {private Provider<UserRepository> userRepositoryProvider;
private DaggerApplicationComponent() {initialize();}
private void initialize() {this.userRepositoryProvider = DoubleCheck.provider(UserRepository_Factory.create(UserLocalDataSource_Factory.create(), UserRemoteDataSource_Factory.create()));}
@Overridepublic UserRepository userRepository() {return userRepositoryProvider.get();}...}
作用域注解约束
有几个关于作用域注解的约束,你需要注意下:
如果某个组件有作用域注解,那么该组件只能给提供带有该注解的类或者不带任何作用域注解的类;
子组件不能使用和某个父组件的相同的作用域注解。
提示: 关于子组件的概念,你可以看 第 3.5 节。
作用域注解规范
只要你满足上面提到的约束规则,Dagger2 框架并不严格限制你定义的作用域语义。你可以按照业务划分作用域,也可以按照生命周期划分作用域。例如:
按照业务划分:@Singleton@LoginScope@RegisterScope
按声明周期划分:@Singleton@ActivityScope@ModuleScope@FeatureScope
不过,按照生命周期划分作用域是更加理想的做法,作用域不应该明确指明其实现目的。
3.4 @Module + @Providers
@Module + @Providers:指示 Dagger 如何实例化一个对象,但不是以构造器的方式
public class UserRemoteDataSource {private final LoginRetrofitService loginRetrofitService;@Injectpublic UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {this.loginRetrofitService = loginRetrofitService;}}
@Modulepublic class NetworkModule {@Providespublic LoginRetrofitService provide001(OkHttpClient client) {return new Retrofit.Builder().baseUrl("https://example.com").build().create(LoginService.class);}}
@Singleton@Component(modules = NetworkModule.class)public interface ApplicationComponent {
UserRepository userRepository();
void inject001(MainActivity activity);}
@Module 模块提供了一种与 @Inject 不同的提供对象实例的方式。在 @Module 里,@Provides 方法的返回值是依赖项实例,而参数是进一步依赖的对象。另外,你还需要在 @Component 参数中应用该模块。
目前为止,我们构造的依赖关系图如下所示:
3.5 @Subcomponent
@Subcomponent:声明子组件,使用子组件的概念可以定义更加细致的作用域
子组件是继承并扩展父组件的对象图的组件,子组件中的对象就可以依赖于父组件中提供的对象,但是父组件不能依赖于子组件依赖的对象(简单的包含关系,对吧?)。
我们继续通过一个简单的例子来展开:假设我们有一个登录模块 LoginActivity,它依赖于 LoginModel。我们的需求是定义一个子组件,它的声明周期
只在一次登录流程中存在。在 第 3.2 节 提过,Activity 无法使用构造器注入,所以 LoginActivity 我们采用的是 @Inject 字段注入的语法:
@Subcomponentpublic interface LoginComponent {void inject(LoginActivity activity);}
但是这样定义的 LoginComponent 还不能真正称为某个组件的子组件,需要增加额外声明:
@Module(subcomponents = LoginComponent.class)public class SubComponentsModule {}
@Component(modules = {NetworkModule.class,SubComponentsModule.class})@Singletonpublic interface ApplicationComponent {UserRepository userRepository();LoginComponent.Factory loginComponent();}
@Subcomponentpublic interface LoginComponent {@Subcomponent.Factoryinterface Factory{LoginComponent create();}void inject001(LoginActivity activity);}
在这里,我们需要定义一个新模块 SubcomponentModule,同时需要在 LoginComponent 中定义子组件 Factory,以便 ApplicationComponent 知道如何创建 LoginComponent 的示例。
现在,LoginComponent 就算声明完成了。为了让 LoginComponent 保持和 LoginActivity 相同的生命周期,你应该在 LoginActivity 内部创建 LoginComponent 实例,并持有引用:
public class LoginActivity extends Activity {1、持有子组件引用,保证相同生命周期 LoginComponent loginComponent;
2、@Inject 字段注入 @InjectLoginViewModel loginViewModel;
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
3、创建子组件实例 loginComponent = ((MyApplication) getApplicationContext()).appComponent.loginComponent().create();4、注入 loginComponent.inject(this);...}}
执行到步骤 4 ,loginViewModel 字段就初始化完成了。这里有一个需要特别注意的点,你思考这个问题:如果你在 LoginActivity 中的一个 Fragment 重复注入 LoginViewModel,它是一个对象吗?
@Subcomponentpublic interface LoginComponent {
@Subcomponent.Factoryinterface Factory {LoginComponent create();}
void inject001(LoginActivity loginActivity);void inject002(LoginUsernameFragment fragment);}
肯定是不同对象的,因为我们还没有使用 第 3.3 节 提到的 @Singleton / @Scope 作用域注解。现在我们增加作用域注解:
@Scope@Retention(RetentionPolicy.RUNTIME)public @interface ActivityScope {}
@ActivityScope@Subcomponentpublic interface LoginComponent { ... }
@ActivityScopepublic class LoginViewModel {private final UserRepository userRepository;
@Injectpublic LoginViewModel(UserRepository userRepository) {this.userRepository = userRepository;}}
目前为止,我们构造的依赖关系图如下所示:
4. 在 Dagger2 的基础上进行单元测试
当一个项目应用了 Dagger2 或者其它依赖注入框架,那么在一定程度上它的各个组件之间是处于一种松耦合的状态,此时进行单元测试显得游刃有余。
在 Dagger2 项目上你可以选择在不同级别上注入模拟依赖项:
4.1 对象级别:
你可以定义一个 FakeLoginViewModel,然后替换到 LoginActivity:
public class LoginActivity extends Activity {1、持有子组件引用,保证相同生命周期 LoginComponent loginComponent;
2、@Inject 字段注入 @InjectFakeLoginViewModel loginViewModel;}
4.2 组件级别
你可为为正式版和测试版定义两个组件:ApplicationComponent 和 TestApplicationComponent:
@Singleton@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})public interface TestApplicationComponent extends ApplicationComponent {}
5. 总结
总结一下我们提到的注解:
评论