以下是一个基于 Java 语言 的 Android MVVM 登录模块详细实现,结合 ViewModel
、LiveData
、DataBinding
和 Repository
模式,确保代码解耦、可维护性强。
一个完整的MVVM登录模块方案,会包括以下核心组件:
Model层使用Repository模式管理数据源,包括网络数据源(使用Retrofit)和本地数据源(使用Room)。
ViewModel层处理业务逻辑,使用LiveData暴露UI状态。
View层(Activity)使用DataBinding实现数据绑定,观察ViewModel的LiveData状态变化。使用单向和双向数据绑定技术简化UI更新。
项目结构概览
app/
├── src/
│ ├── main/
│ │ ├── java/com/example/login/
│ │ │ ├── data/
│ │ │ │ ├── UserRepository.java # 数据仓库
│ │ │ │ ├── local/UserDao.java # Room数据库操作
│ │ │ │ ├── remote/ApiService.java # 网络请求接口
│ │ │ ├── model/
│ │ │ │ ├── User.java # 用户实体类
│ │ │ ├── ui/
│ │ │ │ ├── LoginActivity.java # View层
│ │ │ │ ├── LoginViewModel.java # ViewModel层
│ │ │ ├── utils/
│ │ │ │ ├── BindingAdapters.java # 自定义绑定适配器
│ │ ├── res/
│ │ │ ├── layout/activity_login.xml # 使用DataBinding的布局
1. 添加依赖(build.gradle)
dependencies {
// MVVM核心组件
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.6.0'
implementation 'androidx.lifecycle:lifecycle-livedata:2.6.0'
implementation 'androidx.databinding:databinding-runtime:7.0.0'
// Room数据库(可选,用于本地缓存)
implementation 'androidx.room:room-runtime:2.5.0'
annotationProcessor 'androidx.room:room-compiler:2.5.0'
// 网络请求(Retrofit + Gson)
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
2. Model层实现
2.1 用户实体类(User.java)
public class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Getters & Setters
public String getUsername() { return username; }
public String getPassword() { return password; }
}
2.2 数据仓库(UserRepository.java)
public class UserRepository {
private final ApiService apiService;
private final UserDao userDao;
public UserRepository(ApiService apiService, UserDao userDao) {
this.apiService = apiService;
this.userDao = userDao;
}
public LiveData<Boolean> login(String username, String password) {
MutableLiveData<Boolean> result = new MutableLiveData<>();
// 优先检查本地数据库
userDao.getUser(username, password).observeForever(localUser -> {
if (localUser != null) {
result.setValue(true); // 本地验证成功
} else {
// 本地无数据则发起网络请求
apiService.login(new User(username, password)).enqueue(new Callback<Boolean>() {
@Override
public void onResponse(Call<Boolean> call, Response<Boolean> response) {
if (response.isSuccessful() && response.body()) {
userDao.saveUser(new User(username, password)); // 缓存登录成功的用户
}
result.setValue(response.body());
}
@Override
public void onFailure(Call<Boolean> call, Throwable t) {
result.setValue(false);
}
});
}
});
return result;
}
}
2.3. UserDao (本地数据库访问)
位于 data/local/UserDao.java
,使用 Room 持久化框架实现本地缓存
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.lifecycle.LiveData;
@Dao
public interface UserDao {
// 根据用户名和密码查询用户(用于登录验证)
@Query("SELECT * FROM users WHERE username = :username AND password = :password")
LiveData<User> getUser(String username, String password);
// 插入或更新用户(用于缓存登录成功的用户数据)
@Insert(onConflict = OnConflictStrategy.REPLACE)
void saveUser(User user);
// 删除用户(可选)
@Query("DELETE FROM users WHERE id = :userId")
void deleteUser(int userId);
}
2.4配套实体类 User.java
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
private String username;
private String password;
// 构造方法
public User(String username, String password) {
this.username = username;
this.password = password;
}
// Getters & Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}
2.5. ApiService (网络请求接口)
位于 data/remote/ApiService.java
,使用 Retrofit 定义网络API
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface ApiService {
// 登录API(POST请求,提交User对象)
@POST("auth/login")
Call<Boolean> login(@Body User user);
// 获取用户信息(可选扩展)
@POST("user/profile")
Call<User> getUserProfile(@Body String userId);
}
2.6Retrofit实例化工具类 RetrofitClient.java
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static final String BASE_URL = "https://your-api-domain.com/";
private static Retrofit retrofit;
public static Retrofit getInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
public static ApiService getApiService() {
return getInstance().create(ApiService.class);
}
}
2.7 BindingAdapters.java
以下是基于 MVVM 架构的 BindingAdapters
工具类实现,包含图片加载、视图显隐控制等常用功能,解决 DataBinding
自定义属性绑定问题:
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import androidx.annotation.DrawableRes;
import androidx.databinding.BindingAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
public class BindingAdapters {
// 1. 图片加载(支持URL、错误占位图)[4,7](@ref)
@BindingAdapter(value = {"imageUrl", "errorPlaceholder"}, requireAll = false)
public static void loadImage(ImageView view, String imageUrl, Drawable errorPlaceholder) {
if (imageUrl != null && !imageUrl.isEmpty()) {
Glide.with(view.getContext())
.load(imageUrl)
.apply(new RequestOptions().error(errorPlaceholder))
.into(view);
} else if (errorPlaceholder != null) {
view.setImageDrawable(errorPlaceholder);
}
}
// 2. 资源ID加载图片(避免直接传int导致资源混淆)[4](@ref)
@BindingAdapter("android:src")
public static void setImageResource(ImageView view, @DrawableRes int resId) {
view.setImageResource(resId);
}
// 3. 控制视图显隐(支持布尔值)[5](@ref)
@BindingAdapter("visibleIf")
public static void setVisible(View view, boolean visible) {
view.setVisibility(visible ? View.VISIBLE : View.GONE);
}
// 4. 进度条绑定(支持双向绑定)[5](@ref)
@BindingAdapter("android:progress")
public static void setProgress(ProgressBar progressBar, int progress) {
if (progressBar.getProgress() != progress) {
progressBar.setProgress(progress);
}
}
}
3. ViewModel层(LoginViewModel.java)
public class LoginViewModel extends ViewModel {
private UserRepository userRepository;
private MutableLiveData<Boolean> loginResult = new MutableLiveData<>();
private MutableLiveData<String> errorMessage = new MutableLiveData<>();
private MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
// 双向绑定支持:用户名和密码
public MutableLiveData<String> username = new MutableLiveData<>();
public MutableLiveData<String> password = new MutableLiveData<>();
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 登录操作
public void onLoginClick() {
if (username.getValue() == null || password.getValue() == null) {
errorMessage.setValue("用户名或密码不能为空");
return;
}
isLoading.setValue(true);
userRepository.login(username.getValue(), password.getValue())
.observeForever(new Observer<Boolean>() {
@Override
public void onChanged(Boolean success) {
isLoading.setValue(false);
if (success) loginResult.setValue(true);
else errorMessage.setValue("登录失败,请重试");
}
});
}
// Getters for LiveData
public LiveData<Boolean> getLoginResult() { return loginResult; }
public LiveData<String> getErrorMessage() { return errorMessage; }
public LiveData<Boolean> getIsLoading() { return isLoading; }
}
4. View层实现
4.1 布局文件(activity_login.xml)使用DataBinding
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="viewModel" type="com.example.login.ui.LoginViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- 双向绑定用户名 -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.username}"
android:hint="用户名" />
<!-- 双向绑定密码 -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.password}"
android:inputType="textPassword"
android:hint="密码" />
<!-- 登录按钮绑定ViewModel方法 -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onLoginClick()}"
android:text="登录"
android:enabled="@{!viewModel.isLoading}" />
<!-- 加载状态提示 -->
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
<!-- 错误信息提示 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.errorMessage}"
android:textColor="#FF0000" />
</LinearLayout>
</layout>
4.2 Activity(LoginActivity.java)
public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding binding;
private LoginViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_login);
// 初始化ViewModel(需注入Repository)
UserRepository repository = new UserRepository(RetrofitClient.getApiService(), AppDatabase.get(this).userDao());
viewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new LoginViewModel(repository);
}
}).get(LoginViewModel.class);
binding.setViewModel(viewModel);
binding.setLifecycleOwner(this); // 支持LiveData自动更新UI
// 监听登录结果
viewModel.getLoginResult().observe(this, success -> {
if (success) startActivity(new Intent(this, MainActivity.class));
});
}
}
总结
- MVVM核心优势:
通过LiveData
+DataBinding
实现数据驱动UI,ViewModel
管理业务逻辑,Repository
统一数据源。 - 关键实践:
双向绑定简化输入处理、SingleLiveEvent
处理一次性事件、避免在ViewModel
中持有View
引用。 - 扩展性:
可轻松集成Room
持久化、Retrofit
网络请求、Dagger/Hilt
依赖注入。