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

Android Room 框架表现层源码深度剖析

一、引言

在 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

java 复制代码
// 定义 RecyclerView 的 Adapter
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
    private List<User> userList;

    // 构造函数,接收用户数据列表
    public UserAdapter(List<User> userList) {
        this.userList = userList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载 Item 的布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
        return new UserViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
        // 获取当前位置的用户数据
        User user = userList.get(position);
        // 将用户数据显示在 TextView 上
        holder.textViewName.setText(user.getName());
        holder.textViewAge.setText(String.valueOf(user.getAge()));
    }

    // 获取数据项的数量
    @Override
    public int getItemCount() {
        return userList != null ? userList.size() : 0;
    }

    // 更新数据列表并刷新 Adapter
    public void setUserList(List<User> userList) {
        this.userList = userList;
        notifyDataSetChanged();
    }

    // 定义 ViewHolder 类
    static class UserViewHolder extends RecyclerView.ViewHolder {
        TextView textViewName;
        TextView textViewAge;

        // 构造函数,初始化 ViewHolder 中的视图组件
        UserViewHolder(@NonNull View itemView) {
            super(itemView);
            textViewName = itemView.findViewById(R.id.textViewName);
            textViewAge = itemView.findViewById(R.id.textViewAge);
        }
    }
}

java

java 复制代码
// 在 Activity 中使用 RecyclerView 展示数据
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.List;

// 该 Activity 用于展示用户列表
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        userAdapter = new UserAdapter(null);
        recyclerView.setAdapter(userAdapter);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}

3.2 源码分析

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

3.3 使用 LiveData 实现数据的实时更新

LiveData 是 Android 架构组件中的一个可观察的数据持有者类,它具有生命周期感知能力,能够在 Activity 或 Fragment 的生命周期内自动管理数据的更新。

java

java 复制代码
// 定义 UserViewModel
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;

// 该 ViewModel 用于管理用户数据
public class UserViewModel extends ViewModel {
    private UserRepository userRepository;
    private LiveData<List<User>> allUsers;

    // 构造函数,初始化 UserRepository 并获取所有用户数据
    public UserViewModel() {
        userRepository = new UserRepository();
        allUsers = userRepository.getAllUsers();
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }
}

java

java 复制代码
// 定义 UserRepository
import androidx.lifecycle.LiveData;
import java.util.List;

// 该 Repository 用于与数据层交互,获取用户数据
public class UserRepository {
    private UserDao userDao;

    // 构造函数,初始化 UserDao
    public UserRepository() {
        AppDatabase appDatabase = AppDatabase.getDatabase();
        userDao = appDatabase.userDao();
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return userDao.getAllUsers();
    }
}

java

java 复制代码
// 定义 UserDao
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;

// 该 DAO 接口定义了与用户数据相关的数据库操作方法
@Dao
public interface UserDao {
    // 查询所有用户数据,并返回 LiveData 类型的结果
    @Query("SELECT * FROM user")
    LiveData<List<User>> getAllUsers();
}

3.4 源码分析

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

四、表现层中的用户交互处理

4.1 处理 RecyclerView 中的点击事件

RecyclerView 中处理点击事件可以让用户与列表项进行交互。

java

java 复制代码
// 在 UserAdapter 中添加点击事件处理
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上,并处理点击事件
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
    private List<User> userList;
    private OnItemClickListener onItemClickListener;

    // 构造函数,接收用户数据列表和点击事件监听器
    public UserAdapter(List<User> userList, OnItemClickListener onItemClickListener) {
        this.userList = userList;
        this.onItemClickListener = onItemClickListener;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载 Item 的布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
        return new UserViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
        // 获取当前位置的用户数据
        User user = userList.get(position);
        // 将用户数据显示在 TextView 上
        holder.textViewName.setText(user.getName());
        holder.textViewAge.setText(String.valueOf(user.getAge()));

        // 设置点击事件监听器
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onItemClickListener != null) {
                    // 当点击事件发生时,调用监听器的回调方法
                    onItemClickListener.onItemClick(user);
                }
            }
        });
    }

    // 获取数据项的数量
    @Override
    public int getItemCount() {
        return userList != null ? userList.size() : 0;
    }

    // 更新数据列表并刷新 Adapter
    public void setUserList(List<User> userList) {
        this.userList = userList;
        notifyDataSetChanged();
    }

    // 定义点击事件监听器接口
    public interface OnItemClickListener {
        // 当 Item 被点击时调用该方法
        void onItemClick(User user);
    }

    // 定义 ViewHolder 类
    static class UserViewHolder extends RecyclerView.ViewHolder {
        TextView textViewName;
        TextView textViewAge;

        // 构造函数,初始化 ViewHolder 中的视图组件
        UserViewHolder(@NonNull View itemView) {
            super(itemView);
            textViewName = itemView.findViewById(R.id.textViewName);
            textViewAge = itemView.findViewById(R.id.textViewAge);
        }
    }
}

java

java 复制代码
// 在 UserListActivity 中处理点击事件
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表并处理点击事件
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}

4.2 源码分析

UserAdapter 中,我们定义了一个 OnItemClickListener 接口,并在 onBindViewHolder 方法中为每个 Item 设置点击事件监听器。当 Item 被点击时,会调用监听器的 onItemClick 方法。在 UserListActivity 中,我们实现了 OnItemClickListener 接口,并在 onItemClick 方法中显示一个 Toast 消息,以响应用户的点击操作。

4.3 处理表单输入和提交

在表现层中,我们经常需要处理用户的表单输入,并将输入的数据提交到数据层。

java

java 复制代码
// 定义 AddUserActivity
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.lifecycle.ViewModelProvider;

// 该 Activity 用于添加新用户
public class AddUserActivity extends AppCompatActivity {
    private EditText editTextName;
    private EditText editTextAge;
    private Button buttonAdd;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_user);

        // 初始化视图组件
        editTextName = findViewById(R.id.editTextName);
        editTextAge = findViewById(R.id.editTextAge);
        buttonAdd = findViewById(R.id.buttonAdd);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 设置按钮的点击事件监听器
        buttonAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 获取用户输入的姓名和年龄
                String name = editTextName.getText().toString().trim();
                String ageStr = editTextAge.getText().toString().trim();
                if (!name.isEmpty() && !ageStr.isEmpty()) {
                    int age = Integer.parseInt(ageStr);
                    // 创建新的用户对象
                    User user = new User(name, age);
                    // 调用 ViewModel 的方法添加用户
                    userViewModel.insertUser(user);
                    // 关闭当前 Activity
                    finish();
                }
            }
        });
    }
}

java

java 复制代码
// 在 UserViewModel 中添加插入用户的方法
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;

// 该 ViewModel 用于管理用户数据
public class UserViewModel extends ViewModel {
    private UserRepository userRepository;
    private LiveData<List<User>> allUsers;

    // 构造函数,初始化 UserRepository 并获取所有用户数据
    public UserViewModel() {
        userRepository = new UserRepository();
        allUsers = userRepository.getAllUsers();
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }

    // 插入新用户的方法
    public void insertUser(User user) {
        userRepository.insertUser(user);
    }
}

java

java 复制代码
// 在 UserRepository 中添加插入用户的方法
import androidx.lifecycle.LiveData;
import java.util.List;

// 该 Repository 用于与数据层交互,获取用户数据
public class UserRepository {
    private UserDao userDao;

    // 构造函数,初始化 UserDao
    public UserRepository() {
        AppDatabase appDatabase = AppDatabase.getDatabase();
        userDao = appDatabase.userDao();
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return userDao.getAllUsers();
    }

    // 插入新用户的方法
    public void insertUser(User user) {
        userDao.insertUser(user);
    }
}

java

java 复制代码
// 在 UserDao 中添加插入用户的方法
import androidx.room.Dao;
import androidx.room.Insert;

// 该 DAO 接口定义了与用户数据相关的数据库操作方法
@Dao
public interface UserDao {
    // 查询所有用户数据,并返回 LiveData 类型的结果
    @Query("SELECT * FROM user")
    LiveData<List<User>> getAllUsers();

    // 插入新用户的方法
    @Insert
    void insertUser(User user);
}

4.4 源码分析

AddUserActivity 中,我们获取用户输入的姓名和年龄,创建新的 User 对象,并调用 UserViewModelinsertUser 方法将用户数据插入到数据库中。UserViewModel 调用 UserRepositoryinsertUser 方法,UserRepository 再调用 UserDaoinsertUser 方法。UserDao 使用 @Insert 注解定义了插入用户数据的方法。

五、表现层中的 UI 状态管理

5.1 加载状态管理

在获取数据时,我们需要显示加载状态,以提高用户体验。

java

java 复制代码
// 在 UserListActivity 中添加加载状态管理
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表,添加了加载状态管理
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 初始化 ProgressBar
        progressBar = findViewById(R.id.progressBar);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 显示加载状态
        progressBar.setVisibility(View.VISIBLE);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据加载完成后,隐藏加载状态
                progressBar.setVisibility(View.GONE);
                // 更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}

5.2 源码分析

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

5.3 错误状态管理

当数据获取失败时,我们需要显示错误状态。

java

java 复制代码
// 在 UserViewModel 中添加错误状态管理
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;

// 该 ViewModel 用于管理用户数据,添加了错误状态管理
public class UserViewModel extends ViewModel {
    private UserRepository userRepository;
    private LiveData<List<User>> allUsers;
    private MutableLiveData<String> errorMessage;

    // 构造函数,初始化 UserRepository 并获取所有用户数据
    public UserViewModel() {
        userRepository = new UserRepository();
        allUsers = userRepository.getAllUsers();
        errorMessage = new MutableLiveData<>();
        // 模拟数据获取失败的情况
        userRepository.getErrorLiveData().observeForever(new Observer<String>() {
            @Override
            public void onChanged(String error) {
                errorMessage.setValue(error);
            }
        });
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }

    // 获取错误消息的 LiveData 对象
    public LiveData<String> getErrorMessage() {
        return errorMessage;
    }

    // 插入新用户的方法
    public void insertUser(User user) {
        userRepository.insertUser(user);
    }
}

java

java 复制代码
// 在 UserRepository 中添加错误状态管理
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;

// 该 Repository 用于与数据层交互,获取用户数据,添加了错误状态管理
public class UserRepository {
    private UserDao userDao;
    private MutableLiveData<String> errorLiveData;

    // 构造函数,初始化 UserDao
    public UserRepository() {
        AppDatabase appDatabase = AppDatabase.getDatabase();
        userDao = appDatabase.userDao();
        errorLiveData = new MutableLiveData<>();
        // 模拟数据获取失败的情况
        try {
            // 这里可以添加实际的错误处理逻辑
            if (Math.random() < 0.1) {
                throw new Exception("Data fetch failed");
            }
        } catch (Exception e) {
            errorLiveData.setValue(e.getMessage());
        }
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return userDao.getAllUsers();
    }

    // 获取错误消息的 LiveData 对象
    public LiveData<String> getErrorLiveData() {
        return errorLiveData;
    }

    // 插入新用户的方法
    public void insertUser(User user) {
        userDao.insertUser(user);
    }
}

java

java 复制代码
// 在 UserListActivity 中处理错误状态
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表,处理错误状态
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;
    private ProgressBar progressBar;
    private TextView textViewError;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 初始化 ProgressBar
        progressBar = findViewById(R.id.progressBar);

        // 初始化错误提示 TextView
        textViewError = findViewById(R.id.textViewError);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 显示加载状态
        progressBar.setVisibility(View.VISIBLE);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据加载完成后,隐藏加载状态
                progressBar.setVisibility(View.GONE);
                if (users != null) {
                    // 隐藏错误提示
                    textViewError.setVisibility(View.GONE);
                    // 更新 Adapter 中的数据并刷新界面
                    userAdapter.setUserList(users);
                }
            }
        });

        // 观察错误消息的变化
        userViewModel.getErrorMessage().observe(this, new Observer<String>() {
            @Override
            public void onChanged(String error) {
                // 隐藏加载状态
                progressBar.setVisibility(View.GONE);
                if (error != null) {
                    // 显示错误提示
                    textViewError.setVisibility(View.VISIBLE);
                    textViewError.setText(error);
                }
            }
        });
    }
}

5.4 源码分析

UserViewModelUserRepository 中,我们添加了 MutableLiveData 类型的 errorMessage 用于存储错误消息。在 UserListActivity 中,我们观察 errorMessage 的变化,当有错误消息时,隐藏加载状态并显示错误提示。

六、表现层中的动画和过渡效果

6.1 RecyclerView 中的动画效果

RecyclerView 中添加动画效果可以提升用户体验,让数据的更新更加生动。Android 为 RecyclerView 提供了默认的动画效果,同时也允许开发者自定义动画。

6.1.1 使用默认动画

RecyclerView 默认使用 DefaultItemAnimator 来实现动画效果,当数据发生变化时(如插入、删除、更新),会自动播放相应的动画。

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表,并使用 RecyclerView 默认动画
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // 设置默认的 Item 动画
        recyclerView.setItemAnimator(new DefaultItemAnimator());

        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}
6.1.2 源码分析

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

6.1.3 自定义动画

如果默认动画不能满足需求,开发者可以自定义动画。以下是一个自定义 ItemAnimator 的示例:

java

java 复制代码
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

// 自定义的 RecyclerView Item 动画类
public class CustomItemAnimator extends RecyclerView.ItemAnimator {
    private List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
    private List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();

    @Override
    public boolean animateAdd(RecyclerView.ViewHolder holder) {
        pendingAdditions.add(holder);
        return true;
    }

    @Override
    public boolean animateRemove(RecyclerView.ViewHolder holder) {
        pendingRemovals.add(holder);
        return true;
    }

    @Override
    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        // 这里可以实现移动动画逻辑,暂不实现
        dispatchMoveFinished(holder);
        return false;
    }

    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
        // 这里可以实现变更动画逻辑,暂不实现
        dispatchChangeFinished(oldHolder, true);
        dispatchChangeFinished(newHolder, false);
        return false;
    }

    @Override
    public void runPendingAnimations() {
        if (!pendingAdditions.isEmpty()) {
            for (RecyclerView.ViewHolder holder : pendingAdditions) {
                // 为新增的 Item 播放动画
                animateAddImpl(holder);
            }
            pendingAdditions.clear();
        }

        if (!pendingRemovals.isEmpty()) {
            for (RecyclerView.ViewHolder holder : pendingRemovals) {
                // 为删除的 Item 播放动画
                animateRemoveImpl(holder);
            }
            pendingRemovals.clear();
        }
    }

    private void animateAddImpl(final RecyclerView.ViewHolder holder) {
        View view = holder.itemView;
        // 使用属性动画实现淡入效果
        ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
        animator.setDuration(300);
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                dispatchAddStarting(holder);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                dispatchAddFinished(holder);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                dispatchAddFinished(holder);
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                // 不处理重复动画
            }
        });
        animator.start();
    }

    private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
        View view = holder.itemView;
        // 使用属性动画实现淡出效果
        ObjectAnimator animator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f);
        animator.setDuration(300);
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                dispatchRemoveFinished(holder);
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                dispatchRemoveFinished(holder);
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                // 不处理重复动画
            }
        });
        animator.start();
    }

    @Override
    public void endAnimation(RecyclerView.ViewHolder item) {
        // 结束动画的逻辑
    }

    @Override
    public void endAnimations() {
        // 结束所有动画的逻辑
    }

    @Override
    public boolean isRunning() {
        return !pendingAdditions.isEmpty() || !pendingRemovals.isEmpty();
    }
}

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表,并使用自定义的 RecyclerView 动画
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // 设置自定义的 Item 动画
        recyclerView.setItemAnimator(new CustomItemAnimator());

        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}
6.1.4 源码分析

自定义 CustomItemAnimator 继承自 RecyclerView.ItemAnimator,并重写了 animateAddanimateRemoveanimateMoveanimateChange 等方法来处理不同类型的动画。在 runPendingAnimations 方法中,会根据 pendingAdditionspendingRemovals 列表中的 ViewHolder 执行相应的动画。animateAddImplanimateRemoveImpl 方法分别使用 ObjectAnimator 实现了淡入和淡出的动画效果,并在动画开始和结束时调用相应的 dispatch 方法通知 RecyclerView

6.2 Activity 过渡动画

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

6.2.1 使用系统默认过渡动画

在 Android 中,可以通过设置 Activity 的主题来使用系统默认的过渡动画。

xml

java 复制代码
<!-- styles.xml -->
<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- 设置 Activity 过渡动画 -->
        <item name="android:windowActivityTransitions">true</item>
        <item name="android:windowEnterTransition">@android:transition/fade</item>
        <item name="android:windowExitTransition">@android:transition/fade</item>
    </style>
</resources>

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

// 该 Activity 用于演示 Activity 过渡动画
public class MainActivity extends AppCompatActivity {
    private Button buttonOpenActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化按钮
        buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
        buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 启动新的 Activity
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

// 第二个 Activity
public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }
}
6.2.2 源码分析

styles.xml 中,通过设置 android:windowActivityTransitionstrue 开启 Activity 过渡动画,android:windowEnterTransitionandroid:windowExitTransition 分别设置了 Activity 进入和退出时的过渡动画为淡入淡出效果。当在 MainActivity 中启动 SecondActivity 时,系统会自动应用这些过渡动画。

6.2.3 自定义过渡动画

除了使用系统默认的过渡动画,还可以自定义过渡动画。

xml

java 复制代码
<!-- res/transition/slide_transition.xml -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <slide
        android:duration="300"
        android:slideEdge="right" />
</transitionSet>

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.transition.TransitionInflater;
import android.view.View;
import android.widget.Button;

// 该 Activity 用于演示自定义 Activity 过渡动画
public class MainActivity extends AppCompatActivity {
    private Button buttonOpenActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化按钮
        buttonOpenActivity = findViewById(R.id.buttonOpenActivity);
        buttonOpenActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 启动新的 Activity
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);

                // 设置进入过渡动画
                getWindow().setEnterTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));
                // 设置退出过渡动画
                getWindow().setExitTransition(TransitionInflater.from(MainActivity.this).inflateTransition(R.transition.slide_transition));

                startActivity(intent);
            }
        });
    }
}
6.2.4 源码分析

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

七、表现层中的响应式设计

7.1 布局的响应式设计

在不同的屏幕尺寸和方向下,应用的布局需要能够自适应,以提供一致的用户体验。

7.1.1 使用 ConstraintLayout

ConstraintLayout 是 Android 中一个强大的布局管理器,它可以根据约束条件来布局子视图,非常适合实现响应式设计。

xml

java 复制代码
<!-- activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="16dp" />

    <Button
        android:id="@+id/buttonAction"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Action"
        app:layout_constraintTop_toBottomOf="@id/textViewTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
7.1.2 源码分析

在上述布局文件中,使用 ConstraintLayout 来布局 TextViewButton。通过 app:layout_constraintTop_toTopOfapp:layout_constraintStart_toStartOf 等约束条件,将 TextView 固定在布局的顶部中央,Button 固定在 TextView 的下方中央。这样,无论屏幕尺寸和方向如何变化,布局都会自适应调整。

7.1.3 使用不同的布局文件

除了使用 ConstraintLayout,还可以根据不同的屏幕尺寸和方向提供不同的布局文件。

plaintext

java 复制代码
res/
├── layout/
│   └── activity_main.xml
├── layout-sw600dp/
│   └── activity_main.xml
├── layout-land/
│   └── activity_main.xml

layout-sw600dp 目录下的 activity_main.xml 是针对屏幕最小宽度为 600dp 的设备的布局文件,layout-land 目录下的 activity_main.xml 是针对横屏的布局文件。系统会根据设备的屏幕尺寸和方向自动选择合适的布局文件。

7.2 数据的响应式设计

在表现层中,数据的展示也需要根据不同的情况进行响应式设计。例如,在不同的屏幕尺寸下,可能需要展示不同数量的数据项。

java

java 复制代码
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;

// 该 Activity 用于展示用户列表,并实现数据的响应式设计
public class UserListActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private UserAdapter userAdapter;
    private UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        // 初始化 RecyclerView
        recyclerView = findViewById(R.id.recyclerView);

        // 根据屏幕方向设置不同的列数
        int spanCount = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? 2 : 3;
        recyclerView.setLayoutManager(new GridLayoutManager(this, spanCount));

        userAdapter = new UserAdapter(null, new UserAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(User user) {
                // 当 Item 被点击时,显示一个 Toast 消息
                Toast.makeText(UserListActivity.this, "Clicked on " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(userAdapter);

        // 获取 ViewModel 实例
        userViewModel = new ViewModelProvider(this).get(UserViewModel.class);

        // 观察 LiveData 数据的变化
        userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                // 当数据发生变化时,更新 Adapter 中的数据并刷新界面
                userAdapter.setUserList(users);
            }
        });
    }
}
7.2.1 源码分析

UserListActivity 中,根据屏幕的方向(竖屏或横屏)设置 GridLayoutManager 的列数。在竖屏时,列数为 2;在横屏时,列数为 3。这样,在不同的屏幕方向下,RecyclerView 会以不同的布局方式展示数据。

八、表现层中的性能优化

8.1 减少布局嵌套

布局嵌套过多会导致布局的测量和绘制时间增加,影响性能。可以使用 ConstraintLayout 等布局管理器来减少布局嵌套。

xml

java 复制代码
<!-- 优化前的布局 -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Action" />
    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

xml

java 复制代码
<!-- 优化后的布局 -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textViewTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginTop="16dp"
        android:layout_marginStart="16dp" />

    <Button
        android:id="@+id/buttonAction"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Action"
        app:layout_constraintTop_toTopOf="@id/textViewTitle"
        app:layout_constraintStart_toEndOf="@id/textViewTitle"
        android:layout_marginStart="16dp" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/textViewTitle"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginTop="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
8.1.1 源码分析

优化前的布局使用了两层 LinearLayout 嵌套,而优化后的布局使用 ConstraintLayout 直接布局子视图,减少了布局嵌套。这样可以提高布局的测量和绘制效率。

8.2 避免在主线程进行耗时操作

在表现层中,应该避免在主线程进行耗时操作,如网络请求、数据库查询等。可以使用 ViewModelLiveData 结合 CoroutineRxJava 来实现异步操作。

java

java 复制代码
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.Dispatchers;
import kotlinx.coroutines.Job;
import kotlinx.coroutines.launch;

// 该 ViewModel 用于管理用户数据,并使用 Coroutine 进行异步操作
public class UserViewModel extends ViewModel {
    private UserRepository userRepository;
    private MutableLiveData<List<User>> allUsers;
    private Job job;

    // 构造函数,初始化 UserRepository 并获取所有用户数据
    public UserViewModel() {
        userRepository = new UserRepository();
        allUsers = new MutableLiveData<>();
        loadUsers();
    }

    // 获取所有用户数据的 LiveData 对象
    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }

    // 加载用户数据的方法
    private void loadUsers() {
        job = CoroutineScope(Dispatchers.IO).launch {
            // 在 IO 线程中进行数据库查询
            List<User> users = userRepository.getAllUsersSync();
            // 将结果切换到主线程更新 LiveData
            CoroutineScope(Dispatchers.Main).launch {
                allUsers.setValue(users);
            }
        };
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        // 取消协程任务
        if (job != null && job.isActive()) {
            job.cancel();
        }
    }
}
8.2.2 源码分析

UserViewModel 中,使用 Coroutine 进行异步操作。loadUsers 方法在 IO 线程中进行数据库查询,查询完成后,将结果切换到主线程更新 LiveData。这样可以避免在主线程进行耗时的数据库查询,保证 UI 的流畅性。同时,在 ViewModel 销毁时,取消协程任务,避免内存泄漏。

8.3 使用 RecyclerView 的视图缓存

RecyclerView 提供了视图缓存机制,可以复用已经创建的视图,减少视图的创建和销毁次数,提高性能。

java

java 复制代码
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

// 该 Adapter 用于将 User 数据绑定到 RecyclerView 的每个 Item 上
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
    private List<User> userList;

    // 构造函数,接收用户数据列表
    public UserAdapter(List<User> userList) {
        this.userList = userList;
    }

    // 创建 ViewHolder
    @NonNull
    @Override
    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 加载 Item 的布局文件
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
        return new UserViewHolder(view);
    }

    // 绑定数据到 ViewHolder
    @Override
    public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
        // 获取当前位置的用户数据
        User user = userList.get(position);
        // 将用户数据显示在 TextView 上
        holder.textViewName.setText(user.getName());
        holder.textViewAge.setText(String.valueOf(user.getAge()));
    }

    // 获取数据项的数量
    @Override
    public int getItemCount() {
        return userList != null ? userList.size() : 0;
    }

    // 更新数据列表并刷新 Adapter
    public void setUserList(List<User> userList) {
        this.userList = userList;
        notifyDataSetChanged();
    }

    // 定义 ViewHolder 类
    static class UserViewHolder extends RecyclerView.ViewHolder {
        TextView textViewName;
        TextView textViewAge;

        // 构造函数,初始化 ViewHolder 中的视图组件
        UserViewHolder(@NonNull View itemView) {
            super(itemView);
            textViewName = itemView.findViewById(R.id.textViewName);
            textViewAge = itemView.findViewById(R.id.textViewAge);
        }
    }
}
8.3.3 源码分析

UserAdapter 中,RecyclerView 会自动管理视图的缓存。当 RecyclerView 滚动时,会复用已经创建的 ViewHolder,只需要调用 onBindViewHolder 方法更新视图的数据。这样可以避免频繁创建和销毁视图,提高性能。

九、表现层中的无障碍设计

9.1 为视图添加内容描述

java

java 复制代码
// 在 RecyclerView Adapter 中设置内容描述(关键源码)
class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
           .inflate(R.layout.item_user, parent, false);
        
        // 为整个 Item 设置内容描述(辅助功能)
        itemView.setContentDescription("用户项:姓名 ${user.name},年龄 ${user.age}岁");
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        User user = userList.get(position);
        
        // 为姓名 TextView 添加无障碍标签
        holder.nameView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
            @Override
            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
                super.onInitializeAccessibilityNodeInfo(host, info);
                info.setText("用户名:" + user.name);
                info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
        });
    }
}

// Android 框架中 ContentDescription 的处理逻辑(View.java)
public void setContentDescription(CharSequence contentDescription) {
    mContentDescription = contentDescription;
    // 触发无障碍节点更新
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CONTENT_DESCRIPTION_CHANGED);
}

// RecyclerView 辅助功能更新机制(RecyclerView.java)
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(host, info);
    // 自动聚合子项的无障碍信息
    info.setCollectionInfo(CollectionInfo.obtain(
        getAdapter().getItemCount(), 
        getChildCount(), 
        isLayoutRtl()
    ));
}

9.2 动态字体适配(源码实现)

xml

java 复制代码
<!-- 布局文件中启用自动字体大小 -->
<TextView
    android:id="@+id/user_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:autofillHints="@string/hint_name"
    android:textSize="@dimen/font_size_normal"
    android:fontVariationSettings="wdth 100, wght 400"
    tools:text="张三" />

java

java 复制代码
// 系统字体适配核心类(Configuration.java)
public class Configuration {
    public float fontScale; // 字体缩放比例(用户设置)

    // 框架内部处理逻辑(ActivityThread.java)
    private void handleConfigurationChanged(Configuration config) {
        ViewRootImpl[] roots = mRoots.getArray();
        for (ViewRootImpl root : roots) {
            root.setLayoutParams(null, config, null);
        }
        // 触发全局字体更新
        applyOverrideConfiguration(config);
    }
}

// 在 Activity 中监听字体变化
public class UserActivity extends AppCompatActivity {
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // 重新绑定数据触发字体更新
        userViewModel.getAllUsers().observe(this, users -> adapter.setUsers(users));
    }
}

十、Jetpack Compose 与 Room 的深度集成

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

kotlin

java 复制代码
// Compose 界面组件
@Composable
fun UserListScreen(viewModel: UserViewModel = viewModel()) {
    val users by viewModel.users.collectAsState(emptyList())
    val context = LocalContext.current

    RecyclerView(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        items(users) { user ->
            UserItem(
                user = user,
                onClick = { viewModel.onUserClick(user) }
            )
        }
    }

    // 加载状态处理
    if (viewModel.isLoading.value) {
        CircularProgressIndicator(modifier = Modifier.centerInParent())
    }
}

// ViewModel 中的 State 管理
class UserViewModel : ViewModel() {
    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users = _users.asStateFlow()
    
    private val _isLoading = MutableStateFlow(false)
    val isLoading = _isLoading.asStateFlow()

    init {
        loadUsers()
    }

    private fun loadUsers() {
        viewModelScope.launch {
            _isLoading.value = true
            try {
                val users = userRepository.getAllUsers()
                _users.value = users
            } finally {
                _isLoading.value = false
            }
        }
    }
}

// Room 协程支持(Compose 专用扩展)
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsersFlow(): Flow<List<User>> // 直接返回 Flow
}

10.2 Compose 与 LiveData 的互操作

kotlin

java 复制代码
// LiveData 转 Compose State
@Composable
fun <T> LiveData<T>.asComposeState(
    context: Context = LocalContext.current,
    initialValue: T
): State<T> {
    val state = remember { mutableStateOf(initialValue) }
    val lifecycleOwner = rememberUpdatedState(context as LifecycleOwner)
    
    DisposableEffect(this) {
        val observer = Observer<T> { state.value = it }
        observe(lifecycleOwner.value, observer)
        onDispose { removeObserver(observer) }
    }
    return state
}

// 使用示例
val users = userViewModel.allUsers.asComposeState(emptyList())

十一、表现层性能优化深度解析

11.1 RecyclerView 预布局优化(源码级)

java

java 复制代码
// 自定义 RecyclerView(优化预布局)
class OptimizedRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : RecyclerView(context, attrs, defStyle) {

    override fun onMeasure(widthSpec: Int, heightSpec: Int) {
        // 禁用预布局(针对固定高度列表)
        setHasFixedSize(true);
        super.onMeasure(widthSpec, heightSpec);
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        // 跳过不必要的布局计算
        if (!changed) return;
        super.onLayout(changed, l, t, r, b);
    }
}

// 框架预布局逻辑(RecyclerView.java)
void processLayout(Recycler recycler, State state) {
    if (mState.mRunPredictiveAnimations) {
        // 预布局用于动画计算
        performPredictiveLayout(recycler, state);
    }
    // 正式布局
    performLayout(recycler, state);
}

11.2 数据变更的细粒度更新(DiffUtil 源码)

java

java 复制代码
// Adapter 中的 DiffUtil 实现
class UserAdapter : ListAdapter<User, UserAdapter.ViewHolder>(USER_DIFF_CALLBACK) {
    companion object {
        val USER_DIFF_CALLBACK = object : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id // 基于唯一标识判断
            }

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem // 基于内容比较
            }
        }
    }

    // 框架内部 Diff 计算(DiffUtil.java)
    public static DiffResult calculateDiff(Callback callback) {
        return new DiffUtil(callback).calculate();
    }

    // 差异分发(RecyclerView.java)
    public void dispatchUpdateRanges(List<UpdateOp> ops) {
        mAdapterHelper.calculateDiff(ops);
        // 触发局部刷新
        for (UpdateOp op : ops) {
            dispatchSingleUpdate(op);
        }
    }
}

十二、表现层与 Room 的生命周期协同

12.1 ViewModel 与 Room 的绑定

java

java 复制代码
// ViewModel 中的 Room 初始化(关键源码)
class UserViewModel(application: Application) : AndroidViewModel(application) {
    private val database by lazy {
        AppDatabase.getInstance(application) // 生命周期感知的数据库实例
    }

    val users: LiveData<List<User>> = database.userDao().getAllUsers()

    override fun onCleared() {
        super.onCleared()
        // 释放数据库资源(可选)
        database.close()
    }
}

// 数据库单例实现(AppDatabase.java)
public class AppDatabase {
    private static volatile AppDatabase INSTANCE;

    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class, "user.db"
                    ).addCallback(new Callback() {
                        @Override
                        public void onCreate(@NonNull SupportSQLiteDatabase db) {
                            // 初始化数据(可选)
                        }
                    }).build();
                }
            }
        }
        return INSTANCE;
    }
}

12.2 LiveData 的生命周期安全(源码解析)

java

java 复制代码
// LiveData observe 方法(LiveData.java)
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    // 检查生命周期状态
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer");
    }
    owner.getLifecycle().addObserver(wrapper);
}

// 生命周期事件处理(LifecycleBoundObserver.java)
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
    if (source.getLifecycle().getCurrentState() == DESTROYED) {
        removeObserver(mObserver);
        return;
    }
    activeStateChanged(shouldBeActive());
}

十三、表现层单元测试(源码级验证)

13.1 纯 UI 测试(不带 Room)

java

java 复制代码
@RunWith(AndroidJUnit4::class)
public class UserAdapterTest {
    private UserAdapter adapter;

    @Before
    public void setup() {
        adapter = new UserAdapter(Collections.emptyList());
    }

    @Test
    public void testViewHolderBinding() {
        // 创建测试 View
        View itemView = LayoutInflater.from(ApplicationProvider.getApplicationContext())
           .inflate(R.layout.item_user, null);
        UserAdapter.ViewHolder holder = new UserAdapter.ViewHolder(itemView);
        
        // 绑定数据
        User user = new User(1, "张三", 25);
        adapter.onBindViewHolder(holder, 0);
        
        // 验证 UI 显示
        assertEquals("张三", holder.nameView.getText());
        assertEquals("25", holder.ageView.getText());
    }

    @Test
    public void testDiffUtil() {
        User oldUser = new User(1, "张三", 25);
        User newUser = new User(1, "张三", 26);
        
        // 测试内容变更
        assertEquals(false, USER_DIFF_CALLBACK.areContentsTheSame(oldUser, newUser));
    }
}

13.2 集成测试(结合 Room 测试库)

java

java 复制代码
@RunWith(AndroidJUnit4::class)
public class UserActivityTest {
    private final UserDao dao = Room.inMemoryDatabaseBuilder(
        ApplicationProvider.getApplicationContext(),
        AppDatabase.class
    ).allowMainThreadQueries().build().userDao();

    @Test
    public void testUserListUpdate() {
        // 插入测试数据
        dao.insert(new User(1, "李四", 30));
        
        // 启动 Activity
        ActivityScenario.launch(UserActivity.class);
        
        // 验证列表显示
        onView(withText("李四")).check(matches(isDisplayed()));
    }

    @Test
    public void testAddUserFlow() {
        // 启动添加用户 Activity
        ActivityScenario.launch(AddUserActivity.class);
        
        // 模拟输入
        onView(withId(R.id.edit_name)).perform(typeText("王五"));
        onView(withId(R.id.edit_age)).perform(typeText("28"));
        onView(withId(R.id.btn_add)).perform(click());
        
        // 验证列表更新
        onView(withText("王五")).check(matches(isDisplayed()));
    }
}

十四、表现层设计模式实战

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

java

java 复制代码
// UI 状态枚举
enum UiState {
    LOADING,
    CONTENT,
    ERROR
}

// Activity 中的状态管理
public class UserActivity extends AppCompatActivity {
    private UiState currentState = UiState.LOADING;

    private void updateState(UiState newState) {
        currentState = newState;
        runOnUiThread(() -> {
            switch (newState) {
                case LOADING:
                    showLoading();
                    hideContent();
                    hideError();
                    break;
                case CONTENT:
                    hideLoading();
                    showContent();
                    hideError();
                    break;
                case ERROR:
                    hideLoading();
                    hideContent();
                    showError();
                    break;
            }
        });
    }

    // 框架内部的状态更新(ActivityThread.java)
    public void handleResumeActivity(IBinder token, boolean finalStateRequest) {
        // 恢复 Activity 状态
        performResumeActivity(token, finalStateRequest);
        // 触发 UI 状态更新
        mMainThreadHandler.obtainMessage(H.RESUME_ACTIVITY, token).sendToTarget();
    }
}

14.2 策略模式(UI 样式切换)

java

java 复制代码
// 主题策略接口
interface ThemeStrategy {
    int getBackgroundColor();
    int getTextColor();
}

// 浅色主题实现
class LightThemeStrategy implements ThemeStrategy {
    @Override
    public int getBackgroundColor() {
        return Color.WHITE;
    }

    @Override
    public int getTextColor() {
        return Color.BLACK;
    }
}

// Activity 中的策略应用
public class UserActivity extends AppCompatActivity {
    private ThemeStrategy themeStrategy = new LightThemeStrategy();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
        
        // 应用主题策略
        userList.setBackgroundColor(themeStrategy.getBackgroundColor());
        titleView.setTextColor(themeStrategy.getTextColor());
    }

    // 动态切换主题
    public void switchToDarkTheme() {
        themeStrategy = new DarkThemeStrategy();
        recreate();
    }
}

十五、表现层异常处理最佳实践

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

java

java 复制代码
// 全局异常处理器
public class AppExceptionHandler implements Thread.UncaughtExceptionHandler {
    private final Thread.UncaughtExceptionHandler defaultHandler;
    private final Context context;

    public AppExceptionHandler(Context context) {
        this.context = context;
        defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (ex instanceof SQLiteException) {
            // 处理 Room 数据库异常
            showDatabaseError(ex);
            return;
        }
        defaultHandler.uncaughtException(thread, ex);
    }

    private void showDatabaseError(Throwable ex) {
        new AlertDialog.Builder(context)
           .setTitle("数据库错误")
           .setMessage("数据加载失败:" + ex.getMessage())
           .setPositiveButton("重试", (dialog, which) -> recreate())
           .show();
    }
}

// 在 Application 中注册
public class AppApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Thread.setDefaultUncaughtExceptionHandler(new AppExceptionHandler(this));
    }
}

15.2 Room 异常转换(表现层适配)

java

java 复制代码
// 在 Repository 中封装异常
class UserRepository {
    public LiveData<List<User>> getUsers() {
        return Transformations.map(dao.getAllUsers(), users -> {
            try {
                return users;
            } catch (SQLiteException e) {
                // 转换为表现层可识别的异常
                throw new UiDataException("数据库查询失败", e);
            }
        });
    }
}

// Activity 中的异常处理
userViewModel.users.observe(this, users -> {
    if (users instanceof UiDataException) {
        showError((UiDataException) users);
        return;
    }
    updateUI(users);
});

十六、总结:表现层的 Room 集成哲学

16.1 源码架构总览

plaintext

java 复制代码
表现层
├─ Activity/Fragment (UI 宿主)
│  ├─ ViewModel (数据与逻辑)
│  └─ LiveData/State (数据订阅)
├─ RecyclerView/Compose (数据展示)
│  ├─ Adapter (数据绑定)
│  └─ DiffUtil (增量更新)
├─ DataBinding (视图绑定)
└─ 无障碍/动画/性能 (体验优化)

Room 依赖
├─ DAO (数据访问接口)
├─ LiveData/Flow (数据订阅源)
└─ TypeConverter (数据转换)

16.2 核心设计原则

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

16.3 性能优化清单

优化点 实现方式 源码位置
列表更新 DiffUtil + RecyclerView 局部刷新 ListAdapter.java
数据订阅 LiveData 自动生命周期绑定 LifecycleBoundObserver.java
布局性能 DataBinding 替代 findViewById DataBinderMapperImpl.java
内存管理 ViewModel 绑定 Room 单例 ViewModelStore.java
动画优化 默认 ItemAnimator + 自定义过渡 DefaultItemAnimator.java

16.4 反模式规避

  1. ❌ 在 Activity 直接操作 Room DAO(紧耦合)
    ✅ 通过 ViewModel 间接访问(参考 UserViewModel 源码)
  2. ❌ 忽略 DiffUtil 导致全量刷新(性能损耗)
    ✅ 使用 ListAdapter 强制差分更新(源码强制实现 getItemId
  3. ❌ 在 UI 线程执行 Room 查询(ANR 风险)
    ✅ 通过 LiveData / 协程自动切换线程(RoomDatabase 内部检查)
  4. ❌ 复杂布局嵌套(过度绘制)
    ✅ 使用 ConstraintLayout + 扁平化布局(减少层级)
  5. ❌ 内存泄漏(未取消订阅)
    ✅ LiveData 自动解绑(LifecycleOwner 机制)
相关推荐
雨白2 小时前
开发 SunnyWeather:Android 天气预报 App(下)
android
_extraordinary_3 小时前
Java 字符串常量池 +反射,枚举和lambda表达式
android·java·开发语言
alexhilton3 小时前
学会说不!让你彻底学会Kotlin Flow的取消机制
android·kotlin·android jetpack
来来走走3 小时前
Flutter dart运算符
android·前端·flutter
青小莫4 小时前
IDM下载失败常见原因
android
阿华的代码王国4 小时前
【Android】日期选择器
android·xml·java·前端·后端
小墙程序员6 小时前
Android 性能优化(五)Heap Dump 的使用
android·性能优化
阿华的代码王国6 小时前
【Android】RecyclerView实现新闻列表布局(1)适配器使用相关问题
android·xml·java·前端·后端
EngZegNgi6 小时前
Unity —— Android 应用构建与发布
android·unity·自动化·游戏引擎·构建
fatiaozhang95276 小时前
烽火HG680-KX-海思MV320芯片-2+8G-安卓9.0-强刷卡刷固件包
android·电视盒子·刷机固件·机顶盒刷机