模块 08:架构模式与导航
目标:理解 Android 项目中常见的架构模式(MVC、MVP、MVVM),掌握 ViewModel、LiveData 和 Jetpack Navigation 组件。
前端对照:架构演进
| 前端架构 | Android 架构 | 核心思想 |
|---|---|---|
| jQuery 面条代码 | MVC(原始) | 没有分离,全部写在 Activity |
| Redux / Vuex | MVP | 明确分离 View 和 Presenter |
| React + Hooks / Zustand | MVVM | 数据驱动 UI,响应式 |
1. MVC 模式(最早期)
老项目(2015年前后)最常见的"伪 MVC",实际上所有逻辑都堆在 Activity 中:
java
// ❌ 典型的"God Activity":既是 Controller 又是 View
public class UserListActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// Model:直接在这里定义数据结构
// View:直接在这里操作 UI
// Controller:直接在这里写业务逻辑
// 全部混在一起!
new Thread(() -> {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.example.com/users")
.build();
try {
Response response = client.newCall(request).execute();
String json = response.body().string();
List<User> users = new Gson().fromJson(json,
new TypeToken<List<User>>(){}.getType());
runOnUiThread(() -> {
RecyclerView rv = findViewById(R.id.rv_users);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new UserAdapter(users));
});
} catch (IOException e) {
runOnUiThread(() -> {
Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
});
}
}).start();
}
}2. MVP 模式(2015-2019 主流)
MVP 是 Android 中最广泛使用的架构模式,老项目中最常见:
View (Activity/Fragment) ←→ Presenter ←→ Model (Repository/API)
↑ ↑
只负责 UI 业务逻辑和数据处理2.1 定义契约
java
// 定义 View 和 Presenter 的接口(契约)
public interface UserListContract {
interface View {
void showLoading();
void hideLoading();
void showUsers(List<User> users);
void showError(String message);
void navigateToDetail(User user);
}
interface Presenter {
void attachView(View view);
void detachView();
void loadUsers();
void onUserClicked(User user);
}
}2.2 Presenter 实现
java
public class UserListPresenter implements UserListContract.Presenter {
private UserListContract.View view;
private UserRepository repository;
public UserListPresenter(UserRepository repository) {
this.repository = repository;
}
@Override
public void attachView(UserListContract.View view) {
this.view = view;
}
@Override
public void detachView() {
this.view = null; // 防止内存泄漏
}
@Override
public void loadUsers() {
if (view == null) return;
view.showLoading();
repository.getUsers(new DataCallback<List<User>>() {
@Override
public void onSuccess(List<User> users) {
if (view != null) {
view.hideLoading();
view.showUsers(users);
}
}
@Override
public void onError(Throwable error) {
if (view != null) {
view.hideLoading();
view.showError(error.getMessage());
}
}
});
}
@Override
public void onUserClicked(User user) {
if (view != null) {
view.navigateToDetail(user);
}
}
}2.3 View 实现(Activity)
java
public class UserListActivity extends AppCompatActivity
implements UserListContract.View {
private UserListPresenter presenter;
private ProgressBar progressBar;
private RecyclerView recyclerView;
private UserAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 初始化 UI
progressBar = findViewById(R.id.progress_bar);
recyclerView = findViewById(R.id.rv_users);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new UserAdapter(new ArrayList<>(), this::onUserClicked);
recyclerView.setAdapter(adapter);
// 创建 Presenter
presenter = new UserListPresenter(new UserRepository());
presenter.attachView(this);
presenter.loadUsers();
}
private void onUserClicked(User user) {
presenter.onUserClicked(user);
}
// ---- View 接口实现 ----
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.GONE);
}
@Override
public void showUsers(List<User> users) {
adapter.updateUsers(users);
}
@Override
public void showError(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void navigateToDetail(User user) {
Intent intent = new Intent(this, UserDetailActivity.class);
intent.putExtra("user_id", user.getId());
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.detachView(); // 防止泄漏
}
}3. MVVM 模式(2019+ 主流,推荐)
MVVM 使用数据驱动 UI,ViewModel 持有数据,View 观察数据变化:
View (Activity/Fragment) ←观察→ ViewModel ←→ Repository
↑ ↑
只负责 UI 和事件 持有 UI 数据,存活于配置变更3.1 ViewModel
java
public class UserListViewModel extends ViewModel {
private final UserRepository repository;
// MutableLiveData:ViewModel 内部可修改
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>();
private final MutableLiveData<List<User>> users = new MutableLiveData<>();
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
// 对外暴露 LiveData(只读)
public LiveData<Boolean> getIsLoading() { return isLoading; }
public LiveData<List<User>> getUsers() { return users; }
public LiveData<String> getErrorMessage() { return errorMessage; }
public UserListViewModel() {
this.repository = new UserRepository();
}
public void loadUsers() {
isLoading.setValue(true);
repository.getUsers(new DataCallback<List<User>>() {
@Override
public void onSuccess(List<User> result) {
isLoading.setValue(false);
users.setValue(result);
}
@Override
public void onError(Throwable error) {
isLoading.setValue(false);
errorMessage.setValue(error.getMessage());
}
});
}
}3.2 Activity 观察 ViewModel
java
public class UserListActivity extends AppCompatActivity {
private UserListViewModel viewModel;
private UserAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_list);
// 获取 ViewModel(配置变更时不会重建)
viewModel = new ViewModelProvider(this).get(UserListViewModel.class);
// 初始化 UI
RecyclerView rv = findViewById(R.id.rv_users);
ProgressBar progressBar = findViewById(R.id.progress_bar);
rv.setLayoutManager(new LinearLayoutManager(this));
adapter = new UserAdapter(new ArrayList<>());
rv.setAdapter(adapter);
// 观察数据变化
viewModel.getIsLoading().observe(this, loading -> {
progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
});
viewModel.getUsers().observe(this, userList -> {
adapter.updateUsers(userList);
});
viewModel.getErrorMessage().observe(this, message -> {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
});
// 首次加载
if (savedInstanceState == null) {
viewModel.loadUsers();
}
}
}3.3 LiveData 要点
java
// LiveData 的核心特性:
// 1. 生命周期感知:Activity 销毁后不再发送更新,避免崩溃
// 2. 粘性事件:新的观察者注册时会收到最后一次数据
// 3. 自动取消:不需要手动注销观察者
// setValue():必须在主线程调用
liveData.setValue(newValue);
// postValue():可以在后台线程调用
liveData.postValue(newValue);
// 数据转换(类似 JS 的 .map())
LiveData<String> userName = Transformations.map(userLiveData, user -> user.getName());
// 合并多个 LiveData(类似 Promise.all)
MediatorLiveData<Result> mediator = new MediatorLiveData<>();
mediator.addSource(usersLiveData, users -> combine(users, otherData));
mediator.addSource(otherLiveData, other -> combine(users, other));LiveData 常见误区
java
// ❌ 错误:在 ViewModel 中持有 Activity/Fragment 引用
public class UserViewModel extends ViewModel {
private Activity activity; // 内存泄漏!
}
// ✅ 正确:ViewModel 不引用 View,只通过 LiveData 暴露数据
// ❌ 错误:把点击事件也封装成 LiveData(会导致旋转屏幕后重复触发)
public class LoginViewModel extends ViewModel {
private MutableLiveData<Boolean> loginClick = new MutableLiveData<>();
}
// ✅ 正确:一次性事件用 SingleLiveEvent 或手动回调
public class SingleLiveEvent<T> extends MutableLiveData<T> {
// 实现:通过 AtomicBoolean 保证只消费一次
}
// ✅ 正确:后台线程更新数据用 postValue
new Thread(() -> {
List<User> users = repository.loadUsers();
usersLiveData.postValue(users); // 子线程安全
}).start();3.4 Repository 模式
Repository 是 MVVM 中连接 ViewModel 和数据来源(网络、数据库、缓存)的中介:
java
public class UserRepository {
private final UserApi api;
private final UserDao dao;
public UserRepository(UserApi api, UserDao dao) {
this.api = api;
this.dao = dao;
}
public LiveData<List<User>> getUsers() {
// 1. 先返回本地缓存
// 2. 后台请求网络
// 3. 网络成功后更新数据库
// 4. Room 通过 LiveData 自动通知 UI
refreshUsers();
return dao.getAllUsers();
}
private void refreshUsers() {
new Thread(() -> {
try {
List<User> users = api.fetchUsers().execute().body();
dao.insertAll(users);
} catch (IOException e) {
Log.e("UserRepository", "刷新用户失败", e);
}
}).start();
}
}前端对照:Repository 类似前端项目中的
services/api.ts+stores/cache的合体,负责统一的数据获取和缓存策略。
4. 架构识别指南
接手老项目时,如何判断使用了哪种架构:
| 特征 | 架构 | 文件命名 |
|---|---|---|
| Activity 中直接写网络请求和 UI 操作 | MVC(无架构) | 只有 Activity |
有 XxxContract、XxxPresenter 接口和类 | MVP | UserContract.java、UserPresenter.java |
有 XxxViewModel + LiveData | MVVM | UserViewModel.java |
| 有 Dagger/Hilt 依赖注入 | 通常是 MVP 或 MVVM | XxxModule.java、@Inject |
有 @HiltAndroidApp、@Inject | MVVM + DI | XxxModule.java |
5. Jetpack Navigation 组件
5.1 导航图
xml
<!-- res/navigation/nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.HomeFragment"
android:label="首页">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment"
android:label="详情">
<argument
android:name="userId"
app:argType="integer" />
</fragment>
<fragment
android:id="@+id/settingsFragment"
android:name="com.example.app.SettingsFragment"
android:label="设置" />
</navigation>5.2 在 Activity 中承载导航
xml
<!-- res/layout/activity_main.xml -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />5.3 导航操作
java
// 在 Fragment 中导航到其他页面
Navigation.findNavController(view)
.navigate(R.id.action_home_to_detail);
// 传递参数
Bundle args = new Bundle();
args.putInt("userId", 123);
Navigation.findNavController(view)
.navigate(R.id.action_home_to_detail, args);
// 在目标 Fragment 中接收参数
int userId = getArguments().getInt("userId");
// 返回上一页
Navigation.findNavController(view).navigateUp();
// 配合底部导航栏
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(bottomNav, navController);5.4 Safe Args 类型安全导航
groovy
// 项目级 build.gradle
plugins {
id 'androidx.navigation.safeargs' version '2.7.6' apply false
}
// 模块级 build.gradle
plugins {
id 'androidx.navigation.safeargs'
}java
// 生成的类提供了类型安全的导航
HomeFragmentDirections.ActionHomeToDetail action =
HomeFragmentDirections.actionHomeToDetail()
.setUserId(123);
Navigation.findNavController(view).navigate(action);6. 老项目中常见的架构库
| 库 | 说明 | 识别特征 |
|---|---|---|
| Moxy | MVP 框架 | @InjectPresenter、@BindViewState |
| Mosby | MVP 框架 | MvpActivity、MvpPresenter |
| Conductor | Controller-based | Controller 替代 Fragment |
| Dagger 2 | 依赖注入 | @Module、@Component、@Inject |
| Hilt | 依赖注入(简化 Dagger) | @HiltAndroidApp、@AndroidEntryPoint |
7. ViewModel 进阶用法
7.1 ViewModel + SavedStateHandle
java
public class UserDetailViewModel extends ViewModel {
private final SavedStateHandle handle;
public UserDetailViewModel(SavedStateHandle handle) {
this.handle = handle;
// 从导航参数中获取
int userId = handle.get("userId");
loadUser(userId);
}
// 保存状态(进程被杀后恢复)
public void saveDraft(String text) {
handle.set("draft", text);
}
public String getDraft() {
return handle.get("draft");
}
}7.2 ViewModel 工厂
java
// 当 ViewModel 构造方法需要参数时
public class ViewModelFactory implements ViewModelProvider.Factory {
private final UserRepository repository;
public ViewModelFactory(UserRepository repository) {
this.repository = repository;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass.isAssignableFrom(UserListViewModel.class)) {
return (T) new UserListViewModel(repository);
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
// 使用
UserListViewModel viewModel = new ViewModelProvider(this,
new ViewModelFactory(repository)).get(UserListViewModel.class);7.3 LifecycleObserver:生命周期感知组件
java
// 让普通类也能感知 Activity/Fragment 生命周期
public class MyLocationListener implements DefaultLifecycleObserver {
private final LocationManager manager;
public MyLocationListener(LocationManager manager) {
this.manager = manager;
}
@Override
public void onResume(LifecycleOwner owner) {
manager.start();
}
@Override
public void onPause(LifecycleOwner owner) {
manager.stop();
}
}
// 在 Activity/Fragment 中注册
getLifecycle().addObserver(new MyLocationListener(locationManager));前端对照:类似 React 的
useEffect(() => { start(); return () => stop(); }, []),自动在组件挂载/卸载时执行和清理。
前端开发者备忘
| 前端架构概念 | Android 对应 | 备注 |
|---|---|---|
| 组件内状态 | Activity 成员变量 | 最简单的数据保存 |
| Redux Store | ViewModel + LiveData | 数据驱动 UI |
useSelector() | liveData.observe() | 观察数据变化 |
dispatch(action) | viewModel.method() | 触发状态更新 |
| React Router | Jetpack Navigation | 页面导航 |
useNavigate() | NavController.navigate() | 编程式导航 |
useParams() | getArguments() / Safe Args | 路由参数 |
<Outlet /> | FragmentContainerView | 嵌套路由容器 |
| Context Provider | ViewModelProvider | 共享数据 |
zustand / jotai | ViewModel + LiveData | 轻量状态管理 |
下一步
理解了架构模式后,接下来学习 模块 09:调试、测试与问题排查,掌握实用的调试和排错技能。