Skip to content

模块 08:架构模式与导航

目标:理解 Android 项目中常见的架构模式(MVC、MVP、MVVM),掌握 ViewModel、LiveData 和 Jetpack Navigation 组件。

前端对照:架构演进

前端架构Android 架构核心思想
jQuery 面条代码MVC(原始)没有分离,全部写在 Activity
Redux / VuexMVP明确分离 View 和 Presenter
React + Hooks / ZustandMVVM数据驱动 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
XxxContractXxxPresenter 接口和类MVPUserContract.javaUserPresenter.java
XxxViewModel + LiveDataMVVMUserViewModel.java
有 Dagger/Hilt 依赖注入通常是 MVP 或 MVVMXxxModule.java@Inject
@HiltAndroidApp@InjectMVVM + DIXxxModule.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. 老项目中常见的架构库

说明识别特征
MoxyMVP 框架@InjectPresenter@BindViewState
MosbyMVP 框架MvpActivityMvpPresenter
ConductorController-basedController 替代 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 StoreViewModel + LiveData数据驱动 UI
useSelector()liveData.observe()观察数据变化
dispatch(action)viewModel.method()触发状态更新
React RouterJetpack Navigation页面导航
useNavigate()NavController.navigate()编程式导航
useParams()getArguments() / Safe Args路由参数
<Outlet />FragmentContainerView嵌套路由容器
Context ProviderViewModelProvider共享数据
zustand / jotaiViewModel + LiveData轻量状态管理

下一步

理解了架构模式后,接下来学习 模块 09:调试、测试与问题排查,掌握实用的调试和排错技能。

面向前端开发者的 Java Android 开发教程