手动依赖注入
LoginRetrofitService
public class LoginRetrofitService { }UserRemoteDataSource
public class UserRemoteDataSource { private final LoginRetrofitService loginService; public UserRemoteDataSource(LoginRetrofitService loginService) { this.loginService = loginService; } }UserLocalDataSource
public class UserLocalDataSource { }UserRepository
public class UserRepository { private final UserLocalDataSource localDataSource; private final UserRemoteDataSource remoteDataSource; public UserRepository(UserLocalDataSource localDataSource, UserRemoteDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }LoginViewModelFactory
public class LoginViewModelFactory implements ViewModelProvider.Factory { private final UserRepository userRepository; public LoginViewModelFactory(UserRepository userRepository) { this.userRepository = userRepository; } @NonNull @Override @SuppressWarnings("unchecked") public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { return (T) new LoginViewModel(userRepository); } }LoginViewModel
public class LoginViewModel extends ViewModel { private final UserRepository userRepository; public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }ManualLoginActivity
想要获取LoginViewModel,需要根据依赖关系依次创建LoginRetrofitService、UserLocalDataSource、UserRemoteDataSource、UserRepository、LoginViewModelFactory
public class ManualLoginActivity extends AppCompatActivity { private static final String TAG = ManualLoginActivity.class.getSimpleName(); private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LoginRetrofitService loginService = new LoginRetrofitService(); UserLocalDataSource localDataSource = new UserLocalDataSource(); UserRemoteDataSource remoteDataSource = new UserRemoteDataSource(loginService); UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource); LoginViewModelFactory factory = new LoginViewModelFactory(userRepository); loginViewModel = new ViewModelProvider(this, factory).get(LoginViewModel.class); String msg = "【手动DI】依赖注入成功,ViewModel = " + loginViewModel; Log.d(TAG, msg); showText(msg); } private void showText(String text) { TextView tv = new TextView(this); tv.setText(text); tv.setGravity(Gravity.CENTER); tv.setTextColor(Color.BLACK); tv.setPadding(32, 200, 32, 32); setContentView(tv); } }Hilt
实现依赖项注入可为您带来以下优势:
- 重用代码
- 易于重构
- 易于测试
Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码
依赖
libs.version.toml
[versions] ...... ksp = "2.0.20-1.0.24" hilt = "2.57.1" [libraries] ...... hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } [plugins] ...... ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }build.gradle(根目录)
plugins { ...... alias(libs.plugins.ksp) apply false alias(libs.plugins.hilt) apply false }build.gradle(项目目录)
android { ...... compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = '17' } } plugins { ...... alias(libs.plugins.ksp) alias(libs.plugins.hilt) } dependencies { ...... implementation libs.hilt.android ksp libs.hilt.compiler }Hilt 依赖注入
MyApplication
@HiltAndroidApp public class MyApplication extends Application { private static MyApplication instance; @Override public void onCreate() { super.onCreate(); instance = this; } public static Context getContext() { return instance; } }LoginRetrofitService
public class LoginRetrofitService { @Inject public LoginRetrofitService() { } }UserRemoteDataSource
public class UserRemoteDataSource { private final LoginRetrofitService loginService; @Inject public UserRemoteDataSource(LoginRetrofitService loginService) { this.loginService = loginService; } }UserLocalDataSource
public class UserLocalDataSource { @Inject public UserLocalDataSource() { } }UserRepository
@Singleton 全 App 共享一个
@Singleton public class UserRepository { private final UserLocalDataSource localDataSource; private final UserRemoteDataSource remoteDataSource; @Inject public UserRepository(UserLocalDataSource localDataSource, UserRemoteDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }LoginViewModel
@HiltViewModel + 构造函数 @Inject → 依赖自动装配,不再需要手写 Factory。
@HiltViewModel public class LoginViewModel extends ViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }HiltLoginActivity
为带有 @AndroidEntryPoint 注解的其他 Android 类提供依赖项,省去new的过程
@AndroidEntryPoint public class HiltLoginActivity extends AppCompatActivity { private static final String TAG = HiltLoginActivity.class.getSimpleName(); private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 一行搞定:Hilt 已经把 UserRepository 及其下层依赖全部装配好 loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class); String msg = "【Hilt】依赖注入成功,ViewModel = " + loginViewModel; Log.d(TAG, msg); showText(msg); } private void showText(String text) { TextView tv = new TextView(this); tv.setText(text); tv.setGravity(Gravity.CENTER); tv.setTextColor(Color.BLACK); tv.setPadding(32, 200, 32, 32); setContentView(tv); } }@Module和@InstallIn
- @Module 注解的类向 Hilt 提供有关如何创建无法通过构造函数注入提供的类型(例如接口或第三方类)的实例
- @InstallIn 告知 Hilt 每个模块将用在或安装在哪个 Android 类中
@Bind 注入接口
public interface DemoInterface { String demo(); }public class DemoImpl implements DemoInterface { @Inject public DemoImpl() { } @Override public String demo() { return "DemoImpl"; } }存在接口和具体实现,创建一个带有 @Binds 注解的抽象函数实现接口的依赖注入
- 返回值为接口
- 参数为实现
@Module @InstallIn(SingletonComponent.class) public abstract class DemoModule { @Binds public abstract DemoInterface bind(DemoImpl impl); }@AndroidEntryPoint public class HiltModuleActivity extends AppCompatActivity { private static final String TAG = HiltModuleActivity.class.getSimpleName(); @Inject DemoInterface mDemo; // 业务用接口 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout root = new LinearLayout(this); root.setOrientation(LinearLayout.VERTICAL); root.setPadding(48, 200, 48, 48); TextView tv = new TextView(this); tv.setGravity(Gravity.CENTER); tv.setTextColor(Color.BLACK); root.addView(tv); setContentView(root); String msg = "当前实现:" + mDemo.demo(); Log.d(TAG, msg); tv.setText(msg); } }@Provides 注入外部库
@Module @InstallIn(SingletonComponent.class) public class DemoModule { @Provides public Gson provideGson() { return new GsonBuilder().create(); } }@AndroidEntryPoint public class HiltModuleActivity extends AppCompatActivity { @Inject Gson gson; // @Provides 提供的实例 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setText("Gson实例:" + gson.toJson("hello @Provides")); setContentView(tv); } }@Qualifier和@Retention
- @Qualifier 为同一类型定义多个绑定
- @Retention 设置注解声明周期
如下生成@PrettyGson注解,用于Gson实例对象的依赖注入
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface PrettyGson { }@Module @InstallIn(SingletonComponent.class) public class DemoModule { // 默认绑定:压缩输出。 @Provides public Gson provideGson() { return new GsonBuilder().create(); } // 同类型的第二个绑定,靠 @PrettyGson 限定符区分,避免和上面的冲突。 @Provides @PrettyGson public Gson providePrettyGson() { return new GsonBuilder().setPrettyPrinting().create(); } }@AndroidEntryPoint public class HiltModuleActivity extends AppCompatActivity { @Inject Gson gson; // 默认绑定 @Inject @PrettyGson Gson prettyGson; // 限定符绑定 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 用带字段的对象序列化,才能看出 setPrettyPrinting() 的换行缩进效果; // 序列化单个字符串没有"结构",两种输出会一样。 Map<String, String> data = new HashMap<>(); data.put("name", "Kiro"); data.put("action", "@Provides demo"); String compact = gson.toJson(data); String pretty = prettyGson.toJson(data); TextView tv = new TextView(this); tv.setText("默认Gson: " + compact + "\n\nPrettyGson:\n" + pretty); setContentView(tv); } }自带的预定义限定符
如下注入ApplicationContext和ActivityContext
@Inject @ApplicationContext Context appContext; @Inject @ActivityContext Context activityContext;