Android Room 框架表现层源码深度分析(三)

打印 上一主题 下一主题

主题 1008|帖子 1008|积分 3024

一、引言

在 Android 应用开发中,表现层(Presentation Layer)扮演着至关重要的脚色,它负责将数据以直观、友好的方式展示给用户,并处理用户的交互操作。Android Room 框架作为一个强大的数据库抽象层,为数据的长期化和访问提供了便利。而表现层与 Room 框架的结合,可以或许实现数据的实时更新和高效展示。
本文将深入分析 Android Room 框架在表现层的应用和实现原理,从源码级别详细分析表现层中与 Room 相关的各个组件和流程。通过对源码的解读,我们可以更好地明白如何在表现层中合理运用 Room 框架,以及如何构建高效、美观的用户界面。
二、表现层概述

2.1 表现层的职责

表现层的重要职责是将数据以可视化的方式呈现给用户,并处理用户的交互事件。具体来说,表现层的职责包括:


  • 数据展示:将从数据层获取的数据以符合的 UI 组件(如列表、卡片、图表等)展示给用户。
  • 用户交互处理:处理用户的点击、滑动、输入等交互事件,并根据用户的操作更新 UI 或触发相应的业务逻辑。
  • UI 状态管理:管理 UI 的状态,如加载状态、错误状态、空数据状态等,以提供良好的用户体验。
2.2 表现层与其他层的关系

在范例的 Android 架构中,表现层位于领域层之上,它从领域层获取数据,并将用户的交互反馈通报给领域层。同时,表现层还负责与 Android 体系的 UI 框架进行交互,如使用 Activity、Fragment、View 等组件来构建界面。
2.3 Room 框架在表现层的作用

Room 框架为表现层提供了数据的来源。表现层可以通过 Room 的 DAO(Data Access Object)接口获取数据库中的数据,并将其展示在 UI 上。同时,Room 的 LiveData 支持使得表现层可以或许实时响应数据的变化,自动更新 UI。
三、表现层中的数据展示

3.1 使用 RecyclerView 展示数据

RecyclerView 是 Android 中常用的用于展示列表数据的组件。结合 Room 框架,我们可以实现数据的实时更新和高效展示。
java
  1. // 定义 RecyclerView 的 Adapter
  2. import android.view.LayoutInflater;
  3. import android.view.View;
  4. import android.view.ViewGroup;
  5. import android.widget.TextView;
  6. import androidx.annotation.NonNull;
  7. import androidx.recyclerview.widget.RecyclerView;
  8. import java.util.List;
  9. // 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
  10. public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
  11.     private List<User> userList;
  12.     // 构造函数,接收用户数据列表
  13.     public UserAdapter(List<User> userList) {
  14.         this.userList = userList;
  15.     }
  16.     // 创建 ViewHolder
  17.     @NonNull
  18.     @Override
  19.     public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  20.         // 加载 Item 的布局文件
  21.         View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
  22.         return new UserViewHolder(view);
  23.     }
  24.     // 绑定数据到 ViewHolder
  25.     @Override
  26.     public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
  27.         // 获取当前位置的用户数据
  28.         User user = userList.get(position);
  29.         // 将用户数据显示在 TextView 上
  30.         holder.textViewName.setText(user.getName());
  31.         holder.textViewAge.setText(String.valueOf(user.getAge()));
  32.     }
  33.     // 获取数据项的数量
  34.     @Override
  35.     public int getItemCount() {
  36.         return userList != null ? userList.size() : 0;
  37.     }
  38.     // 更新数据列表并刷新 Adapter
  39.     public void setUserList(List<User> userList) {
  40.         this.userList = userList;
  41.         notifyDataSetChanged();
  42.     }
  43.     // 定义 ViewHolder 类
  44.     static class UserViewHolder extends RecyclerView.ViewHolder {
  45.         TextView textViewName;
  46.         TextView textViewAge;
  47.         // 构造函数,初始化 ViewHolder 中的视图组件
  48.         UserViewHolder(@NonNull View itemView) {
  49.             super(itemView);
  50.             textViewName = itemView.findViewById(R.id.textViewName);
  51.             textViewAge = itemView.findViewById(R.id.textViewAge);
  52.         }
  53.     }
  54. }
复制代码
java
  1. // 在 Activity 中使用 RecyclerView 展示数据
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.lifecycle.Observer;
  4. import androidx.lifecycle.ViewModelProvider;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import java.util.List;
  9. // 该 Activity 用于展示用户列表
  10. public class UserListActivity extends AppCompatActivity {
  11.     private RecyclerView recyclerView;
  12.     private UserAdapter userAdapter;
  13.     private UserViewModel userViewModel;
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_user_list);
  18.         // 初始化 RecyclerView
  19.         recyclerView = findViewById(R.id.recyclerView);
  20.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  21.         userAdapter = new UserAdapter(null);
  22.         recyclerView.setAdapter(userAdapter);
  23.         // 获取 ViewModel 实例
  24.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  25.         // 观察 LiveData 数据的变化
  26.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  27.             @Override
  28.             public void onChanged(List<User> users) {
  29.                 // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
  30.                 userAdapter.setUserList(users);
  31.             }
  32.         });
  33.     }
  34. }
复制代码
3.2 源码分析

从源码的角度来看,UserAdapter 类继续自 RecyclerView.Adapter,负责将 User 数据绑定到 RecyclerView 的每个 Item 上。UserListActivity 类中,我们使用 ViewModel 获取 LiveData 类型的用户数据,并通过 observe 方法监听数据的变化。当数据发生变化时,Observer 的 onChanged 方法会被调用,我们在该方法中更新 Adapter 中的数据并革新界面。
3.3 使用 LiveData 实现数据的实时更新

LiveData 是 Android 架构组件中的一个可观察的数据持有者类,它具有生命周期感知能力,可以或许在 Activity 或 Fragment 的生命周期内自动管理数据的更新。
java
  1. // 定义 UserViewModel
  2. import androidx.lifecycle.LiveData;
  3. import androidx.lifecycle.ViewModel;
  4. import java.util.List;
  5. // 该 ViewModel 用于管理用户数据
  6. public class UserViewModel extends ViewModel {
  7.     private UserRepository userRepository;
  8.     private LiveData<List<User>> allUsers;
  9.     // 构造函数,初始化 UserRepository 并获取所有用户数据
  10.     public UserViewModel() {
  11.         userRepository = new UserRepository();
  12.         allUsers = userRepository.getAllUsers();
  13.     }
  14.     // 获取所有用户数据的 LiveData 对象
  15.     public LiveData<List<User>> getAllUsers() {
  16.         return allUsers;
  17.     }
  18. }
复制代码
java
  1. // 定义 UserRepository
  2. import androidx.lifecycle.LiveData;
  3. import java.util.List;
  4. // 该 Repository 用于与数据层交互,获取用户数据
  5. public class UserRepository {
  6.     private UserDao userDao;
  7.     // 构造函数,初始化 UserDao
  8.     public UserRepository() {
  9.         AppDatabase appDatabase = AppDatabase.getDatabase();
  10.         userDao = appDatabase.userDao();
  11.     }
  12.     // 获取所有用户数据的 LiveData 对象
  13.     public LiveData<List<User>> getAllUsers() {
  14.         return userDao.getAllUsers();
  15.     }
  16. }
复制代码
java
  1. // 定义 UserDao
  2. import androidx.lifecycle.LiveData;
  3. import androidx.room.Dao;
  4. import androidx.room.Query;
  5. import java.util.List;
  6. // 该 DAO 接口定义了与用户数据相关的数据库操作方法
  7. @Dao
  8. public interface UserDao {
  9.     // 查询所有用户数据,并返回 LiveData 类型的结果
  10.     @Query("SELECT * FROM user")
  11.     LiveData<List<User>> getAllUsers();
  12. }
复制代码
3.4 源码分析

在 UserViewModel 中,我们通过 UserRepository 获取 LiveData 类型的用户数据。UserRepository 负责与数据层交互,调用 UserDao 的 getAllUsers 方法。UserDao 是 Room 框架的 DAO 接口,使用 @Query 注解界说了查询所有用户数据的 SQL 语句,并返回 LiveData 类型的结果。当数据库中的数据发生变化时,LiveData 会自动通知所有的观察者,从而实现数据的实时更新。
四、表现层中的用户交互处理

4.1 处理 RecyclerView 中的点击事件

在 RecyclerView 中处理点击事件可以让用户与列表项进行交互。
java
  1. // 在 UserAdapter 中添加点击事件处理
  2. import android.view.LayoutInflater;
  3. import android.view.View;
  4. import android.view.ViewGroup;
  5. import android.widget.TextView;
  6. import androidx.annotation.NonNull;
  7. import androidx.recyclerview.widget.RecyclerView;
  8. import java.util.List;
  9. // 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上,并处理点击事件
  10. public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
  11.     private List<User> userList;
  12.     private OnItemClickListener onItemClickListener;
  13.     // 构造函数,接收用户数据列表和点击事件监听器
  14.     public UserAdapter(List<User> userList, OnItemClickListener onItemClickListener) {
  15.         this.userList = userList;
  16.         this.onItemClickListener = onItemClickListener;
  17.     }
  18.     // 创建 ViewHolder
  19.     @NonNull
  20.     @Override
  21.     public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  22.         // 加载 Item 的布局文件
  23.         View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
  24.         return new UserViewHolder(view);
  25.     }
  26.     // 绑定数据到 ViewHolder
  27.     @Override
  28.     public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
  29.         // 获取当前位置的用户数据
  30.         User user = userList.get(position);
  31.         // 将用户数据显示在 TextView 上
  32.         holder.textViewName.setText(user.getName());
  33.         holder.textViewAge.setText(String.valueOf(user.getAge()));
  34.         // 设置点击事件监听器
  35.         holder.itemView.setOnClickListener(new View.OnClickListener() {
  36.             @Override
  37.             public void onClick(View v) {
  38.                 if (onItemClickListener != null) {
  39.                     // 当点击事件发生时,调用监听器的回调方法
  40.                     onItemClickListener.onItemClick(user);
  41.                 }
  42.             }
  43.         });
  44.     }
  45.     // 获取数据项的数量
  46.     @Override
  47.     public int getItemCount() {
  48.         return userList != null ? userList.size() : 0;
  49.     }
  50.     // 更新数据列表并刷新 Adapter
  51.     public void setUserList(List<User> userList) {
  52.         this.userList = userList;
  53.         notifyDataSetChanged();
  54.     }
  55.     // 定义点击事件监听器接口
  56.     public interface OnItemClickListener {
  57.         // 当 Item 被点击时调用该方法
  58.         void onItemClick(User user);
  59.     }
  60.     // 定义 ViewHolder 类
  61.     static class UserViewHolder extends RecyclerView.ViewHolder {
  62.         TextView textViewName;
  63.         TextView textViewAge;
  64.         // 构造函数,初始化 ViewHolder 中的视图组件
  65.         UserViewHolder(@NonNull View itemView) {
  66.             super(itemView);
  67.             textViewName = itemView.findViewById(R.id.textViewName);
  68.             textViewAge = itemView.findViewById(R.id.textViewAge);
  69.         }
  70.     }
  71. }
复制代码
java
  1. // 在 UserListActivity 中处理点击事件
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.lifecycle.Observer;
  4. import androidx.lifecycle.ViewModelProvider;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import android.widget.Toast;
  9. import java.util.List;
  10. // 该 Activity 用于展示用户列表并处理点击事件
  11. public class UserListActivity extends AppCompatActivity {
  12.     private RecyclerView recyclerView;
  13.     private UserAdapter userAdapter;
  14.     private UserViewModel userViewModel;
  15.     @Override
  16.     protected void onCreate(Bundle savedInstanceState) {
  17.         super.onCreate(savedInstanceState);
  18.         setContentView(R.layout.activity_user_list);
  19.         // 初始化 RecyclerView
  20.         recyclerView = findViewById(R.id.recyclerView);
  21.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  22.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  23.             @Override
  24.             public void onItemClick(User user) {
  25.                 // 当 Item 被点击时,显示一个 Toast 消息
  26.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  27.             }
  28.         });
  29.         recyclerView.setAdapter(userAdapter);
  30.         // 获取 ViewModel 实例
  31.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  32.         // 观察 LiveData 数据的变化
  33.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  34.             @Override
  35.             public void onChanged(List<User> users) {
  36.                 // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
  37.                 userAdapter.setUserList(users);
  38.             }
  39.         });
  40.     }
  41. }
复制代码
4.2 源码分析

在 UserAdapter 中,我们界说了一个 OnItemClickListener 接口,并在 onBindViewHolder 方法中为每个 Item 设置点击事件监听器。当 Item 被点击时,会调用监听器的 onItemClick 方法。在 UserListActivity 中,我们实现了 OnItemClickListener 接口,并在 onItemClick 方法中表现一个 Toast 消息,以响应用户的点击操作。
4.3 处理表单输入和提交

在表现层中,我们经常须要处理用户的表单输入,并将输入的数据提交到数据层。
java
  1. // 定义 AddUserActivity
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. import android.widget.EditText;
  7. import androidx.lifecycle.ViewModelProvider;
  8. // 该 Activity 用于添加新用户
  9. public class AddUserActivity extends AppCompatActivity {
  10.     private EditText editTextName;
  11.     private EditText editTextAge;
  12.     private Button buttonAdd;
  13.     private UserViewModel userViewModel;
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_add_user);
  18.         // 初始化视图组件
  19.         editTextName = findViewById(R.id.editTextName);
  20.         editTextAge = findViewById(R.id.editTextAge);
  21.         buttonAdd = findViewById(R.id.buttonAdd);
  22.         // 获取 ViewModel 实例
  23.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  24.         // 设置按钮的点击事件监听器
  25.         buttonAdd.setOnClickListener(new View.OnClickListener() {
  26.             @Override
  27.             public void onClick(View v) {
  28.                 // 获取用户输入的姓名和年龄
  29.                 String name = editTextName.getText().toString().trim();
  30.                 String ageStr = editTextAge.getText().toString().trim();
  31.                 if (!name.isEmpty() && !ageStr.isEmpty()) {
  32.                     int age = Integer.parseInt(ageStr);
  33.                     // 创建新的用户对象
  34.                     User user = new User(name, age);
  35.                     // 调用 ViewModel 的方法添加用户
  36.                     userViewModel.insertUser(user);
  37.                     // 关闭当前 Activity
  38.                     finish();
  39.                 }
  40.             }
  41.         });
  42.     }
  43. }
复制代码
java
  1. // 在 UserViewModel 中添加插入用户的方法
  2. import androidx.lifecycle.LiveData;
  3. import androidx.lifecycle.ViewModel;
  4. import java.util.List;
  5. // 该 ViewModel 用于管理用户数据
  6. public class UserViewModel extends ViewModel {
  7.     private UserRepository userRepository;
  8.     private LiveData<List<User>> allUsers;
  9.     // 构造函数,初始化 UserRepository 并获取所有用户数据
  10.     public UserViewModel() {
  11.         userRepository = new UserRepository();
  12.         allUsers = userRepository.getAllUsers();
  13.     }
  14.     // 获取所有用户数据的 LiveData 对象
  15.     public LiveData<List<User>> getAllUsers() {
  16.         return allUsers;
  17.     }
  18.     // 插入新用户的方法
  19.     public void insertUser(User user) {
  20.         userRepository.insertUser(user);
  21.     }
  22. }
复制代码
java
  1. // 在 UserRepository 中添加插入用户的方法
  2. import androidx.lifecycle.LiveData;
  3. import java.util.List;
  4. // 该 Repository 用于与数据层交互,获取用户数据
  5. public class UserRepository {
  6.     private UserDao userDao;
  7.     // 构造函数,初始化 UserDao
  8.     public UserRepository() {
  9.         AppDatabase appDatabase = AppDatabase.getDatabase();
  10.         userDao = appDatabase.userDao();
  11.     }
  12.     // 获取所有用户数据的 LiveData 对象
  13.     public LiveData<List<User>> getAllUsers() {
  14.         return userDao.getAllUsers();
  15.     }
  16.     // 插入新用户的方法
  17.     public void insertUser(User user) {
  18.         userDao.insertUser(user);
  19.     }
  20. }
复制代码
java
  1. // 在 UserDao 中添加插入用户的方法
  2. import androidx.room.Dao;
  3. import androidx.room.Insert;
  4. // 该 DAO 接口定义了与用户数据相关的数据库操作方法
  5. @Dao
  6. public interface UserDao {
  7.     // 查询所有用户数据,并返回 LiveData 类型的结果
  8.     @Query("SELECT * FROM user")
  9.     LiveData<List<User>> getAllUsers();
  10.     // 插入新用户的方法
  11.     @Insert
  12.     void insertUser(User user);
  13. }
复制代码
4.4 源码分析

在 AddUserActivity 中,我们获取用户输入的姓名和年龄,创建新的 User 对象,并调用 UserViewModel 的 insertUser 方法将用户数据插入到数据库中。UserViewModel 调用 UserRepository 的 insertUser 方法,UserRepository 再调用 UserDao 的 insertUser 方法。UserDao 使用 @Insert 注解界说了插入用户数据的方法。
五、表现层中的 UI 状态管理

5.1 加载状态管理

在获取数据时,我们须要表现加载状态,以提高用户体验。
java
  1. // 在 UserListActivity 中添加加载状态管理
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.lifecycle.Observer;
  4. import androidx.lifecycle.ViewModelProvider;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import android.view.View;
  9. import android.widget.ProgressBar;
  10. import android.widget.Toast;
  11. import java.util.List;
  12. // 该 Activity 用于展示用户列表,添加了加载状态管理
  13. public class UserListActivity extends AppCompatActivity {
  14.     private RecyclerView recyclerView;
  15.     private UserAdapter userAdapter;
  16.     private UserViewModel userViewModel;
  17.     private ProgressBar progressBar;
  18.     @Override
  19.     protected void onCreate(Bundle savedInstanceState) {
  20.         super.onCreate(savedInstanceState);
  21.         setContentView(R.layout.activity_user_list);
  22.         // 初始化 RecyclerView
  23.         recyclerView = findViewById(R.id.recyclerView);
  24.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  25.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  26.             @Override
  27.             public void onItemClick(User user) {
  28.                 // 当 Item 被点击时,显示一个 Toast 消息
  29.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  30.             }
  31.         });
  32.         recyclerView.setAdapter(userAdapter);
  33.         // 初始化 ProgressBar
  34.         progressBar = findViewById(R.id.progressBar);
  35.         // 获取 ViewModel 实例
  36.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  37.         // 显示加载状态
  38.         progressBar.setVisibility(View.VISIBLE);
  39.         // 观察 LiveData 数据的变化
  40.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  41.             @Override
  42.             public void onChanged(List<User> users) {
  43.                 // 当数据加载完成后,隐藏加载状态
  44.                 progressBar.setVisibility(View.GONE);
  45.                 // 更新 Adapter 中的数据并刷新界面
  46.                 userAdapter.setUserList(users);
  47.             }
  48.         });
  49.     }
  50. }
复制代码
5.2 源码分析

在 UserListActivity 中,我们添加了一个 ProgressBar 用于表现加载状态。在获取数据之前,将 ProgressBar 的可见性设置为 VISIBLE,当数据加载完成后,将其可见性设置为 GONE。这样可以让用户清楚地知道数据正在加载中。
5.3 错误状态管理

当数据获取失败时,我们须要表现错误状态。
java
  1. // 在 UserViewModel 中添加错误状态管理
  2. import androidx.lifecycle.LiveData;
  3. import androidx.lifecycle.MutableLiveData;
  4. import androidx.lifecycle.ViewModel;
  5. import java.util.List;
  6. // 该 ViewModel 用于管理用户数据,添加了错误状态管理
  7. public class UserViewModel extends ViewModel {
  8.     private UserRepository userRepository;
  9.     private LiveData<List<User>> allUsers;
  10.     private MutableLiveData<String> errorMessage;
  11.     // 构造函数,初始化 UserRepository 并获取所有用户数据
  12.     public UserViewModel() {
  13.         userRepository = new UserRepository();
  14.         allUsers = userRepository.getAllUsers();
  15.         errorMessage = new MutableLiveData<>();
  16.         // 模拟数据获取失败的情况
  17.         userRepository.getErrorLiveData().observeForever(new Observer<String>() {
  18.             @Override
  19.             public void onChanged(String error) {
  20.                 errorMessage.setValue(error);
  21.             }
  22.         });
  23.     }
  24.     // 获取所有用户数据的 LiveData 对象
  25.     public LiveData<List<User>> getAllUsers() {
  26.         return allUsers;
  27.     }
  28.     // 获取错误消息的 LiveData 对象
  29.     public LiveData<String> getErrorMessage() {
  30.         return errorMessage;
  31.     }
  32.     // 插入新用户的方法
  33.     public void insertUser(User user) {
  34.         userRepository.insertUser(user);
  35.     }
  36. }
复制代码
java
  1. // 在 UserRepository 中添加错误状态管理
  2. import androidx.lifecycle.LiveData;
  3. import androidx.lifecycle.MutableLiveData;
  4. import java.util.List;
  5. // 该 Repository 用于与数据层交互,获取用户数据,添加了错误状态管理
  6. public class UserRepository {
  7.     private UserDao userDao;
  8.     private MutableLiveData<String> errorLiveData;
  9.     // 构造函数,初始化 UserDao
  10.     public UserRepository() {
  11.         AppDatabase appDatabase = AppDatabase.getDatabase();
  12.         userDao = appDatabase.userDao();
  13.         errorLiveData = new MutableLiveData<>();
  14.         // 模拟数据获取失败的情况
  15.         try {
  16.             // 这里可以添加实际的错误处理逻辑
  17.             if (Math.random() < 0.1) {
  18.                 throw new Exception("Data fetch failed");
  19.             }
  20.         } catch (Exception e) {
  21.             errorLiveData.setValue(e.getMessage());
  22.         }
  23.     }
  24.     // 获取所有用户数据的 LiveData 对象
  25.     public LiveData<List<User>> getAllUsers() {
  26.         return userDao.getAllUsers();
  27.     }
  28.     // 获取错误消息的 LiveData 对象
  29.     public LiveData<String> getErrorLiveData() {
  30.         return errorLiveData;
  31.     }
  32.     // 插入新用户的方法
  33.     public void insertUser(User user) {
  34.         userDao.insertUser(user);
  35.     }
  36. }
复制代码
java
  1. // 在 UserListActivity 中处理错误状态
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.lifecycle.Observer;
  4. import androidx.lifecycle.ViewModelProvider;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import android.view.View;
  9. import android.widget.ProgressBar;
  10. import android.widget.TextView;
  11. import android.widget.Toast;
  12. import java.util.List;
  13. // 该 Activity 用于展示用户列表,处理错误状态
  14. public class UserListActivity extends AppCompatActivity {
  15.     private RecyclerView recyclerView;
  16.     private UserAdapter userAdapter;
  17.     private UserViewModel userViewModel;
  18.     private ProgressBar progressBar;
  19.     private TextView textViewError;
  20.     @Override
  21.     protected void onCreate(Bundle savedInstanceState) {
  22.         super.onCreate(savedInstanceState);
  23.         setContentView(R.layout.activity_user_list);
  24.         // 初始化 RecyclerView
  25.         recyclerView = findViewById(R.id.recyclerView);
  26.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  27.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  28.             @Override
  29.             public void onItemClick(User user) {
  30.                 // 当 Item 被点击时,显示一个 Toast 消息
  31.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  32.             }
  33.         });
  34.         recyclerView.setAdapter(userAdapter);
  35.         // 初始化 ProgressBar
  36.         progressBar = findViewById(R.id.progressBar);
  37.         // 初始化错误提示 TextView
  38.         textViewError = findViewById(R.id.textViewError);
  39.         // 获取 ViewModel 实例
  40.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  41.         // 显示加载状态
  42.         progressBar.setVisibility(View.VISIBLE);
  43.         // 观察 LiveData 数据的变化
  44.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  45.             @Override
  46.             public void onChanged(List<User> users) {
  47.                 // 当数据加载完成后,隐藏加载状态
  48.                 progressBar.setVisibility(View.GONE);
  49.                 if (users != null) {
  50.                     // 隐藏错误提示
  51.                     textViewError.setVisibility(View.GONE);
  52.                     // 更新 Adapter 中的数据并刷新界面
  53.                     userAdapter.setUserList(users);
  54.                 }
  55.             }
  56.         });
  57.         // 观察错误消息的变化
  58.         userViewModel.getErrorMessage().observe(this, new Observer<String>() {
  59.             @Override
  60.             public void onChanged(String error) {
  61.                 // 隐藏加载状态
  62.                 progressBar.setVisibility(View.GONE);
  63.                 if (error != null) {
  64.                     // 显示错误提示
  65.                     textViewError.setVisibility(View.VISIBLE);
  66.                     textViewError.setText(error);
  67.                 }
  68.             }
  69.         });
  70.     }
  71. }
复制代码
5.4 源码分析

在 UserViewModel 和 UserRepository 中,我们添加了 MutableLiveData 类型的 errorMessage 用于存储错误消息。在 UserListActivity 中,我们观察 errorMessage 的变化,当有错误消息时,隐藏加载状态并表现错误提示。
六、表现层中的动画和过渡结果

6.1 RecyclerView 中的动画结果

在 RecyclerView 中添加动画结果可以提拔用户体验,让数据的更新更加生动。Android 为 RecyclerView 提供了默认的动画结果,同时也允许开发者自界说动画。
6.1.1 使用默认动画

RecyclerView 默认使用 DefaultItemAnimator 来实现动画结果,当数据发生变化时(如插入、删除、更新),会自动播放相应的动画。
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import androidx.lifecycle.Observer;
  3. import androidx.lifecycle.ViewModelProvider;
  4. import androidx.recyclerview.widget.DefaultItemAnimator;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import android.widget.Toast;
  9. import java.util.List;
  10. // 该 Activity 用于展示用户列表,并使用 RecyclerView 默认动画
  11. public class UserListActivity extends AppCompatActivity {
  12.     private RecyclerView recyclerView;
  13.     private UserAdapter userAdapter;
  14.     private UserViewModel userViewModel;
  15.     @Override
  16.     protected void onCreate(Bundle savedInstanceState) {
  17.         super.onCreate(savedInstanceState);
  18.         setContentView(R.layout.activity_user_list);
  19.         // 初始化 RecyclerView
  20.         recyclerView = findViewById(R.id.recyclerView);
  21.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  22.         // 设置默认的 Item 动画
  23.         recyclerView.setItemAnimator(new DefaultItemAnimator());
  24.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  25.             @Override
  26.             public void onItemClick(User user) {
  27.                 // 当 Item 被点击时,显示一个 Toast 消息
  28.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  29.             }
  30.         });
  31.         recyclerView.setAdapter(userAdapter);
  32.         // 获取 ViewModel 实例
  33.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  34.         // 观察 LiveData 数据的变化
  35.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  36.             @Override
  37.             public void onChanged(List<User> users) {
  38.                 // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
  39.                 userAdapter.setUserList(users);
  40.             }
  41.         });
  42.     }
  43. }
复制代码
6.1.2 源码分析

在上述代码中,通过 recyclerView.setItemAnimator(new DefaultItemAnimator()) 设置了 RecyclerView 的默认动画。DefaultItemAnimator 是 RecyclerView.ItemAnimator 的一个实现类,它内部处理了 RecyclerView 中 Item 的插入、删除、移动和更新动画。当调用 userAdapter.setUserList(users) 并触发 notifyDataSetChanged() 时,DefaultItemAnimator 会根据数据的变化情况播放相应的动画。
6.1.3 自界说动画

如果默认动画不能满足需求,开发者可以自界说动画。以下是一个自界说 ItemAnimator 的示例:
java
  1. import android.animation.Animator;
  2. import android.animation.ObjectAnimator;
  3. import android.view.View;
  4. import android.view.animation.Animation;
  5. import android.view.animation.AnimationUtils;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. // 自定义的 RecyclerView Item 动画类
  10. public class CustomItemAnimator extends RecyclerView.ItemAnimator {
  11.     private List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
  12.     private List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
  13.     @Override
  14.     public boolean animateAdd(RecyclerView.ViewHolder holder) {
  15.         pendingAdditions.add(holder);
  16.         return true;
  17.     }
  18.     @Override
  19.     public boolean animateRemove(RecyclerView.ViewHolder holder) {
  20.         pendingRemovals.add(holder);
  21.         return true;
  22.     }
  23.     @Override
  24.     public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
  25.         // 这里可以实现移动动画逻辑,暂不实现
  26.         dispatchMoveFinished(holder);
  27.         return false;
  28.     }
  29.     @Override
  30.     public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
  31.         // 这里可以实现变更动画逻辑,暂不实现
  32.         dispatchChangeFinished(oldHolder, true);
  33.         dispatchChangeFinished(newHolder, false);
  34.         return false;
  35.     }
  36.     @Override
  37.     public void runPendingAnimations() {
  38.         if (!pendingAdditions.isEmpty()) {
  39.             for (RecyclerView.ViewHolder holder : pendingAdditions) {
  40.                 // 为新增的 Item 播放动画
  41.                 animateAddImpl(holder);
  42.             }
  43.             pendingAdditions.clear();
  44.         }
  45.         if (!pendingRemovals.isEmpty()) {
  46.             for (RecyclerView.ViewHolder holder : pendingRemovals) {
  47.                 // 为删除的 Item 播放动画
  48.                 animateRemoveImpl(holder);
  49.             }
  50.             pendingRemovals.clear();
  51.         }
  52.     }
  53.     private void animateAddImpl(final RecyclerView.ViewHolder holder) {
  54.         View view = holder.itemView;
  55.         // 使用属性动画实现淡入效果
  56.         ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
  57.         animator.setDuration(300);
  58.         animator.addListener(new Animator.AnimatorListener() {
  59.             @Override
  60.             public void onAnimationStart(Animator animation) {
  61.                 dispatchAddStarting(holder);
  62.             }
  63.             @Override
  64.             public void onAnimationEnd(Animator animation) {
  65.                 dispatchAddFinished(holder);
  66.             }
  67.             @Override
  68.             public void onAnimationCancel(Animator animation) {
  69.                 dispatchAddFinished(holder);
  70.             }
  71.             @Override
  72.             public void onAnimationRepeat(Animator animation) {
  73.                 // 不处理重复动画
  74.             }
  75.         });
  76.         animator.start();
  77.     }
  78.     private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
  79.         View view = holder.itemView;
  80.         // 使用属性动画实现淡出效果
  81.         ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
  82.         animator.setDuration(300);
  83.         animator.addListener(new Animator.AnimatorListener() {
  84.             @Override
  85.             public void onAnimationStart(Animator animation) {
  86.                 dispatchRemoveStarting(holder);
  87.             }
  88.             @Override
  89.             public void onAnimationEnd(Animator animation) {
  90.                 dispatchRemoveFinished(holder);
  91.             }
  92.             @Override
  93.             public void onAnimationCancel(Animator animation) {
  94.                 dispatchRemoveFinished(holder);
  95.             }
  96.             @Override
  97.             public void onAnimationRepeat(Animator animation) {
  98.                 // 不处理重复动画
  99.             }
  100.         });
  101.         animator.start();
  102.     }
  103.     @Override
  104.     public void endAnimation(RecyclerView.ViewHolder item) {
  105.         // 结束动画的逻辑
  106.     }
  107.     @Override
  108.     public void endAnimations() {
  109.         // 结束所有动画的逻辑
  110.     }
  111.     @Override
  112.     public boolean isRunning() {
  113.         return !pendingAdditions.isEmpty() || !pendingRemovals.isEmpty();
  114.     }
  115. }
复制代码
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import androidx.lifecycle.Observer;
  3. import androidx.lifecycle.ViewModelProvider;
  4. import androidx.recyclerview.widget.LinearLayoutManager;
  5. import androidx.recyclerview.widget.RecyclerView;
  6. import android.os.Bundle;
  7. import android.widget.Toast;
  8. import java.util.List;
  9. // 该 Activity 用于展示用户列表,并使用自定义的 RecyclerView 动画
  10. public class UserListActivity extends AppCompatActivity {
  11.     private RecyclerView recyclerView;
  12.     private UserAdapter userAdapter;
  13.     private UserViewModel userViewModel;
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_user_list);
  18.         // 初始化 RecyclerView
  19.         recyclerView = findViewById(R.id.recyclerView);
  20.         recyclerView.setLayoutManager(new LinearLayoutManager(this));
  21.         // 设置自定义的 Item 动画
  22.         recyclerView.setItemAnimator(new CustomItemAnimator());
  23.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  24.             @Override
  25.             public void onItemClick(User user) {
  26.                 // 当 Item 被点击时,显示一个 Toast 消息
  27.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  28.             }
  29.         });
  30.         recyclerView.setAdapter(userAdapter);
  31.         // 获取 ViewModel 实例
  32.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  33.         // 观察 LiveData 数据的变化
  34.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  35.             @Override
  36.             public void onChanged(List<User> users) {
  37.                 // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
  38.                 userAdapter.setUserList(users);
  39.             }
  40.         });
  41.     }
  42. }
复制代码
6.1.4 源码分析

自界说 CustomItemAnimator 继续自 RecyclerView.ItemAnimator,并重写了 animateAdd、animateRemove、animateMove、animateChange 等方法来处理不同类型的动画。在 runPendingAnimations 方法中,会根据 pendingAdditions 和 pendingRemovals 列表中的 ViewHolder 执行相应的动画。animateAddImpl 和 animateRemoveImpl 方法分别使用 ObjectAnimator 实现了淡入和淡出的动画结果,并在动画开始和竣事时调用相应的 dispatch 方法通知 RecyclerView。
6.2 Activity 过渡动画

Activity 过渡动画可以让 Activity 之间的切换更加流畅和美观。Android 提供了多种过渡动画结果,如淡入淡出、滑动、缩放等。
6.2.1 使用体系默认过渡动画

在 Android 中,可以通过设置 Activity 的主题来使用体系默认的过渡动画。
xml
  1. <!-- styles.xml -->
  2. <resources>
  3.     <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
  4.         <!-- 设置 Activity 过渡动画 -->
  5.         <item name="android:windowActivityTransitions">true</item>
  6.         <item name="android:windowEnterTransition">@android:transition/fade</item>
  7.         <item name="android:windowExitTransition">@android:transition/fade</item>
  8.     </style>
  9. </resources>
复制代码
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. // 该 Activity 用于演示 Activity 过渡动画
  7. public class MainActivity extends AppCompatActivity {
  8.     private Button buttonOpenActivity;
  9.     @Override
  10.     protected void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.activity_main);
  13.         // 初始化按钮
  14.         buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
  15.         buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
  16.             @Override
  17.             public void onClick(View v) {
  18.                 // 启动新的 Activity
  19.                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
  20.                 startActivity(intent);
  21.             }
  22.         });
  23.     }
  24. }
复制代码
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import android.os.Bundle;
  3. // 第二个 Activity
  4. public class SecondActivity extends AppCompatActivity {
  5.     @Override
  6.     protected void onCreate(Bundle savedInstanceState) {
  7.         super.onCreate(savedInstanceState);
  8.         setContentView(R.layout.activity_second);
  9.     }
  10. }
复制代码
6.2.2 源码分析

在 styles.xml 中,通过设置 android:windowActivityTransitions 为 true 开启 Activity 过渡动画,android:windowEnterTransition 和 android:windowExitTransition 分别设置了 Activity 进入和退出时的过渡动画为淡入淡出结果。当在 MainActivity 中启动 SecondActivity 时,体系会自动应用这些过渡动画。
6.2.3 自界说过渡动画

除了使用体系默认的过渡动画,还可以自界说过渡动画。
xml
  1. <!-- res/transition/slide_transition.xml -->
  2. <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <slide
  4.         android:duration="300"
  5.         android:slideEdge="right" />
  6. </transitionSet>
复制代码
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import android.transition.TransitionInflater;
  5. import android.view.View;
  6. import android.widget.Button;
  7. // 该 Activity 用于演示自定义 Activity 过渡动画
  8. public class MainActivity extends AppCompatActivity {
  9.     private Button buttonOpenActivity;
  10.     @Override
  11.     protected void onCreate(Bundle savedInstanceState) {
  12.         super.onCreate(savedInstanceState);
  13.         setContentView(R.layout.activity_main);
  14.         // 初始化按钮
  15.         buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
  16.         buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
  17.             @Override
  18.             public void onClick(View v) {
  19.                 // 启动新的 Activity
  20.                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
  21.                 // 设置进入过渡动画
  22.                 getWindow().setEnterTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));
  23.                 // 设置退出过渡动画
  24.                 getWindow().setExitTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));
  25.                 startActivity(intent);
  26.             }
  27.         });
  28.     }
  29. }
复制代码
6.2.4 源码分析

在 res/transition/slide_transition.xml 中界说了一个滑动过渡动画,android:slideEdge="right" 表示从右侧滑入。在 MainActivity 中,通过 getWindow().setEnterTransition 和 getWindow().setExitTransition 方法设置了进入和退出时的过渡动画。当启动 SecondActivity 时,会应用自界说的滑动过渡动画。
七、表现层中的响应式设计

7.1 结构的响应式设计

在不同的屏幕尺寸和方向下,应用的结构须要可以或许自适应,以提供一致的用户体验。
7.1.1 使用 ConstraintLayout

ConstraintLayout 是 Android 中一个强大的结构管理器,它可以根据束缚条件来结构子视图,非常适合实现响应式设计。
xml
  1. <!-- activity_main.xml -->
  2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="match_parent">
  6.     <TextView
  7.         android:id="@+id/textViewTitle"
  8.         android:layout_width="wrap_content"
  9.         android:layout_height="wrap_content"
  10.         android:text="Title"
  11.         app:layout_constraintTop_toTopOf="parent"
  12.         app:layout_constraintStart_toStartOf="parent"
  13.         app:layout_constraintEnd_toEndOf="parent"
  14.         android:layout_marginTop="16dp" />
  15.     <Button
  16.         android:id="@+id/buttonAction"
  17.         android:layout_width="wrap_content"
  18.         android:layout_height="wrap_content"
  19.         android:text="Action"
  20.         app:layout_constraintTop_toBottomOf="@id/textViewTitle"
  21.         app:layout_constraintStart_toStartOf="parent"
  22.         app:layout_constraintEnd_toEndOf="parent"
  23.         android:layout_marginTop="16dp" />
  24. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码
7.1.2 源码分析

在上述结构文件中,使用 ConstraintLayout 来结构 TextView 和 Button。通过 app:layout_constraintTop_toTopOf、app:layout_constraintStart_toStartOf 等束缚条件,将 TextView 固定在结构的顶部中央,Button 固定在 TextView 的下方中央。这样,无论屏幕尺寸和方向如何变化,结构都会自适应调解。
7.1.3 使用不同的结构文件

除了使用 ConstraintLayout,还可以根据不同的屏幕尺寸和方向提供不同的结构文件。
plaintext
  1. res/
  2. ├── layout/
  3. │   └── activity_main.xml
  4. ├── layout-sw600dp/
  5. │   └── activity_main.xml
  6. ├── layout-land/
  7. │   └── activity_main.xml
复制代码
在 layout-sw600dp 目录下的 activity_main.xml 是针对屏幕最小宽度为 600dp 的装备的结构文件,layout-land 目录下的 activity_main.xml 是针对横屏的结构文件。体系会根据装备的屏幕尺寸和方向自动选择符合的结构文件。
7.2 数据的响应式设计

在表现层中,数据的展示也须要根据不同的情况进行响应式设计。例如,在不同的屏幕尺寸下,可能须要展示不同数目标数据项。
java
  1. import androidx.appcompat.app.AppCompatActivity;
  2. import androidx.lifecycle.Observer;
  3. import androidx.lifecycle.ViewModelProvider;
  4. import androidx.recyclerview.widget.GridLayoutManager;
  5. import androidx.recyclerview.widget.RecyclerView;
  6. import android.content.res.Configuration;
  7. import android.os.Bundle;
  8. import android.widget.Toast;
  9. import java.util.List;
  10. // 该 Activity 用于展示用户列表,并实现数据的响应式设计
  11. public class UserListActivity extends AppCompatActivity {
  12.     private RecyclerView recyclerView;
  13.     private UserAdapter userAdapter;
  14.     private UserViewModel userViewModel;
  15.     @Override
  16.     protected void onCreate(Bundle savedInstanceState) {
  17.         super.onCreate(savedInstanceState);
  18.         setContentView(R.layout.activity_user_list);
  19.         // 初始化 RecyclerView
  20.         recyclerView = findViewById(R.id.recyclerView);
  21.         // 根据屏幕方向设置不同的列数
  22.         int spanCount = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? 2 : 3;
  23.         recyclerView.setLayoutManager(new GridLayoutManager(this, spanCount));
  24.         userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
  25.             @Override
  26.             public void onItemClick(User user) {
  27.                 // 当 Item 被点击时,显示一个 Toast 消息
  28.                 Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
  29.             }
  30.         });
  31.         recyclerView.setAdapter(userAdapter);
  32.         // 获取 ViewModel 实例
  33.         userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
  34.         // 观察 LiveData 数据的变化
  35.         userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
  36.             @Override
  37.             public void onChanged(List<User> users) {
  38.                 // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
  39.                 userAdapter.setUserList(users);
  40.             }
  41.         });
  42.     }
  43. }
复制代码
7.2.1 源码分析

在 UserListActivity 中,根据屏幕的方向(竖屏或横屏)设置 GridLayoutManager 的列数。在竖屏时,列数为 2;在横屏时,列数为 3。这样,在不同的屏幕方向下,RecyclerView 会以不同的结构方式展示数据。
八、表现层中的性能优化

8.1 减少结构嵌套

结构嵌套过多会导致结构的测量和绘制时间增加,影响性能。可以使用 ConstraintLayout 等结构管理器来减少结构嵌套。
xml
  1. <!-- 优化前的布局 -->
  2. <LinearLayout
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:orientation="vertical">
  6.     <LinearLayout
  7.         android:layout_width="match_parent"
  8.         android:layout_height="wrap_content"
  9.         android:orientation="horizontal">
  10.         <TextView
  11.             android:layout_width="wrap_content"
  12.             android:layout_height="wrap_content"
  13.             android:text="Title" />
  14.         <Button
  15.             android:layout_width="wrap_content"
  16.             android:layout_height="wrap_content"
  17.             android:text="Action" />
  18.     </LinearLayout>
  19.     <ListView
  20.         android:layout_width="match_parent"
  21.         android:layout_height="match_parent" />
  22. </LinearLayout>
复制代码
xml
  1. <!-- 优化后的布局 -->
  2. <androidx.constraintlayout.widget.ConstraintLayout
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <TextView
  6.         android:id="@+id/textViewTitle"
  7.         android:layout_width="wrap_content"
  8.         android:layout_height="wrap_content"
  9.         android:text="Title"
  10.         app:layout_constraintTop_toTopOf="parent"
  11.         app:layout_constraintStart_toStartOf="parent"
  12.         android:layout_marginTop="16dp"
  13.         android:layout_marginStart="16dp" />
  14.     <Button
  15.         android:id="@+id/buttonAction"
  16.         android:layout_width="wrap_content"
  17.         android:layout_height="wrap_content"
  18.         android:text="Action"
  19.         app:layout_constraintTop_toTopOf="@id/textViewTitle"
  20.         app:layout_constraintStart_toEndOf="@id/textViewTitle"
  21.         android:layout_marginStart="16dp" />
  22.     <ListView
  23.         android:layout_width="match_parent"
  24.         android:layout_height="0dp"
  25.         app:layout_constraintTop_toBottomOf="@id/textViewTitle"
  26.         app:layout_constraintBottom_toBottomOf="parent"
  27.         android:layout_marginTop="16dp" />
  28. </androidx.constraintlayout.widget.ConstraintLayout>
复制代码
8.1.1 源码分析

优化前的结构使用了两层 LinearLayout 嵌套,而优化后的结构使用 ConstraintLayout 直接结构子视图,减少了结构嵌套。这样可以提高结构的测量和绘制效率。
8.2 克制在主线程进行耗时操作

在表现层中,应该克制在主线程进行耗时操作,如网络请求、数据库查询等。可以使用 ViewModel 和 LiveData 结合 Coroutine 或 RxJava 来实现异步操作。
java
  1. import androidx.lifecycle.LiveData;
  2. import androidx.lifecycle.MutableLiveData;
  3. import androidx.lifecycle.ViewModel;
  4. import java.util.List;
  5. import kotlinx.coroutines.CoroutineScope;
  6. import kotlinx.coroutines.Dispatchers;
  7. import kotlinx.coroutines.Job;
  8. import kotlinx.coroutines.launch;
  9. // 该 ViewModel 用于管理用户数据,并使用 Coroutine 进行异步操作
  10. public class UserViewModel extends ViewModel {
  11.     private UserRepository userRepository;
  12.     private MutableLiveData<List<User>> allUsers;
  13.     private Job job;
  14.     // 构造函数,初始化 UserRepository 并获取所有用户数据
  15.     public UserViewModel() {
  16.         userRepository = new UserRepository();
  17.         allUsers = new MutableLiveData<>();
  18.         loadUsers();
  19.     }
  20.     // 获取所有用户数据的 LiveData 对象
  21.     public LiveData<List<User>> getAllUsers() {
  22.         return allUsers;
  23.     }
  24.     // 加载用户数据的方法
  25.     private void loadUsers() {
  26.         job = CoroutineScope(Dispatchers.IO).launch {
  27.             // 在 IO 线程中进行数据库查询
  28.             List<User> users = userRepository.getAllUsersSync();
  29.             // 将结果切换到主线程更新 LiveData
  30.             CoroutineScope(Dispatchers.Main).launch {
  31.                 allUsers.setValue(users);
  32.             }
  33.         };
  34.     }
  35.     @Override
  36.     protected void onCleared() {
  37.         super.onCleared();
  38.         // 取消协程任务
  39.         if (job != null && job.isActive()) {
  40.             job.cancel();
  41.         }
  42.     }
  43. }
复制代码
8.2.2 源码分析

在 UserViewModel 中,使用 Coroutine 进行异步操作。loadUsers 方法在 IO 线程中进行数据库查询,查询完成后,将结果切换到主线程更新 LiveData。这样可以克制在主线程进行耗时的数据库查询,包管 UI 的流畅性。同时,在 ViewModel 烧毁时,取消协程使命,克制内存走漏。
8.3 使用 RecyclerView 的视图缓存

RecyclerView 提供了视图缓存机制,可以复用已经创建的视图,减少视图的创建和烧毁次数,提高性能。
java
  1. import android.view.LayoutInflater;
  2. import android.view.View;
  3. import android.view.ViewGroup;
  4. import android.widget.TextView;
  5. import androidx.annotation.NonNull;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import java.util.List;
  8. // 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
  9. public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
  10.     private List<User> userList;
  11.     // 构造函数,接收用户数据列表
  12.     public UserAdapter(List<User> userList) {
  13.         this.userList = userList;
  14.     }
  15.     // 创建 ViewHolder
  16.     @NonNull
  17.     @Override
  18.     public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  19.         // 加载 Item 的布局文件
  20.         View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
  21.         return new UserViewHolder(view);
  22.     }
  23.     // 绑定数据到 ViewHolder
  24.     @Override
  25.     public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
  26.         // 获取当前位置的用户数据
  27.         User user = userList.get(position);
  28.         // 将用户数据显示在 TextView 上
  29.         holder.textViewName.setText(user.getName());
  30.         holder.textViewAge.setText(String.valueOf(user.getAge()));
  31.     }
  32.     // 获取数据项的数量
  33.     @Override
  34.     public int getItemCount() {
  35.         return userList != null ? userList.size() : 0;
  36.     }
  37.     // 更新数据列表并刷新 Adapter
  38.     public void setUserList(List<User> userList) {
  39.         this.userList = userList;
  40.         notifyDataSetChanged();
  41.     }
  42.     // 定义 ViewHolder 类
  43.     static class UserViewHolder extends RecyclerView.ViewHolder {
  44.         TextView textViewName;
  45.         TextView textViewAge;
  46.         // 构造函数,初始化 ViewHolder 中的视图组件
  47.         UserViewHolder(@NonNull View itemView) {
  48.             super(itemView);
  49.             textViewName = itemView.findViewById(R.id.textViewName);
  50.             textViewAge = itemView.findViewById(R.id.textViewAge);
  51.         }
  52.     }
  53. }
复制代码
8.3.3 源码分析

在 UserAdapter 中,RecyclerView 会自动管理视图的缓存。当 RecyclerView 滚动时,会复用已经创建的 ViewHolder,只须要调用 onBindViewHolder 方法更新视图的数据。这样可以克制频仍创建和烧毁视图,提高性能。
九、表现层中的无停滞设计

9.1 为视图添加内容描述

java
  1. // 在 RecyclerView Adapter 中设置内容描述(关键源码)
  2. class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
  3.     @NonNull
  4.     @Override
  5.     public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  6.         View itemView = LayoutInflater.from(parent.getContext())
  7.            .inflate(R.layout.item_user, parent, false);
  8.         
  9.         // 为整个 Item 设置内容描述(辅助功能)
  10.         itemView.setContentDescription("用户项:姓名 ${user.name},年龄 ${user.age}岁");
  11.         return new ViewHolder(itemView);
  12.     }
  13.     @Override
  14.     public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
  15.         User user = userList.get(position);
  16.         
  17.         // 为姓名 TextView 添加无障碍标签
  18.         holder.nameView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
  19.             @Override
  20.             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
  21.                 super.onInitializeAccessibilityNodeInfo(host, info);
  22.                 info.setText("用户名:" + user.name);
  23.                 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
  24.             }
  25.         });
  26.     }
  27. }
  28. // Android 框架中 ContentDescription 的处理逻辑(View.java)
  29. public void setContentDescription(CharSequence contentDescription) {
  30.     mContentDescription = contentDescription;
  31.     // 触发无障碍节点更新
  32.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CONTENT_DESCRIPTION_CHANGED);
  33. }
  34. // RecyclerView 辅助功能更新机制(RecyclerView.java)
  35. @Override
  36. public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
  37.     super.onInitializeAccessibilityNodeInfo(host, info);
  38.     // 自动聚合子项的无障碍信息
  39.     info.setCollectionInfo(CollectionInfo.obtain(
  40.         getAdapter().getItemCount(),
  41.         getChildCount(),
  42.         isLayoutRtl()
  43.     ));
  44. }
复制代码
9.2 动态字体适配(源码实现)

xml
  1. <!-- 布局文件中启用自动字体大小 -->
  2. <TextView
  3.     android:id="@+id/user_name"
  4.     android:layout_width="wrap_content"
  5.     android:layout_height="wrap_content"
  6.     android:autofillHints="@string/hint_name"
  7.     android:textSize="@dimen/font_size_normal"
  8.     android:fontVariationSettings="wdth 100, wght 400"
  9.     tools:text="张三" />
复制代码
java
  1. // 系统字体适配核心类(Configuration.java)
  2. public class Configuration {
  3.     public float fontScale; // 字体缩放比例(用户设置)
  4.     // 框架内部处理逻辑(ActivityThread.java)
  5.     private void handleConfigurationChanged(Configuration config) {
  6.         ViewRootImpl[] roots = mRoots.getArray();
  7.         for (ViewRootImpl root : roots) {
  8.             root.setLayoutParams(null, config, null);
  9.         }
  10.         // 触发全局字体更新
  11.         applyOverrideConfiguration(config);
  12.     }
  13. }
  14. // 在 Activity 中监听字体变化
  15. public class UserActivity extends AppCompatActivity {
  16.     @Override
  17.     public void onConfigurationChanged(Configuration newConfig) {
  18.         super.onConfigurationChanged(newConfig);
  19.         // 重新绑定数据触发字体更新
  20.         userViewModel.getAllUsers().observe(this, users -> adapter.setUsers(users));
  21.     }
  22. }
复制代码
十、Jetpack Compose 与 Room 的深度集成

10.1 基于 Compose 的数据绑定(核心源码)

kotlin
  1. // Compose 界面组件
  2. @Composable
  3. fun UserListScreen(viewModel: UserViewModel = viewModel()) {
  4.     val users by viewModel.users.collectAsState(emptyList())
  5.     val context = LocalContext.current
  6.     RecyclerView(
  7.         modifier = Modifier.fillMaxSize(),
  8.         verticalArrangement = Arrangement.spacedBy(16.dp)
  9.     ) {
  10.         items(users) { user ->
  11.             UserItem(
  12.                 user = user,
  13.                 onClick = { viewModel.onUserClick(user) }
  14.             )
  15.         }
  16.     }
  17.     // 加载状态处理
  18.     if (viewModel.isLoading.value) {
  19.         CircularProgressIndicator(modifier = Modifier.centerInParent())
  20.     }
  21. }
  22. // ViewModel 中的 State 管理
  23. class UserViewModel : ViewModel() {
  24.     private val _users = MutableStateFlow<List<User>>(emptyList())
  25.     val users = _users.asStateFlow()
  26.    
  27.     private val _isLoading = MutableStateFlow(false)
  28.     val isLoading = _isLoading.asStateFlow()
  29.     init {
  30.         loadUsers()
  31.     }
  32.     private fun loadUsers() {
  33.         viewModelScope.launch {
  34.             _isLoading.value = true
  35.             try {
  36.                 val users = userRepository.getAllUsers()
  37.                 _users.value = users
  38.             } finally {
  39.                 _isLoading.value = false
  40.             }
  41.         }
  42.     }
  43. }
  44. // Room 协程支持(Compose 专用扩展)
  45. @Dao
  46. interface UserDao {
  47.     @Query("SELECT * FROM users")
  48.     fun getAllUsersFlow(): Flow<List<User>> // 直接返回 Flow
  49. }
复制代码
10.2 Compose 与 LiveData 的互操作

kotlin
  1. // LiveData 转 Compose State
  2. @Composable
  3. fun <T> LiveData<T>.asComposeState(
  4.     context: Context = LocalContext.current,
  5.     initialValue: T
  6. ): State<T> {
  7.     val state = remember { mutableStateOf(initialValue) }
  8.     val lifecycleOwner = rememberUpdatedState(context as LifecycleOwner)
  9.    
  10.     DisposableEffect(this) {
  11.         val observer = Observer<T> { state.value = it }
  12.         observe(lifecycleOwner.value, observer)
  13.         onDispose { removeObserver(observer) }
  14.     }
  15.     return state
  16. }
  17. // 使用示例
  18. val users = userViewModel.allUsers.asComposeState(emptyList())
复制代码
十一、表现层性能优化深度解析

11.1 RecyclerView 预结构优化(源码级)

java
  1. // 自定义 RecyclerView(优化预布局)
  2. class OptimizedRecyclerView @JvmOverloads constructor(
  3.     context: Context,
  4.     attrs: AttributeSet? = null,
  5.     defStyle: Int = 0
  6. ) : RecyclerView(context, attrs, defStyle) {
  7.     override fun onMeasure(widthSpec: Int, heightSpec: Int) {
  8.         // 禁用预布局(针对固定高度列表)
  9.         setHasFixedSize(true);
  10.         super.onMeasure(widthSpec, heightSpec);
  11.     }
  12.     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
  13.         // 跳过不必要的布局计算
  14.         if (!changed) return;
  15.         super.onLayout(changed, l, t, r, b);
  16.     }
  17. }
  18. // 框架预布局逻辑(RecyclerView.java)
  19. void processLayout(Recycler recycler, State state) {
  20.     if (mState.mRunPredictiveAnimations) {
  21.         // 预布局用于动画计算
  22.         performPredictiveLayout(recycler, state);
  23.     }
  24.     // 正式布局
  25.     performLayout(recycler, state);
  26. }
复制代码
11.2 数据变更的细粒度更新(DiffUtil 源码)

java
  1. // Adapter 中的 DiffUtil 实现
  2. class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(USER_DIFF_CALLBACK) {
  3.     companion object {
  4.         val USER_DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
  5.             override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
  6.                 return oldItem.id == newItem.id // 基于唯一标识判断
  7.             }
  8.             override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
  9.                 return oldItem == newItem // 基于内容比较
  10.             }
  11.         }
  12.     }
  13.     // 框架内部 Diff 计算(DiffUtil.java)
  14.     public static DiffResult calculateDiff(Callback callback) {
  15.         return new DiffUtil(callback).calculate();
  16.     }
  17.     // 差异分发(RecyclerView.java)
  18.     public void dispatchUpdateRanges(List<UpdateOp> ops) {
  19.         mAdapterHelper.calculateDiff(ops);
  20.         // 触发局部刷新
  21.         for (UpdateOp op : ops) {
  22.             dispatchSingleUpdate(op);
  23.         }
  24.     }
  25. }
复制代码
十二、表现层与 Room 的生命周期协同

12.1 ViewModel 与 Room 的绑定

java
  1. // ViewModel 中的 Room 初始化(关键源码)
  2. class UserViewModel(application: Application) : AndroidViewModel(application) {
  3.     private val database by lazy {
  4.         AppDatabase.getInstance(application) // 生命周期感知的数据库实例
  5.     }
  6.     val users: LiveData<List<User>> = database.userDao().getAllUsers()
  7.     override fun onCleared() {
  8.         super.onCleared()
  9.         // 释放数据库资源(可选)
  10.         database.close()
  11.     }
  12. }
  13. // 数据库单例实现(AppDatabase.java)
  14. public class AppDatabase {
  15.     private static volatile AppDatabase INSTANCE;
  16.     public static AppDatabase getInstance(Context context) {
  17.         if (INSTANCE == null) {
  18.             synchronized (AppDatabase.class) {
  19.                 if (INSTANCE == null) {
  20.                     INSTANCE = Room.databaseBuilder(
  21.                         context.getApplicationContext(),
  22.                         AppDatabase.class, "user.db"
  23.                     ).addCallback(new Callback() {
  24.                         @Override
  25.                         public void onCreate(@NonNull SupportSQLiteDatabase db) {
  26.                             // 初始化数据(可选)
  27.                         }
  28.                     }).build();
  29.                 }
  30.             }
  31.         }
  32.         return INSTANCE;
  33.     }
  34. }
复制代码
12.2 LiveData 的生命周期安全(源码解析)

java
  1. // LiveData observe 方法(LiveData.java)
  2. public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
  3.     // 检查生命周期状态
  4.     if (owner.getLifecycle().getCurrentState() == DESTROYED) {
  5.         return;
  6.     }
  7.     LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
  8.     ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
  9.     if (existing != null && !existing.isAttachedTo(owner)) {
  10.         throw new IllegalArgumentException("Cannot add the same observer");
  11.     }
  12.     owner.getLifecycle().addObserver(wrapper);
  13. }
  14. // 生命周期事件处理(LifecycleBoundObserver.java)
  15. @Override
  16. public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
  17.     if (source.getLifecycle().getCurrentState() == DESTROYED) {
  18.         removeObserver(mObserver);
  19.         return;
  20.     }
  21.     activeStateChanged(shouldBeActive());
  22. }
复制代码
十三、表现层单元测试(源码级验证)

13.1 纯 UI 测试(不带 Room)

java
  1. @RunWith(AndroidJUnit4::class)
  2. public class UserAdapterTest {
  3.     private UserAdapter adapter;
  4.     @Before
  5.     public void setup() {
  6.         adapter = new UserAdapter(Collections.emptyList());
  7.     }
  8.     @Test
  9.     public void testViewHolderBinding() {
  10.         // 创建测试 View
  11.         View itemView = LayoutInflater.from(ApplicationProvider.getApplicationContext())
  12.            .inflate(R.layout.item_user, null);
  13.         UserAdapter.ViewHolder holder = new UserAdapter.ViewHolder(itemView);
  14.         
  15.         // 绑定数据
  16.         User user = new User(1, "张三", 25);
  17.         adapter.onBindViewHolder(holder, 0);
  18.         
  19.         // 验证 UI 显示
  20.         assertEquals("张三", holder.nameView.getText());
  21.         assertEquals("25", holder.ageView.getText());
  22.     }
  23.     @Test
  24.     public void testDiffUtil() {
  25.         User oldUser = new User(1, "张三", 25);
  26.         User newUser = new User(1, "张三", 26);
  27.         
  28.         // 测试内容变更
  29.         assertEquals(false, USER_DIFF_CALLBACK.areContentsTheSame(oldUser, newUser));
  30.     }
  31. }
复制代码
13.2 集成测试(结合 Room 测试库)

java
  1. @RunWith(AndroidJUnit4::class)
  2. public class UserActivityTest {
  3.     private final UserDao dao = Room.inMemoryDatabaseBuilder(
  4.         ApplicationProvider.getApplicationContext(),
  5.         AppDatabase.class
  6.     ).allowMainThreadQueries().build().userDao();
  7.     @Test
  8.     public void testUserListUpdate() {
  9.         // 插入测试数据
  10.         dao.insert(new User(1, "李四", 30));
  11.         
  12.         // 启动 Activity
  13.         ActivityScenario.launch(UserActivity.class);
  14.         
  15.         // 验证列表显示
  16.         onView(withText("李四")).check(matches(isDisplayed()));
  17.     }
  18.     @Test
  19.     public void testAddUserFlow() {
  20.         // 启动添加用户 Activity
  21.         ActivityScenario.launch(AddUserActivity.class);
  22.         
  23.         // 模拟输入
  24.         onView(withId(R.id.edit_name)).perform(typeText("王五"));
  25.         onView(withId(R.id.edit_age)).perform(typeText("28"));
  26.         onView(withId(R.id.btn_add)).perform(click());
  27.         
  28.         // 验证列表更新
  29.         onView(withText("王五")).check(matches(isDisplayed()));
  30.     }
  31. }
复制代码
十四、表现层设计模式实战

14.1 状态模式(UI 状态管理)

java
  1. // UI 状态枚举
  2. enum UiState {
  3.     LOADING,
  4.     CONTENT,
  5.     ERROR
  6. }
  7. // Activity 中的状态管理
  8. public class UserActivity extends AppCompatActivity {
  9.     private UiState currentState = UiState.LOADING;
  10.     private void updateState(UiState newState) {
  11.         currentState = newState;
  12.         runOnUiThread(() -> {
  13.             switch (newState) {
  14.                 case LOADING:
  15.                     showLoading();
  16.                     hideContent();
  17.                     hideError();
  18.                     break;
  19.                 case CONTENT:
  20.                     hideLoading();
  21.                     showContent();
  22.                     hideError();
  23.                     break;
  24.                 case ERROR:
  25.                     hideLoading();
  26.                     hideContent();
  27.                     showError();
  28.                     break;
  29.             }
  30.         });
  31.     }
  32.     // 框架内部的状态更新(ActivityThread.java)
  33.     public void handleResumeActivity(IBinder token, boolean finalStateRequest) {
  34.         // 恢复 Activity 状态
  35.         performResumeActivity(token, finalStateRequest);
  36.         // 触发 UI 状态更新
  37.         mMainThreadHandler.obtainMessage(H.RESUME_ACTIVITY, token).sendToTarget();
  38.     }
  39. }
复制代码
14.2 策略模式(UI 样式切换)

java
  1. // 主题策略接口
  2. interface ThemeStrategy {
  3.     int getBackgroundColor();
  4.     int getTextColor();
  5. }
  6. // 浅色主题实现
  7. class LightThemeStrategy implements ThemeStrategy {
  8.     @Override
  9.     public int getBackgroundColor() {
  10.         return Color.WHITE;
  11.     }
  12.     @Override
  13.     public int getTextColor() {
  14.         return Color.BLACK;
  15.     }
  16. }
  17. // Activity 中的策略应用
  18. public class UserActivity extends AppCompatActivity {
  19.     private ThemeStrategy themeStrategy = new LightThemeStrategy();
  20.     @Override
  21.     protected void onCreate(Bundle savedInstanceState) {
  22.         super.onCreate(savedInstanceState);
  23.         setContentView(R.layout.activity_user);
  24.         
  25.         // 应用主题策略
  26.         userList.setBackgroundColor(themeStrategy.getBackgroundColor());
  27.         titleView.setTextColor(themeStrategy.getTextColor());
  28.     }
  29.     // 动态切换主题
  30.     public void switchToDarkTheme() {
  31.         themeStrategy = new DarkThemeStrategy();
  32.         recreate();
  33.     }
  34. }
复制代码
十五、表现层异常处理最佳实践

15.1 全局异常捕获(源码实现)

java
  1. // 全局异常处理器
  2. public class AppExceptionHandler implements Thread.UncaughtExceptionHandler {
  3.     private final Thread.UncaughtExceptionHandler defaultHandler;
  4.     private final Context context;
  5.     public AppExceptionHandler(Context context) {
  6.         this.context = context;
  7.         defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
  8.     }
  9.     @Override
  10.     public void uncaughtException(Thread thread, Throwable ex) {
  11.         if (ex instanceof SQLiteException) {
  12.             // 处理 Room 数据库异常
  13.             showDatabaseError(ex);
  14.             return;
  15.         }
  16.         defaultHandler.uncaughtException(thread, ex);
  17.     }
  18.     private void showDatabaseError(Throwable ex) {
  19.         new AlertDialog.Builder(context)
  20.            .setTitle("数据库错误")
  21.            .setMessage("数据加载失败:" + ex.getMessage())
  22.            .setPositiveButton("重试", (dialog, which) -> recreate())
  23.            .show();
  24.     }
  25. }
  26. // 在 Application 中注册
  27. public class AppApplication extends Application {
  28.     @Override
  29.     public void onCreate() {
  30.         super.onCreate();
  31.         Thread.setDefaultUncaughtExceptionHandler(new AppExceptionHandler(this));
  32.     }
  33. }
复制代码
15.2 Room 异常转换(表现层适配)

java
  1. // 在 Repository 中封装异常
  2. class UserRepository {
  3.     public LiveData<List<User>> getUsers() {
  4.         return Transformations.map(dao.getAllUsers(), users -> {
  5.             try {
  6.                 return users;
  7.             } catch (SQLiteException e) {
  8.                 // 转换为表现层可识别的异常
  9.                 throw new UiDataException("数据库查询失败", e);
  10.             }
  11.         });
  12.     }
  13. }
  14. // Activity 中的异常处理
  15. userViewModel.users.observe(this, users -> {
  16.     if (users instanceof UiDataException) {
  17.         showError((UiDataException) users);
  18.         return;
  19.     }
  20.     updateUI(users);
  21. });
复制代码
十六、总结:表现层的 Room 集成哲学

16.1 源码架构总览

plaintext
  1. 表现层
  2. ├─ Activity/Fragment (UI 宿主)
  3. │  ├─ ViewModel (数据与逻辑)
  4. │  └─ LiveData/State (数据订阅)
  5. ├─ RecyclerView/Compose (数据展示)
  6. │  ├─ Adapter (数据绑定)
  7. │  └─ DiffUtil (增量更新)
  8. ├─ DataBinding (视图绑定)
  9. └─ 无障碍/动画/性能 (体验优化)
  10. Room 依赖
  11. ├─ DAO (数据访问接口)
  12. ├─ LiveData/Flow (数据订阅源)
  13. └─ TypeConverter (数据转换)
复制代码
16.2 核心设计原则


  • 单向数据流:Room → ViewModel → UI,确保状态可追溯(参考 LiveData 源码)
  • 生命周期感知:通过 LifecycleOwner 管理 Room 查询(ViewModel 内部实现)
  • 增量更新:利用 DiffUtil 和 RecyclerView 局部革新(减少绘制操作)
  • 异步抽象:通过协程 / LiveData 隐藏 Room 线程细节(SuspendSupport 源码)
  • 防御性编程:在 UI 层处理 Room 异常(SQLiteException 转换)
16.3 性能优化清单

优化点实现方式源码位置列表更新DiffUtil + RecyclerView 局部革新ListAdapter.java数据订阅LiveData 自动生命周期绑定LifecycleBoundObserver.java结构性能DataBinding 替代 findViewByIdDataBinderMapperImpl.java内存管理ViewModel 绑定 Room 单例ViewModelStore.java动画优化默认 ItemAnimator + 自界说过渡DefaultItemAnimator.java 16.4 反模式规避


  • ❌ 在 Activity 直接操作 Room DAO(紧耦合)
    ✅ 通过 ViewModel 间接访问(参考 UserViewModel 源码)
  • ❌ 忽略 DiffUtil 导致全量革新(性能损耗)
    ✅ 使用 ListAdapter 逼迫差分更新(源码逼迫实现 getItemId)
  • ❌ 在 UI 线程执行 Room 查询(ANR 风险)
    ✅ 通过 LiveData / 协程自动切换线程(RoomDatabase 内部查抄)
  • ❌ 复杂结构嵌套(过分绘制)
    ✅ 使用 ConstraintLayout + 扁平化结构(减少层级)
  • ❌ 内存走漏(未取消订阅)
    ✅ LiveData 自动解绑(LifecycleOwner 机制)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表