1. 背景与目标 (Background and Goal)
背景:
我们要创建一个用户登录界面。用户输入用户名和密码,点击“登录”按钮。应用会显示一个加载中的“圈圈”(ProgressBar),然后模拟一个耗时2秒的网络请求。根据请求结果,界面会显示“登录成功”或“登录失败”的消息。
核心挑战:
- 在等待网络响应的2秒内,如果用户旋转屏幕,应用不能崩溃,加载状态不能丢失(“圈圈”必须继续转),用户输入的内容也不能丢失。
- 整个应用的组件(网络服务、数据仓库、ViewModel等)需要被优雅地组织和管理,而不是在Activity里手动创建一大堆对象。
我们的目标:
通过这个例子,你将看到:
- Hilt 如何像一个总管家,自动创建并“注入”所有需要的对象。
- Repository 和 ApiService 如何使用**回调(Callback)**来处理异步的网络请求。
- ViewModel 如何作为UI的“大脑”,管理所有UI状态(加载状态、错误信息、用户输入),并轻松应对屏幕旋转,保证数据和状态不丢失。
- Activity 如何变得非常“轻量”,只负责展示
ViewModel
提供的数据和发送用户指令。
2. 登场角色与职责
AuthCallback
(接口): “汇报合同”。定义了登录任务的结果必须如何汇报(成功或失败)。AuthApiService
(模拟的网络服务): “远程服务器接口人”。负责执行真正的(模拟的)登录网络请求,并在完成后,通过AuthCallback
汇报结果。AuthRepository
(数据仓库): “业务逻辑层”。作为ViewModel
和数据源之间的中间人,它负责调用AuthApiService
。LoginViewModel
(UI的“大脑”): “状态管理器”。持有所有UI需要的数据和状态(加载中?成功/失败信息?),处理用户的登录请求,并在屏幕旋转后存活下来。LoginActivity
(UI界面): “视图展示层”。一个“傻瓜”界面,只负责显示LoginViewModel
给它的数据,和告诉LoginViewModel
“用户点击了登录按钮”。
数据流向:
- 指令流:
Activity
->ViewModel
->Repository
->ApiService
- 结果流:
ApiService
--(Callback)–>Repository
--(Callback)–>ViewModel
--(LiveData)–>Activity
3. 代码实现:一步步构建
第1步:定义“合同”和“网络工人”
AuthCallback.java
public interface AuthCallback {void onSuccess(String successMessage);void onFailure(String errorMessage);
}
AuthApiService.java
// 这个类模拟与远程服务器的通信
@Singleton // 整个应用共享一个实例
public class AuthApiService {@Inject // Hilt知道如何创建它public AuthApiService() {}// 模拟登录,这是一个异步操作public void login(String username, String password, AuthCallback callback) {System.out.println("【API服务】: 开始向服务器发送登录请求...");// 用Handler模拟2秒的网络延迟new Handler(Looper.getMainLooper()).postDelayed(() -> {if (username.equals("admin") && password.equals("123456")) {// 登录成功,通过callback通知调用者System.out.println("【API服务】: 服务器响应:登录成功!");callback.onSuccess("欢迎回来, " + username);} else {// 登录失败,通过callback通知调用者System.out.println("【API服务】: 服务器响应:用户名或密码错误!");callback.onFailure("用户名或密码错误");}}, 2000);}
}
第2步:创建“业务逻辑层”
AuthRepository.java
@Singleton
public class AuthRepository {private final AuthApiService apiService;@Injectpublic AuthRepository(AuthApiService apiService) {this.apiService = apiService;}// Repository的方法,它接收ViewModel的指令,并把callback传递给下一层public void login(String username, String password, AuthCallback callback) {System.out.println("【仓库层】: 收到登录请求,正在转交给API服务...");apiService.login(username, password, callback);}
}
第3步:创建 Hilt 的“制造手册”
di/module/AppModule.java
@Module
@InstallIn(SingletonComponent.class)
public class AppModule {// 因为 AuthApiService 和 AuthRepository 的构造函数都被@Inject标记了,// Hilt可以自动创建它们,所以这个Module暂时可以是空的。// 如果它们来自第三方库,我们就在这里写 @Provides 方法。
}
讲解:在这个例子中,因为我们的类构造函数很简单,都用了 @Inject
,所以 Hilt 可以自动处理。但保留这个文件是好的实践,未来可以用来提供 Room
, Retrofit
等复杂对象。
第4. 创建 UI 的“大脑” (ViewModel
)
LoginViewModel.java
@HiltViewModel // 1. 标记为Hilt管理的ViewModel
public class LoginViewModel extends ViewModel {private final AuthRepository authRepository;// 2. 定义UI所需的所有状态,并用LiveData包裹private final MutableLiveData<Boolean> _isLoading = new MutableLiveData<>(false);public final LiveData<Boolean> isLoading = _isLoading;private final MutableLiveData<String> _loginResult = new MutableLiveData<>();public final LiveData<String> loginResult = _loginResult;@Inject // 3. Hilt会自动注入AuthRepositorypublic LoginViewModel(AuthRepository authRepository) {this.authRepository = authRepository;}// 4. 定义一个方法,供UI层调用以触发登录逻辑public void onLoginClicked(String username, String password) {System.out.println("【ViewModel】: 收到UI的登录点击事件!");_isLoading.setValue(true); // 更新状态为“正在加载”// 5. 调用Repository,并提供一个匿名的Callback实现来处理结果authRepository.login(username, password, new AuthCallback() {@Overridepublic void onSuccess(String successMessage) {System.out.println("【ViewModel】: 收到成功回调!正在更新UI状态...");_loginResult.postValue(successMessage);_isLoading.postValue(false); // 更新状态为“加载结束”}@Overridepublic void onFailure(String errorMessage) {System.out.println("【ViewModel】: 收到失败回调!正在更新UI状态...");_loginResult.postValue(errorMessage);_isLoading.postValue(false); // 更新状态为“加载结束”}});}
}
第5步:创建“轻量”的 UI 界面
activity_login.xml
(布局文件)
<LinearLayout ...><EditText android:id="@+id/username_input" ... /><EditText android:id="@+id/password_input" ... /><Button android:id="@+id/login_button" android:text="登录" ... /><ProgressBar android:id="@+id/loading_spinner" android:visibility="gone" ... />
</LinearLayout>
LoginActivity.java
@AndroidEntryPoint
public class LoginActivity extends AppCompatActivity {private LoginViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);// 用标准方式获取Hilt准备好的ViewModelviewModel = new ViewModelProvider(this).get(LoginViewModel.class);EditText usernameInput = findViewById(R.id.username_input);EditText passwordInput = findViewById(R.id.password_input);Button loginButton = findViewById(R.id.login_button);ProgressBar loadingSpinner = findViewById(R.id.loading_spinner);// --- UI 操作:只负责通知 ViewModel ---loginButton.setOnClickListener(v -> {String username = usernameInput.getText().toString();String password = passwordInput.getText().toString();viewModel.onLoginClicked(username, password);});// --- UI 观察:只负责根据 ViewModel 的数据更新自己 ---viewModel.isLoading.observe(this, isLoading -> {System.out.println("【Activity】: 观察到isLoading状态变为 " + isLoading);loadingSpinner.setVisibility(isLoading ? View.VISIBLE : View.GONE);loginButton.setEnabled(!isLoading);});viewModel.loginResult.observe(this, resultMessage -> {System.out.println("【Activity】: 观察到loginResult变化: " + resultMessage);Toast.makeText(this, resultMessage, Toast.LENGTH_SHORT).show();});}
}
4. 运行与分析:屏幕旋转的考验
- 输入正确的用户名(“admin”)和密码(“123456”),点击登录。
- 日志会显示指令从 Activity -> ViewModel -> Repository -> ApiService 传递。
- UI上,登录按钮会变灰,加载“圈圈”开始旋转。
- 在2秒的等待时间内,立刻旋转手机屏幕!
LoginActivity
被销毁并重建。- 但
LoginViewModel
还活着,它的_isLoading
状态依然是true
。 - 新的
LoginActivity
创建后,重新获取到同一个ViewModel
实例。 viewModel.isLoading.observe(...)
被重新设置,它立刻收到了当前的true
值,所以加载“圈圈”依然在旋转,按钮依然是灰色。UI状态完美恢复!
- 2秒结束后…
AuthApiService
的回调被触发,最终调用到ViewModel
里的onSuccess
。ViewModel
调用_isLoading.postValue(false)
和_loginResult.postValue("欢迎回来...")
。- 即使是在新的
Activity
实例中,它的Observer
也能立刻收到这两个更新。 - UI上,加载“圈圈”消失,按钮恢复,并弹出一个“登录成功”的
Toast
。
结论:在这个例子中,Hilt 负责组装,Callback 负责异步通信,ViewModel 负责状态保持。三者结合,构成了一套非常强大、健壮、且易于测试的现代安卓应用架构,轻松解决了异步操作和屏幕旋转带来的各种难题。