Android 之 MVVM架构
以下是一个基于 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 & Setterspublic 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>() {@Overridepublic void onResponse(Call<Boolean> call, Response<Boolean> response) {if (response.isSuccessful() && response.body()) {userDao.saveUser(new User(username, password)); // 缓存登录成功的用户}result.setValue(response.body());}@Overridepublic 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 & Setterspublic 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>() {@Overridepublic void onChanged(Boolean success) {isLoading.setValue(false);if (success) loginResult.setValue(true);else errorMessage.setValue("登录失败,请重试");}});}// Getters for LiveDatapublic 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><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="16dp"><!-- 双向绑定用户名 --><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.username}"android:hint="用户名" /><!-- 双向绑定密码 --><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="@={viewModel.password}"android:inputType="textPassword"android:hint="密码" /><!-- 登录按钮绑定ViewModel方法 --><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:onClick="@{() -> viewModel.onLoginClick()}"android:text="登录"android:enabled="@{!viewModel.isLoading}" /><!-- 加载状态提示 --><ProgressBarandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" /><!-- 错误信息提示 --><TextViewandroid: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;@Overrideprotected 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@Overridepublic <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
依赖注入。