当前位置:首页 > 技术 > 正文内容

Android 清洁架构开源项目指南:打造可维护的应用

访客 技术 2026年9月14日 12

Android 清洁架构开源项目指南:打造可维护的应用

许多开发者在开发过程中会遇到这样的问题:随着应用功能的增加,代码变得混乱且难以维护,新功能的开发也变得困难重重。本文将介绍如何利用Android清洁架构来解决这些问题。

清洁架构简介

清洁架构(Clean Architecture)是由Robert C. Martin提出的架构模式,旨在通过分层设计实现代码的解耦,提高代码的可维护性和可测试性。

架构分层

架构分层图

各层职责

层级 职责 关键技术组件
表现层(Presentation Layer) 用户界面展示与交互处理 Activity, Fragment, Presenter, ViewModel
领域层(Domain Layer) 核心业务逻辑 UseCase, Interactor, Entity, Repository接口
数据层(Data Layer) 数据获取与持久化 Repository实现, DataSource, API, Database

项目结构分析

领域层:业务核心


public class UserProfile {
    private final int profileId;
    private String name;
    private String email;
    private String bio;
    
    public UserProfile(int profileId) {
        this.profileId = profileId;
    }
    
    public int getId() { return profileId; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // 更多getter/setter...
}

用例层:业务逻辑封装


public abstract class BusinessOperation {
    private final Executor backgroundExecutor;
    private final MainThread mainThread;
    private final CompositeDisposable disposables;

    protected abstract Observable<T> createObservable(Params params);

    public void execute(Observer<T> observer, Params params) {
        Preconditions.checkNotNull(observer);
        final Observable<T> observable = createObservable(params)
            .subscribeOn(Schedulers.from(backgroundExecutor))
            .observeOn(mainThread.getScheduler());
        addDisposable(observable.subscribeWith(observer));
    }

    public void dispose() {
        if (!disposables.isDisposed()) {
            disposables.dispose();
        }
    }
}

数据层:数据管理


public class ProfileDataRepo implements ProfileRepository {
    private final ProfileDataSourceFactory dataSourceFactory;
    private final ProfileEntityMapper entityMapper;

    @Override
    public Observable fetchProfiles() {
        final ProfileDataSource dataSource = dataSourceFactory.createCloudDataSource();
        return dataSource.fetchProfileEntities()
            .map(entities -> entityMapper.mapToProfiles(entities));
    }
}

表现层:界面展示


public class ProfileListPresenter implements Presenter {
    private ProfileListView profileView;
    private final FetchProfiles fetchProfilesUseCase;
    private final ProfileDataMapper dataMapper;

    public void start() {
        loadProfiles();
    }

    private void loadProfiles() {
        hideRetryView();
        showLoading();
        fetchProfiles();
    }

    private void fetchProfiles() {
        fetchProfilesUseCase.execute(new ProfilesObserver(), null);
    }

    private final class ProfilesObserver extends DefaultObserver {
        @Override public void onNext(List<UserProfile> profiles) {
            ProfileListPresenter.this.displayProfiles(profiles);
        }
        
        @Override public void onError(Throwable e) {
            ProfileListPresenter.this.showError(new ErrorBundle((Exception) e));
            ProfileListPresenter.this.showRetry();
        }
    }
}

依赖注入

本项目采用Dagger 2实现依赖注入:


@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    void inject(MyApplication app);
    Context appContext();
    Executor threadExecutor();
    MainThread mainThread();
    ProfileRepository profileRepository();
}

@PerActivity
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComp {
    void inject(ProfileListActivity activity);
    void inject(ProfileDetailActivity activity);
}

测试方法

领域层测试


public class FetchProfilesTest {
    private FetchProfiles fetchProfiles;
    private MockProfileRepository mockRepo;
    private MockMainThread mockMainThread;
    private MockExecutor mockExecutor;

    @Test
    public void testFetchProfilesObservable() {
        fetchProfiles.createObservable(null).test().assertValue(PROFILE_LIST);
    }
}

表现层测试


public class ProfileListPresenterTest {
    @Mock private ProfileListView mockView;
    @Mock private FetchProfiles mockFetchProfiles;
    @Mock private ProfileDataMapper mockMapper;
    
    private ProfileListPresenter presenter;

    @Test
    public void testPresenterStart() {
        presenter.start();
        verify(mockView).showLoading();
        verify(mockFetchProfiles).execute(any(DefaultObserver.class), eq(null));
    }
}

开发指南

环境设置


# 克隆仓库
git clone https://gitcode.com/gh_mirrors/an/Android-CleanArchitecture

# 构建项目
./gradlew assembleDebug

# 运行单元测试
./gradlew test

# 运行验收测试
./gradlew connectedCheck

添加新功能

  1. 领域层:定义实体和用例
  2. 数据层:实现仓库和数据源
  3. 表现层:创建表示器和视图
  4. 配置依赖注入:更新Dagger模块

编码建议

  • 采用清晰的包结构
  • 遵循单一职责原则
  • 编写充分的单元测试
  • 使用一致的命名约定

总结

Android清洁架构为大型应用提供了良好的架构方案,能够显著提升代码的可维护性和可测试性。尽管初期学习成本较高,但从长远来看,这是一项值得的投资。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。