重构Android刷新组件:SmartRefreshLayout与Clean Architecture的完美融合

重构Android刷新组件:SmartRefreshLayout与Clean Architecture的完美融合

【免费下载链接】SmartRefreshLayout 🔥下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScroll,Android智能下拉刷新框架,支持越界回弹、越界拖动,具有极强的扩展性,集成了几十种炫酷的Header和 Footer。 【免费下载链接】SmartRefreshLayout 项目地址: https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout

痛点直击:你的刷新组件是否正在污染业务逻辑?

你是否遇到过这样的困境:精心设计的MVVM架构中,刷新状态管理却散落各地?RecyclerView的滚动监听与ViewModel纠缠不清?数据加载逻辑与UI刷新混杂在Activity/Fragment中?当项目复杂度提升时,这些问题会导致代码可维护性急剧下降,单元测试覆盖率难以提升。本文将展示如何通过Clean Architecture(整洁架构)思想重构SmartRefreshLayout的集成方式,实现UI组件与业务逻辑的彻底解耦。

读完本文你将获得:

  • 一套基于Clean Architecture的刷新组件集成方案
  • 业务逻辑与UI组件的解耦实践
  • 可复用的刷新状态管理组件
  • 完整的单元测试实现指南
  • 解决内存泄漏与状态一致性问题的最佳实践

架构解析:SmartRefreshLayout的设计局限

SmartRefreshLayout核心类结构

SmartRefreshLayout作为Android生态中最流行的下拉刷新框架之一,其设计充分考虑了灵活性和扩展性,但原生集成方式仍存在一定局限:

mermaid

传统集成方式的问题

传统使用方式通常在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();
    }
}

这种实现存在以下架构问题:

  1. 职责混乱:Activity/Fragment既处理UI渲染,又处理业务逻辑和数据加载
  2. 紧耦合:UI组件与业务逻辑直接绑定,难以独立测试
  3. 状态管理分散:刷新状态分布在View和ViewModel中,易导致不一致
  4. 复用困难:相同的刷新逻辑难以在不同界面复用

架构重构:Clean Architecture视角下的刷新组件设计

Clean Architecture核心思想

Clean Architecture强调依赖规则:内层不依赖外层,所有依赖指向内部。我们将基于此思想重构刷新组件的集成方式:

mermaid

核心组件设计

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管理所有刷新相关状态,实现单向数据流:

mermaid

依赖注入实现

使用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")
        )
    }
}

性能优化与最佳实践

避免内存泄漏

  1. 正确管理生命周期:使用lifecycleScope确保协程随生命周期结束而取消
  2. 弱引用持有:RefreshManager不持有Activity/Fragment的强引用
  3. 及时移除监听器:在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结合,我们实现了:

  1. 关注点分离:UI组件、业务逻辑、数据处理清晰分离
  2. 可测试性:各层可独立测试,提高代码质量
  3. 可维护性:模块化设计使代码更易于理解和修改
  4. 可复用性:刷新逻辑可在不同界面复用

进一步优化方向

  1. 实现刷新状态持久化:使用Room保存刷新状态,支持应用重启后恢复
  2. 添加刷新拦截与重试机制:统一处理网络错误和重试逻辑
  3. 支持多种刷新样式动态切换:根据不同场景切换合适的刷新样式

这套架构不仅适用于SmartRefreshLayout,也可推广到其他UI组件的集成,是构建健壮Android应用的有效实践。通过遵循Clean Architecture原则,我们能够编写出更清晰、更可维护的代码,从容应对复杂应用的挑战。

代码获取:通过以下命令获取完整示例代码

git clone https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout

希望本文提供的架构思路能帮助你解决项目中的刷新组件问题。如果你有更好的实践或疑问,欢迎在评论区交流讨论。别忘了点赞、收藏、关注,获取更多Android架构实践内容!

【免费下载链接】SmartRefreshLayout 🔥下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScroll,Android智能下拉刷新框架,支持越界回弹、越界拖动,具有极强的扩展性,集成了几十种炫酷的Header和 Footer。 【免费下载链接】SmartRefreshLayout 项目地址: https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值