重构Android刷新组件:SmartRefreshLayout与Clean Architecture的完美融合
痛点直击:你的刷新组件是否正在污染业务逻辑?
你是否遇到过这样的困境:精心设计的MVVM架构中,刷新状态管理却散落各地?RecyclerView的滚动监听与ViewModel纠缠不清?数据加载逻辑与UI刷新混杂在Activity/Fragment中?当项目复杂度提升时,这些问题会导致代码可维护性急剧下降,单元测试覆盖率难以提升。本文将展示如何通过Clean Architecture(整洁架构)思想重构SmartRefreshLayout的集成方式,实现UI组件与业务逻辑的彻底解耦。
读完本文你将获得:
- 一套基于Clean Architecture的刷新组件集成方案
- 业务逻辑与UI组件的解耦实践
- 可复用的刷新状态管理组件
- 完整的单元测试实现指南
- 解决内存泄漏与状态一致性问题的最佳实践
架构解析:SmartRefreshLayout的设计局限
SmartRefreshLayout核心类结构
SmartRefreshLayout作为Android生态中最流行的下拉刷新框架之一,其设计充分考虑了灵活性和扩展性,但原生集成方式仍存在一定局限:
传统集成方式的问题
传统使用方式通常在Activity/Fragment中直接操作SmartRefreshLayout:
// 传统实现方式(存在架构问题)
public class NewsListActivity extends AppCompatActivity implements OnRefreshListener, OnLoadMoreListener {
private SmartRefreshLayout mRefreshLayout;
private NewsViewModel mViewModel;
private NewsAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news_list);
mRefreshLayout = findViewById(R.id.refreshLayout);
mRefreshLayout.setOnRefreshListener(this);
mRefreshLayout.setOnLoadMoreListener(this);
mViewModel = ViewModelProviders.of(this).get(NewsViewModel.class);
mViewModel.getNewsList().observe(this, newsList -> {
mAdapter.setData(newsList);
mRefreshLayout.finishRefresh();
mRefreshLayout.finishLoadMore();
});
mRefreshLayout.autoRefresh();
}
@Override
public void onRefresh(RefreshLayout refreshlayout) {
mViewModel.refreshNews();
}
@Override
public void onLoadMore(RefreshLayout refreshlayout) {
mViewModel.loadMoreNews();
}
}
这种实现存在以下架构问题:
- 职责混乱:Activity/Fragment既处理UI渲染,又处理业务逻辑和数据加载
- 紧耦合:UI组件与业务逻辑直接绑定,难以独立测试
- 状态管理分散:刷新状态分布在View和ViewModel中,易导致不一致
- 复用困难:相同的刷新逻辑难以在不同界面复用
架构重构:Clean Architecture视角下的刷新组件设计
Clean Architecture核心思想
Clean Architecture强调依赖规则:内层不依赖外层,所有依赖指向内部。我们将基于此思想重构刷新组件的集成方式:
核心组件设计
1. 领域层:定义刷新状态与用例接口
RefreshState.java - 统一的刷新状态模型:
// 领域层:刷新状态模型
public sealed class RefreshState {
public object None : RefreshState()
public object Refreshing : RefreshState()
public data class RefreshSuccess(val hasMore: Boolean) : RefreshState()
public data class RefreshFailed(val error: Throwable) : RefreshState()
public object LoadingMore : RefreshState()
public data class LoadMoreSuccess(val hasMore: Boolean) : RefreshState()
public data class LoadMoreFailed(val error: Throwable) : RefreshState()
}
PagedDataUseCase.kt - 分页数据用例接口:
// 领域层:分页数据用例接口
interface PagedDataUseCase<Param, Data> {
// 刷新数据
suspend fun refresh(param: Param): Result<List<Data>>
// 加载更多
suspend fun loadMore(param: Param): Result<List<Data>>
// 获取数据流
fun getRefreshStateFlow(): Flow<RefreshState>
// 获取数据列表
fun getDataFlow(): Flow<List<Data>>
}
2. 数据层:实现用例接口
NewsPagedUseCase.kt - 新闻分页用例实现:
// 数据层:用例实现
class NewsPagedUseCase @Inject constructor(
private val newsRepository: NewsRepository
) : PagedDataUseCase<NewsParam, News> {
private val _refreshState = MutableStateFlow<RefreshState>(RefreshState.None)
private val _data = MutableStateFlow<List<News>>(emptyList())
private var currentPage = 1
override fun getRefreshStateFlow(): Flow<RefreshState> = _refreshState
override fun getDataFlow(): Flow<List<News>> = _data
override suspend fun refresh(param: NewsParam): Result<List<News>> {
_refreshState.value = RefreshState.Refreshing
return try {
currentPage = 1
val result = newsRepository.getNews(param.category, currentPage)
result.fold(
onSuccess = { newsList ->
_data.value = newsList
_refreshState.value = RefreshState.RefreshSuccess(newsList.size >= param.pageSize)
Result.success(newsList)
},
onFailure = { error ->
_refreshState.value = RefreshState.RefreshFailed(error)
Result.failure(error)
}
)
} catch (e: Exception) {
_refreshState.value = RefreshState.RefreshFailed(e)
Result.failure(e)
}
}
override suspend fun loadMore(param: NewsParam): Result<List<News>> {
_refreshState.value = RefreshState.LoadingMore
return try {
currentPage++
val result = newsRepository.getNews(param.category, currentPage)
result.fold(
onSuccess = { newsList ->
val newList = _data.value.toMutableList().apply { addAll(newsList) }
_data.value = newList
_refreshState.value = RefreshState.LoadMoreSuccess(newsList.size >= param.pageSize)
Result.success(newsList)
},
onFailure = { error ->
currentPage-- // 加载失败回滚页码
_refreshState.value = RefreshState.LoadMoreFailed(error)
Result.failure(error)
}
)
} catch (e: Exception) {
currentPage--
_refreshState.value = RefreshState.LoadMoreFailed(e)
Result.failure(e)
}
}
}
3. 表现层:实现刷新状态管理器
RefreshManager.java - 刷新状态管理组件:
// 表现层:刷新状态管理器
public class RefreshManager {
private final SmartRefreshLayout refreshLayout;
private final CoroutineScope scope;
private final MutableStateFlow<RefreshState> stateFlow = new MutableStateFlow<>(RefreshState.None);
public RefreshManager(SmartRefreshLayout refreshLayout, CoroutineScope scope) {
this.refreshLayout = refreshLayout;
this.scope = scope;
setupRefreshLayout();
}
private void setupRefreshLayout() {
// 配置默认Header和Footer
refreshLayout.setRefreshHeader(new BezierRadarHeader(refreshLayout.getContext()));
refreshLayout.setRefreshFooter(new BallPulseFooter(refreshLayout.getContext()));
// 设置刷新监听器
refreshLayout.setOnRefreshListener(layout -> {
stateFlow.tryEmit(RefreshState.Refreshing);
onRefreshListener?.onRefresh();
});
refreshLayout.setOnLoadMoreListener(layout -> {
stateFlow.tryEmit(RefreshState.LoadingMore);
onLoadMoreListener?.onLoadMore();
});
// 观察状态变化
scope.launch {
stateFlow.collect(this::handleRefreshState);
}
}
private void handleRefreshState(RefreshState state) {
switch (state) {
case Refreshing:
if (!refreshLayout.isRefreshing) {
refreshLayout.autoRefresh();
}
break;
case RefreshSuccess:
refreshLayout.finishRefresh(true);
refreshLayout.setNoMoreData(!((RefreshSuccess) state).hasMore);
break;
case RefreshFailed:
refreshLayout.finishRefresh(false);
break;
case LoadingMore:
if (!refreshLayout.isLoading) {
refreshLayout.autoLoadMore();
}
break;
case LoadMoreSuccess:
refreshLayout.finishLoadMore(true);
refreshLayout.setNoMoreData(!((LoadMoreSuccess) state).hasMore);
break;
case LoadMoreFailed:
refreshLayout.finishLoadMore(false);
break;
case None:
// 重置状态
break;
}
}
// 对外暴露的状态更新方法
public void updateState(RefreshState state) {
stateFlow.tryEmit(state);
}
// 刷新和加载监听器
private OnRefreshListener onRefreshListener;
private OnLoadMoreListener onLoadMoreListener;
public void setOnRefreshListener(OnRefreshListener listener) {
this.onRefreshListener = listener;
}
public void setOnLoadMoreListener(OnLoadMoreListener listener) {
this.onLoadMoreListener = listener;
}
public interface OnRefreshListener {
void onRefresh();
}
public interface OnLoadMoreListener {
void onLoadMore();
}
}
完整架构实现
ViewModel实现
// ViewModel实现
class NewsViewModel @ViewModelInject constructor(
private val newsUseCase: NewsPagedUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<NewsUiState>(NewsUiState.Loading)
val uiState: StateFlow<NewsUiState> = _uiState
private val param = NewsParam("technology", 20)
init {
// 观察用例的数据和状态变化
viewModelScope.launch {
combine(
newsUseCase.getDataFlow(),
newsUseCase.getRefreshStateFlow()
) { data, refreshState ->
NewsUiState.Success(data, refreshState)
}.collect {
_uiState.value = it
}
}
// 初始加载数据
refreshNews()
}
fun refreshNews() {
viewModelScope.launch {
newsUseCase.refresh(param)
}
}
fun loadMoreNews() {
viewModelScope.launch {
newsUseCase.loadMore(param)
}
}
}
// UI状态密封类
sealed class NewsUiState {
object Loading : NewsUiState()
data class Success(
val newsList: List<News>,
val refreshState: RefreshState
) : NewsUiState()
data class Error(val exception: Throwable) : NewsUiState()
}
Activity实现
// Activity实现
class NewsListActivity : AppCompatActivity() {
private lateinit var binding: ActivityNewsListBinding
private lateinit var viewModel: NewsViewModel
private lateinit var refreshManager: RefreshManager
private val newsAdapter = NewsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNewsListBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化ViewModel
viewModel = ViewModelProvider(this)[NewsViewModel::class.java]
// 初始化刷新管理器
refreshManager = RefreshManager(binding.refreshLayout, lifecycleScope).apply {
setOnRefreshListener { viewModel.refreshNews() }
setOnLoadMoreListener { viewModel.loadMoreNews() }
}
// 设置RecyclerView
binding.recyclerView.adapter = newsAdapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
// 观察UI状态变化
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is NewsUiState.Loading -> {
// 显示加载中UI
}
is NewsUiState.Success -> {
newsAdapter.submitList(state.newsList)
refreshManager.updateState(state.refreshState)
}
is NewsUiState.Error -> {
// 显示错误UI
Toast.makeText(this@NewsListActivity,
"加载失败: ${state.exception.message}",
Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
布局文件
<!-- activity_news_list.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</layout>
关键技术点解析
状态驱动设计
通过统一的RefreshState管理所有刷新相关状态,实现单向数据流:
依赖注入实现
使用Hilt实现依赖注入,解耦组件依赖:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
fun provideNewsRepository(): NewsRepository {
return NewsRepositoryImpl(NewsApiService.create())
}
@Provides
fun provideNewsUseCase(repository: NewsRepository): NewsPagedUseCase {
return NewsPagedUseCase(repository)
}
}
单元测试实现
由于业务逻辑已从UI中剥离,我们可以轻松为ViewModel和用例编写单元测试:
@RunWith(JUnit4::class)
class NewsViewModelTest {
@get:Rule
val coroutineRule = MainCoroutineRule()
@Mock
private lateinit var mockNewsUseCase: NewsPagedUseCase
private lateinit var viewModel: NewsViewModel
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
// 模拟用例返回数据
runBlocking {
`when`(mockNewsUseCase.getDataFlow()).thenReturn(flowOf(fakeNewsList))
`when`(mockNewsUseCase.getRefreshStateFlow()).thenReturn(flowOf(RefreshState.None))
}
viewModel = NewsViewModel(mockNewsUseCase)
}
@Test
fun `refreshNews updates state correctly`() = runBlocking {
// Arrange
val testNews = listOf(News("1", "Test Title", "Test Content"))
`when`(mockNewsUseCase.refresh(any())).thenReturn(Result.success(testNews))
// Act
viewModel.refreshNews()
// Assert
verify(mockNewsUseCase).refresh(any())
}
companion object {
private val fakeNewsList = listOf(
News("1", "Title 1", "Content 1"),
News("2", "Title 2", "Content 2")
)
}
}
性能优化与最佳实践
避免内存泄漏
- 正确管理生命周期:使用
lifecycleScope确保协程随生命周期结束而取消 - 弱引用持有:RefreshManager不持有Activity/Fragment的强引用
- 及时移除监听器:在onDestroy中清理监听器
// 在Activity的onDestroy中清理
override fun onDestroy() {
super.onDestroy()
refreshManager.setOnRefreshListener(null)
refreshManager.setOnLoadMoreListener(null)
}
状态防抖动处理
防止快速重复触发刷新/加载:
// 在RefreshManager中添加防抖动
private var isRefreshing = false
private var isLoadingMore = false
private void setupRefreshLayout() {
refreshLayout.setOnRefreshListener(layout -> {
if (!isRefreshing) {
isRefreshing = true;
stateFlow.tryEmit(RefreshState.Refreshing);
onRefreshListener?.onRefresh();
}
});
// 状态恢复时重置标志
scope.launch {
stateFlow.collect { state ->
if (state !is RefreshState.Refreshing) {
isRefreshing = false;
}
if (state !is RefreshState.LoadingMore) {
isLoadingMore = false;
}
}
}
}
自定义刷新视图
SmartRefreshLayout支持高度自定义的Header和Footer,可根据产品需求定制:
public class CustomRefreshHeader extends LinearLayout implements RefreshHeader {
private ProgressBar progressBar;
private TextView textView;
public CustomRefreshHeader(Context context) {
super(context);
initView();
}
private void initView() {
LayoutInflater.from(getContext()).inflate(R.layout.custom_refresh_header, this);
progressBar = findViewById(R.id.progress_bar);
textView = findViewById(R.id.text_view);
}
@Override
public void onRefreshBegin(RefreshLayout layout) {
progressBar.setVisibility(VISIBLE);
textView.setText("正在刷新...");
}
@Override
public int onFinish(RefreshLayout layout, boolean success) {
progressBar.setVisibility(GONE);
textView.setText(success ? "刷新成功" : "刷新失败");
return 500; // 延迟500毫秒后关闭
}
// 实现其他必要方法...
}
总结与展望
通过将SmartRefreshLayout与Clean Architecture结合,我们实现了:
- 关注点分离:UI组件、业务逻辑、数据处理清晰分离
- 可测试性:各层可独立测试,提高代码质量
- 可维护性:模块化设计使代码更易于理解和修改
- 可复用性:刷新逻辑可在不同界面复用
进一步优化方向
- 实现刷新状态持久化:使用Room保存刷新状态,支持应用重启后恢复
- 添加刷新拦截与重试机制:统一处理网络错误和重试逻辑
- 支持多种刷新样式动态切换:根据不同场景切换合适的刷新样式
这套架构不仅适用于SmartRefreshLayout,也可推广到其他UI组件的集成,是构建健壮Android应用的有效实践。通过遵循Clean Architecture原则,我们能够编写出更清晰、更可维护的代码,从容应对复杂应用的挑战。
代码获取:通过以下命令获取完整示例代码
git clone https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout
希望本文提供的架构思路能帮助你解决项目中的刷新组件问题。如果你有更好的实践或疑问,欢迎在评论区交流讨论。别忘了点赞、收藏、关注,获取更多Android架构实践内容!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



