{"id":36622985,"url":"https://github.com/zrq1060/architecture-android","last_synced_at":"2026-01-12T09:27:23.107Z","repository":{"id":274005024,"uuid":"921589002","full_name":"zrq1060/architecture-android","owner":"zrq1060","description":null,"archived":false,"fork":false,"pushed_at":"2025-07-09T16:14:14.000Z","size":9339,"stargazers_count":169,"open_issues_count":1,"forks_count":35,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-10T01:16:01.612Z","etag":null,"topics":["android","architecture","jetpack","kotlin","mvi"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zrq1060.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-24T08:29:44.000Z","updated_at":"2025-07-09T16:14:18.000Z","dependencies_parsed_at":"2025-02-18T09:21:01.038Z","dependency_job_id":"7ac9f27e-549f-46f6-9245-5c69156e7abe","html_url":"https://github.com/zrq1060/architecture-android","commit_stats":null,"previous_names":["zrq1060/architecture-android"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zrq1060/architecture-android","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zrq1060%2Farchitecture-android","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zrq1060%2Farchitecture-android/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zrq1060%2Farchitecture-android/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zrq1060%2Farchitecture-android/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zrq1060","download_url":"https://codeload.github.com/zrq1060/architecture-android/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zrq1060%2Farchitecture-android/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28337716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","architecture","jetpack","kotlin","mvi"],"created_at":"2026-01-12T09:27:22.407Z","updated_at":"2026-01-12T09:27:23.094Z","avatar_url":"https://github.com/zrq1060.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Architecture-Android\n\n# 功能介绍\n\n- 支持**配置变更**后的还原\n  - **屏幕旋转**\n  - **亮暗主题**切换\n  - **语言**切换（国际化）\n  - **字体大小**更改\n  - **分屏**\n  - **...**\n- 支持**进程杀死**后的还原\n- 项目架构：**模块化+组件化+MVI**（UiState+ViewModel+Flow+Kotlin协程+Repository+DataSource+Retrofit）\n- 支持**多App开发**\n- 支持一键**切换Feature模块单独运行**\n- 支持一键**去除可移除功能代码**\n- 支持项目**无反射**实现（项目默认无反射实现，反射实现也提供，可供在两者选择）\n- 支持**EdgeToEdge**（`targetSdk \u003e= 35`(Android15)，**强制**开启了，所以为了开启时兼任低版本，需要全部支持）\n- 支持**动态主题**（`Android12+`支持此功能）\n- 支持**刷新、自动预加载**（自动预加载，如果用户滑动慢并且获取数据快，用户是感觉不到加载的）\n\n本项目为一个**Android架构**，它遵循 **Android 设计**和**开发最佳实践**，旨在为开发者**提供实用参考**。\n\n本项目**目标**是为了**同时**支持`Compose`和`View`，以支持**公司项目**目前已有的`View`代码和之后的`Compose`代码，目前本项目**仅**支持`View`，后续项目**关注度** **（Star点赞数）** **高后**会支持`Compose`。\n\n本项目是以**字节跳动**公司的**抖音**App为参照，模拟开发的**抖音**App。由于本项目，**无抖音真正的网络数据**，所以本项目使用的数据，是通过某些**开源API**网络接口，**模拟**转的**网络数据**。\n\n本项目是在**官方的架构**（[nowinandroid（18.2k Star）](https://github.com/android/nowinandroid)、[architecture-samples（44.9k Star）](https://github.com/android/architecture-samples)）上做的**升级和修改**，如果大家对**此架构模块的划分**不理解，建议大家先了解**官方的**[nowinandroid](https://github.com/android/nowinandroid)，然后再来看本项目。\n\n本项目文档分为**快速介绍**、**详细介绍**（**模块间架构**、**模块内架构**）、**使用**，建议大家按照顺序阅读文档。如果你**想快速**的了解，可以只看**快速介绍**、**使用**，我提供了**demo工程**，里面有**最简单**的**使用案例**，可以先阅读此代码。\n\n欢迎大家一起来**维护**项目，使其功能更加的**强大**、**健硕**。有问题，有需求，请提[issue](https://github.com/zrq1060/architecture-android/issues/new)，或者私信我。\n\n**项目链接：** [architecture-android](https://github.com/zrq1060/architecture-android)，**欢迎大家点赞、收藏，以方便您后续查看。**\n\n## 下载\n\n[Apk下载](https://github.com/zrq1060/architecture-android/blob/master/docs/images/apk/app-release.apk)\n\n**扫码下载**\n\n\u003cimg src=\"docs/images/apk/qrcode.png\" title=\"扫码下载\" alt=\"扫码下载\" data-align=\"left\"\u003e\n\n## 截图\n\n### App展示\n\n| ![screenshot1](docs/images/screenshot/screenshot1.png) | ![screenshot2](docs/images/screenshot/screenshot2.png) | ![screenshot3](docs/images/screenshot/screenshot3.png) |\n| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ |\n\n### 主题展示\n\n|    Themes    |                                Light                                |                                Dark                                |\n| :-----------: | :------------------------------------------------------------------: | :----------------------------------------------------------------: |\n| **抖音主题** |   ![theme_douyin_light](docs/images/themes/theme_douyin_light.png)   |   ![theme_douyin_dark](docs/images/themes/theme_douyin_dark.png)   |\n| **动态主题1** | ![theme_dynamic1_light](docs/images/themes/theme_dynamic1_light.png) | ![theme_dynamic1_dark](docs/images/themes/theme_dynamic1_dark.png) |\n| **动态主题2** | ![theme_dynamic2_light](docs/images/themes/theme_dynamic2_light.png) | ![theme_dynamic2_dark](docs/images/themes/theme_dynamic2_dark.png) |\n\n# 快速介绍\n\n## 下载\n\n**下载项目并运行**\n\n```shell\ngit clone git@github.com:zrq1060/architecture-android.git\n```\n\n## 功能演示\n\n\u003e 本项目**任何**页面，都支持如下：\n\u003e\n\u003e - **配置变更后的还原**（**屏幕旋转**、**亮暗模式切换**、**语言切换**、**字体大小更改**、**分屏**等），配置变更会导致`Activity`、`Fragment`会**重新创建新的**。\n\u003e - **进程被杀死后的还原**（可打开，开发者选项-后台进程限制-不允许后台进程，以更好的测试进程被杀死。开启后，可在后台多打开一些无关的app，再切换打开此app即可演示此效果）。\n\n本项目，目前**仅支持**如下功能：\n\n- **登录页**：登录账号（手机号、邮箱）为 **【任意】**，登录密码（验证码、密码）为 **【123456】**。**可断网**，或输入**错误密码**，查看页面效果。\n\n- **Home首页**：顶部栏目的排序（**长按首页-顶部栏目**）\n\n- **Main主页**：好友、商场栏目的切换（**长按主页-底部栏目第2个**）\n\n- **Shop商城页**：支持**刷新**、**自动加载**，**点击商城条目**模拟的商城列表数据的**增、删、改**操作。**可断网**，查看页面效果。\n\n**在此跟着上面，操作App支持的功能（记得开启屏幕自动旋转、切换亮暗模式、点击商城Item），以演示上面功能效果。**\n\n## 单独运行Feature模块演示\n\n1、修改**项目根目录**下`gradle.properties`内`isFeatureSingle`为`true`，并`Sync`同步`Gradle`。\n\n```groovy\nisFeatureSingle = true\n```\n\n2、执行**安装全部命令**\n\n点击右侧`Gradle`-`Tasks`-`install`-`installDebug`，**或**执行如下命令：\n\n```groovy\n.\\gradlew installDebug\n```\n\n执行完后，会在**手机桌面**出现**所有Feature模块**的`App`（如下图所示），**点击**某个即可测试某单个**Feature**模块。\n\n![feature_single_apps](docs/images/modularization/feature_single_apps.png)\n\n# 详细介绍\n\n我们先讲**模块间的架构**，然后再讲**模块内的架构**，最后讲**使用**。\n\n## 模块间架构\n\n在不断变大的代码库中，**可扩缩性**、**可读性**和**整体代码质量**通常会随着时间的推移而降低。这是因为代码库在不断变大，而其维护者未采取积极措施来保持易于维护的结构。**模块化**是一种行之有效的代码库构建方法，可帮助改善可维护性并避免此类问题。\n\n\u003e - **模块化**相关，请看官方的 [Android 应用模块化指南](https://developer.android.com/topic/modularization)。\n\u003e\n\u003e - **官方（标准版）模块划分demo**相关，请看官方的[nowinandroid](https://github.com/android/nowinandroid)。\n\n### 模块划分\n\n#### 标准版\n\n本项目**官方（标准版）**的**模块化**完成后，**模块图（部分模块）** 如下：\n\n![modularization_graph_standard](docs/images/modularization/modularization_graph_standard.png)\n\n**模块说明：**\n\n* `app`**模块**：`app`模块依赖于**所有**的`feature`模块和**必需**的`core`模块。\n* `feature:`**模块**：`feature`模块**不应该依赖于其它的**`feature`模块，它们只依赖于**所需**的`core`模块。\n* `core:`**模块**：`core`模块**可以依赖于其它的**`core`模块，但**它们不应该依赖于**`feature`模块或`app`模块。\n\n本项目**官方**的**模块化**完成后，项目**目录图**如下（**标准版**）：\n\n![directory_graph_standard](docs/images/modularization/directory_graph_standard.png)\n\n#### 多App版\n\n**一般一个公司并非一个App**，比如**商城功能App**（一个用户端、一个商家端）、**外卖功能App**（一个用户端、一个骑手端）。以**字节跳动**公司为例子，其中公司开发的App有**抖音**、**西瓜视频**、**今日头条**、**飞书**、**剪映**等。 \n\n上面**官方（标准版）**的**模块**划分，导致内部的`core`模块含有**本App特有**的、**所有App通用**的代码及资源，仅适用于**单App**架构。如果要适用**多App**架构，就需要把`core`模块内**所有App通用部分**提取出来，提取后的项目，项目**目录图**如下（**多App版**）：\n\n![directory_graph_multi_app](docs/images/modularization/directory_graph_multi_app.png)\n\n\u003e 变化说明：\n\u003e\n\u003e 1. 把**标准版**的**抖音**的模块结构，存到了**最外层**`douyin`**目录**。\n\u003e 2. 把一些**可所有App共用**的模块，存到了**最外层**`core`**目录**。\n\u003e 3. 把**标准版**的**西瓜视频**的模块结构，存到了**最外层**`xigua`**目录**。\n\n**目录说明：**\n\n* **最外层**`core`**目录**：为**所有App**都可以使用的代码及资源，**内部模块**被**所有App**内的`core`**模块**依赖。\n* **最外层**`douyin`**目录**：为 **抖音App自己独有（特有）** 的相关代码及资源。\n  * `core`**模块**：依赖**最外层**`core`**目录**内的模块，**反之不行**。\n  * `app`**模块**、`feature`**模块**：直接依赖**抖音内部**`core`模块即可，此为**最外层**`core`**目录**的**功能定制**，如：直接依赖**抖音App**的`:douyin:core:architecture`模块即可，此模块为**抖音App**对`:core:architecture`模块（**所有App通用的-架构模块**）的**定制**。\n* **最外层**`xigua`**目录**：为**西瓜视频App**的相关代码，规则同上（抖音）。\n\n\u003e 说明：还可以在**最外层**继续开发其它App，如**今日头条**、**飞书**、**剪映**等，规则同上（抖音、西瓜视频）。\n\n#### 可移除版\n\n在项目开发过程中，如果你**不看好**要开发的功能，或者领导、产品告诉你，要开发的功能**之后可能会移除**，你可以使用此设计。\n\n以**抖音App**为例，**最早**的**抖音**是没有**商城**功能的，如果以**商城**功能之后会**移除**来开发，你可以使用以下模块设计。\n\n现在的项目，项目**目录图**如下（**可移除版**）：\n\n![directory_graph_removable](docs/images/modularization/directory_graph_removable.png)\n\n\u003e 变化说明：\n\u003e\n\u003e 1. 把**可能要移除的shop功能**的模块结构，存到了**最外层**`douyin`**目录**。\n\n**新增的shop目录说明：**\n\n* `core`**模块**：**可以依赖抖音、商城core模块，但是抖音core模块不能依赖商城core模块（以便好在抖音内移除）**。\n* `feature`**模块**：**同级feature间不能相互依赖，只能依赖抖音、商城core模块**。\n\n可以**修改项目根目录**下`gradle.properties`内`isShopInclude`为`false`、`isRouterReflect`为`true`，来演示此**移除功能**。\n\n```groovy\nisShopInclude = false\nisRouterReflect = true\n```\n\n\u003e 说明：\n\u003e\n\u003e `isShopInclude`：为是否**包括商城**。\n\u003e\n\u003e `isRouterReflect`：为是否`Router`**反射实现**。\n\u003e\n\u003e - 本项目`Router`的实现分为了两种，**Dagger实现**（**正式**用）、**反射实现**（**测试**用），详细看`router`模块。\n\n### 模块包名\n\n包名格式一般为：**域名反转+项目名+功能名**，以此**字节跳动**（域名：[www.bytedance.com](http://www.bytedance.com) ）公司**抖音**项目为例，规则如下：\n\n* `core`：**com.bytedance.core.xxx**（**和特定App无关**），如：com.bytedance.core.architecture\n* `douyin`：com.bytedance.**douyin.xxx**（**和特定App有关**）\n  * `app`：com.bytedance.**douyin**\n  * `core`：com.bytedance.**douyin.core**.xxx，如：com.bytedance.douyin.core.architecture\n  * `feature`：com.bytedance.**douyin.feature**.xxx，如：com.bytedance.douyin.feature.home\n  * `shop`：com.bytedance.**douyin.shop**.xxx\n    * `core`：com.bytedance.**douyin.shop.core**.xxx，如：com.bytedance.douyin.shop.core.data\n    * `feature`：com.bytedance.**douyin.shop.feature**.xxx，如：com.bytedance.douyin.shop.feature.shop\n* `xigua`：规则同上（抖音）\n\n### 模块功能\n\n* `app`：项目的入口，含有`MainActivity`、`Application`等。\n* `core`\n  * `architecture`：架构相关，包含一些基础类，如：最外层`:core:architecture`模块包含通用的`BaseViewsActivity`、`BaseViewsFragment`等，抖音层`:douyin:core:architecture`模块包含**抖音**定制的`AppViewsActivity`、`AppViewsFragment`等。\n  * `architecture-reflect`：架构反射实现相关，包含一些架构内的反射实现，如：`reflectInflateViewBinding`（反射实现`ViewBinding`）、`reflectViewModels`（反射实现`ViewModel`）。\n  * `common`：通用相关，包含一些通用类、工具类等。\n  * `designsystem`：设计系统相关，包含控件、主题等。\n  * `model`：`Model`类相关，包含`Model`类等。\n  * `network`：网络相关，包含`NetworkDataSource`、网络工具类、图片加载等。\n  * `test`：测试页面相关（为了给未实现的功能，占位用），包含`TestActivity`、`TestFragment`等。\n  * `webview`：网页相关，包含网页的跳转、配置等。\n  * `data`：数据相关，包含`Repository`类等。\n  * `datastore`：`DataStore`存储相关，包含`PreferencesDataSource`等。\n  * `datastore-proto`：`DataStore`的`proto`配置相关，包含`user.proto`配置等。\n  * `feature-single`：单独模块运行通用配置相关，包含`TestFragmentDetailsAndroidEntryPointActivity`等。\n  * `login`：登录相关，包含登录检测、当前登录状态、退出登录等。\n  * `router`：路由系统相关，包含`Router`的**Dagger实现**、**反射实现**等。\n* `feature`：功能业务，包含UI、`ViewModel`等。\n\n### Feature模块间通信介绍\n\n`Feature`模块间通信，使用`router`模块的`Router`类进行通信，以`home`模块为例规则如下：\n\n#### 定义\n\n```kotlin\ninterface HomeRouter {\n    fun createHomeFragment(): Fragment\n}\n```\n\n此`HomeRouter`接口为`home`模块**对外暴露**的**可供其它模块调用**部分，在`router`模块内定义，如果还有其它的，可继续在此接口内添加，如：`createXXXFragment`、`startXXXActivity`方法等。\n\n#### 真的实现\n\n```kotlin\nclass DefaultHomeRouter : HomeRouter {\n    override fun createHomeFragment(): Fragment = HomeFragment.newInstance()\n}\n```\n\n此`DefaultHomeRouter`类为`HomeRouter`接口**真的实现**，在`home`模块内实现。\n\n#### 假的实现\n\n```kotlin\nclass FakeHomeRouter : HomeRouter {\n    override fun createHomeFragment(): Fragment = AppTestFragment.newInstance(\"Home\")\n}\n```\n\n此`FakeHomeRouter`类为`HomeRouter`接口**假的实现**，在`router`模块的`router-reflect`内实现，内部使用的`AppTestFragment`仅是**为了显示时占位用**。\n\n\u003e 说明：\n\u003e\n\u003e **Router-Dagger实现**：使用`Dagger`找`HomeRouter`的实现（目前提供的是`DefaultHomeRouter`），如果找不到**会报错**。\n\u003e\n\u003e **Router-反射实现**：使用**反射**直接找`DefaultHomeRouter`，如果找不到会直接使用`FakeHomeRouter`，**不会报错**。\n\n#### 调用\n\n```kotlin\nval homeFragment = Router.Home.createHomeFragment()\n```\n\n### 单独运行Feature模块介绍\n\n如果你只负责某个`Feature`模块，或者想**更解耦**、**更快**的测试你的功能，你可以使用此**单独运行Feature模块**，步骤如下：\n\n#### 修改配置\n\n修改**项目根目录**下`gradle.properties`内`isFeatureSingle`为`true`，并`Sync`同步`Gradle`。\n\n```groovy\nisFeatureSingle = true\n```\n\n\u003e 说明：\n\u003e\n\u003e `isFeatureSingle`：为是否**单独运行Feature模块**。如果**开启**，则`Router`使用**反射实现**，以使其调用**其它模块**没有时**不会报错**，而是使用`Fake`的实现（如：占位显示）。\n\n#### 添加测试入口点\n\n此功能需要配合使用我的[TestPoint](https://github.com/zrq1060/TestPoint)库来实现，添加**测试入口点**，即会在**测试列表页**增加一个按钮，点击按钮跳转到此`Activity`、`Fragment`，**定制按钮点击**等详细使用请看[TestPoint](https://github.com/zrq1060/TestPoint)。\n\n在目标类上添加`TestEntryPoint`注解，如`ShopFragment`：\n\n```kotlin\n@TestEntryPoint(\"商城\")\nclass ShopFragment{\n\n}\n```\n\n#### 运行\n\n**单个运行：**\n\n![feature_single_configurations](docs/images/modularization/feature_single_configurations.png)\n\n选择上面的一个，并运行，如：选择`douyin.shop.feature.shop`，则**运行抖音的商城功能**（可以测试**商城的点击Item**功能等）。\n\n**多个运行：**\n\n点击右侧`Gradle`-`Tasks`-`install`-`installDebug`，**或**执行如下命令：\n\n```groovy\n.\\gradlew installDebug\n```\n\n执行完后，会在**手机桌面**出现**所有Feature模块**的`App`（如快速介绍-单独运行Feature模块演示图所示），**点击**某个即可测试某单个**Feature**模块。\n\n## 模块内架构\n\n### 官方架构\n\n模块内架构，使用**官方**的[推荐架构](https://developer.android.com/topic/architecture#recommended-app-arch)，有助于构建强大而优质的应用。\n\n\u003e **应用架构**相关，请看官方的 [应用架构指南](https://developer.android.com/topic/architecture)。\n\n官方的**架构概述图** 如下：\n\n![architecture_overview](docs/images/architecture/architecture_overview.png)\n\n官方架构分为了：**UI层**、**Domain层**（可选）、**Data数据层**。\n\n### 项目架构\n\n本项目，目前**没有使用**`Domain`层，也**没有使用**`Room`库，目前的项目**架构图** 如下：\n\n![architecture_project](docs/images/architecture/architecture_project.png)\n\n# 使用\n\n## Activity、Fragment\n\n以`demo`模块的`MainActivity`为例：\n\n```kotlin\npackage com.bytedance.demo.app.main\n\nimport android.view.LayoutInflater\nimport androidx.activity.viewModels\nimport com.bytedance.douyin.core.architecture.app.views.AppViewsActivity\nimport dagger.hilt.android.AndroidEntryPoint\n// 设置as别名，一般都是设置这几个。\n// 使用别名后，此类的模板，下面的不需要改了，只需要改上面as这里即可。\nimport com.bytedance.demo.app.main.MainUiState as UiState\nimport com.bytedance.demo.app.main.MainViewModel as ViewModel\nimport com.bytedance.demo.databinding.ActivityMainBinding as ViewBinding\n\n/**\n * 描述：\n *\n * @author zhangrq\n * createTime 2025/3/24 11:14\n */\n@AndroidEntryPoint\nclass MainActivity : AppViewsActivity\u003cViewBinding, UiState, ViewModel\u003e() {\n    // 在父类AppViewsActivity中，可用反射实现（reflectViewModels()），省略此实现。\n    override val viewModel: ViewModel by viewModels()\n\n    // 在父类AppViewsActivity中，可用反射实现（reflectInflateViewBinding()），省略此实现。\n    override fun inflateViewBinding(inflater: LayoutInflater) = ViewBinding.inflate(inflater)\n\n    // 初始化View（可以在里面直接拿到当前页面布局控件）\n    override fun ViewBinding.initViews() {\n        // 设置TextView控件\n        content.textSize = 50f\n//        content.setTextColor(Color.BLACK)\n    }\n\n    // 初始化Listener（可以在里面直接拿到当前页面布局控件）\n    override fun ViewBinding.initListeners() {\n        // 设置TextView点击\n        content.setOnClickListener {\n            // 显示Toast，此Toast和当前页面的生命周期绑定，当前页面不可见，Toast关闭。\n            viewModel.showMessage(\"Long Toast\", isShort = false)\n        }\n    }\n\n    // 初始化Observer（可以在里面直接拿到当前页面布局控件），用于观察（收集）ViewModel内的暴露的属性值（Flow值）。\n    override fun ViewBinding.initObservers() {\n    }\n\n    // 收集UiState的值（可以在里面直接拿到当前页面布局控件），用于设置当前页面的数据。\n    override fun ViewBinding.onUiStateCollect(uiState: UiState) {\n        // 设置TextView的值\n        content.text = uiState.tabs?.joinToString()\n    }\n}\n```\n\n\u003e `Activity`、`Fragment`、`DialogFragment`的**使用规则相同**，以`Activity`为例，说明如下：\n\u003e\n\u003e 1. `MainActivity`直接继承**App级**的`AppViewsActivity`，此类为**抖音**项目对**通用级**的`BaseViewsActivity`的**定制**。\n\u003e 2. `ViewModel`、`ViewBinding`的**创建**，由于本项目**为了性能没有使用反射**，所以需要在**每个子类中自己实现**，可以在**App级**的`AppViewsActivity`内使用`reflectInflateViewBinding`、`reflectViewModels`**反射实现**，这样就**可以在每个子类**中省略`ViewModel`、`ViewBinding`的**创建代码**。\n\u003e 3. **初始化系列方法**，使用`ViewBinding`扩展方法，是为了能让其**在方法内直接获取到**`xxx`**控件**，而不用通过`binding.xxx`获取，以**更方便**的**操作控件**。\n\u003e 4. `XXXBinding`、`XXXUiState`、`XXXViewModel`，全部通过**as别名**来命名，**简化了名字长度**，**统一了代码样式一致性**，这样**新类**只需要**修改模板类上面as别名**即可。\n\n## ViewModel\n\n以`demo`模块的`MainViewModel`为例：\n\n```kotlin\npackage com.bytedance.demo.app.main\n\nimport com.bytedance.douyin.core.architecture.app.AppViewModel\nimport com.bytedance.douyin.core.data.repository.interfaces.MainRepository\nimport com.bytedance.douyin.core.model.MainTabType\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\nimport javax.inject.Inject\n// 设置as别名\nimport com.bytedance.demo.app.main.MainUiState as UiState\n\n/**\n * 描述：\n *\n * @author zhangrq\n * createTime 2025/3/24 11:14\n */\n@HiltViewModel\nclass MainViewModel @Inject constructor(mainRepository: MainRepository) : AppViewModel\u003cUiState\u003e() {\n\n    override val uiStateInitialValue: UiState = UiState() // UiState-初始化值\n\n    // 从MainRepository获取的本地流，本地数据改，UI改。\n    override val uiStateFlow: Flow\u003cUiState\u003e = mainRepository.getMainTabsStream().map {\n        // UiState-页面值\n        UiState(tabs = it)\n    }\n}\n\n// Main-UiState\ndata class MainUiState(\n    val tabs: List\u003cMainTabType\u003e? = null,\n)\n```\n\n\u003e 说明：\n\u003e\n\u003e 1. `ViewModel`直接继承**App级**的`AppViewModel`，此类为**抖音**项目对**通用级**的`BaseViewModel`的**定制**。\n\u003e 2. `uiStateInitialValue`为`UiState`的初始化值，一般为**默认的**`UiState`**对象**。\n\u003e 3. `uiStateFlow`为`UiState`的`Flow`流，它变化会影响到`Activity`、`Fragment`的`onUiStateCollect()`，一般为`Flow`（**单个**`Flow`、使用`combine()`观察**多个**`Flow`）的`map()`转为`UiState`的`Flow`。\n\u003e 4. `XXXUiState`，通过**as别名**来命名，**简化了名字长度**，**统一了代码样式一致性**，这样**新类**只需要**修改模板类上面as别名**即可。\n\n## 生命周期Toast、Snackbar\n\n**直接显示**`Toast`、`Snackbar`，是没有**生命周期控制**的（**只负责显示**），即使`Activiy`、`Fragment`**不可见**（被销毁、回到后台），**也还在显示**。我增加了**生命周期消息显示**，仅在`Activiy`、`Fragment`**可见时显示**。\n\n### 指定消息的显示\n\n**指定消息的显示**，是使用`Toast`，还是`Snackbar`，目前**默认**为`Toast`。\n\n- **全局消息指定**，在`BaseGlobalMessageInitializer`类设置。\n- **生命周期消息指定**，在`App`级的`AppViewsActivity`、`AppViewsFragment`、`AppViewsDialogFragment`**重写**`messageCollector`的实现。\n\n### 使用消息\n\n```kotlin\n// 全局消息，不受Activiy、Fragment的生命周期影响。\nMessageManager.showGlobalMessage(\"Global Message\")\n// 生命周期消息，受viewModel的Activiy、Fragment的生命周期影响。\nviewModel.showMessage(\"Short Message\")\nviewModel.showMessage(\"Long Message\", isShort = false)\n```\n\n## StateView\n\n`StateView`为**包含多个状态形式View**的接口，状态包括：`Loading`、`Error`、`Empty`、`Success`。\n\n### 定制UI\n\n**定制UI**：目前实现`StateView`接口的类是`DefaultStateView`。\n\n- **小改**：`DefaultStateView`，**默认**实现了`Loading`、`Error`、`Empty`**状态**的`View`，可**修改指定某个**来**定制UI**。\n- **大改**：可通过修改`createAppStateView()`、`createAppListStateView()`方法，返回`StateView`**接口的其它实现类**。\n\n### 原理\n\n- **列表使用**：是使用[BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)的`stateView`实现，底层原理是**给RecyclerView的Adapter添加了一条Item布局**。**Empty状态**，是通过**返回的列表数据是否为空**来判断的，详细使用看`BaseRefreshLoadMoreHelper`。\n- **普通使用**：是使用**Base类**`Activity`、`Fragment`的`getStateViewReplaceView()`方法实现，底层原理是**给此方法返回的View替换显示为StateView**。**Empty状态**，**目前未判断**，如需修改请看`BaseViewModel.requestAsyncBase()`扩展方法。\n\n### 使用\n\n- **列表使用**：**已封装好**，目前已支持[SmartRefreshLayout](https://github.com/scwang90/SmartRefreshLayout)、[SwipeRefreshLayout](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout)两个控件，详细使用请看`SmartRefreshLoadMoreHelper`、`SwipeRefreshLoadMoreHelper`。\n- **普通使用**：需要使用`BaseViewModel.requestAsyncBase()`扩展方法**定制**。\n  - **配置**：`Activity`、`Fragment`需要实现`getStateViewReplaceView()`，此为`StateView`要**替换的View**（用于实现**替换显示StateView**时，**隐藏此View**），可通过**覆写**此方法来**修改StateView的显示范围**，如果不覆写**默认**为此`Activity`、`Fragment`的`root`**根布局**。详细使用，请看**通用级**的`BaseViewsActivity`、`BaseViewsFragment`等。\n  - **使用**：**请求异步**的**UI**每人的需求不同（如：`Error`状态，有人想要显示`Error`**重试布局**，有人想要只需要**消息提示**），**定制**详细使用，请看`BaseViewModel.requestAsyncBase()`扩展方法。\n\n## 刷新、自动加载\n\n### 原理\n\n- **刷新**：是使用[SmartRefreshLayout](https://github.com/scwang90/SmartRefreshLayout)或[SwipeRefreshLayout](https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout)实现。\n- **自动加载**：是使用[BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)的`setTrailingLoadStateAdapter()`实现，底层原理是**通过**`ConcatAdapter.addAdapter(adapter)`**增加了尾Adapter**，详细使用请看`BaseRefreshLoadMoreHelper`。\n\n### 使用\n\n1. **UI层**：`Activity`、`Fragment`实现类，需要使用`SmartRefreshLoadMoreHelper`或`SwipeRefreshLoadMoreHelper`初始化，详细看**ShopFragment**。\n2. **ViewModel层**：`ViewModel`实现类，需要实现`RefreshRepositoryOwner`接口，其`onRefreshRepository()`方法需要返回**刷新/刷新加载**仓库。\n3. **Repository层**：\n   1. `Repository`实现类，需要**实现**`RefreshRepository`（**仅刷新**）或`RefreshLoadMoreRepository`（**刷新加载**）接口。\n   2. `Repository`实现类，需要**继承**`PageKeyedMemoryRefreshLoadMoreRepository`（**通过page加载**）或`ItemKeyedMemoryRefreshLoadMoreRepository`（**通过Item加载**）类。\n\n## 网络\n\n一个公司，可能有**多个网络规则**，可创建实现`BaseNetworkModel`接口的**XXXBaseNetworkModel**类，来实现**此规则定制**功能，后续只需使用此类即可。目前项目内有**2个规则**案例，请看`ApiOpenBaseNetworkModel`、`AppBaseNetworkModel`类。\n\n### 定义XXXBaseNetworkModel\n\n以开源接口[ApiOpen](https://api.apiopen.top/swagger/index.html)为例，其返回格式模板为：\n\n```json\n{\"code\": 200,  \"message\": \"成功!\",  \"result\": \"string\"}\n```\n\n`code`为**200代表公司的规则成功**，`message`为**提示的消息**，`result`为**结果（类型任意）**，以此创建类如下：\n\n```kotlin\n@Serializable\ndata class ApiOpenBaseNetworkModel\u003cT\u003e(val code: Int, val message: String, val result: T? = null) :\n    BaseNetworkModel\u003cT\u003e {\n    override fun isRuleSuccess() = code == 200\n\n    override fun code() = code\n    override fun message() = message\n    override fun data() = result\n}\n```\n\n### 使用XXXBaseNetworkModel\n\n```kotlin\ninterface FakeNetworkLoginApi {\n    /**\n     * 登录\n     */\n    @POST(\"api/login\")\n    @FormUrlEncoded\n    suspend fun login(\n        @Field(\"account\") account: String,\n        @Field(\"password\") password: String,\n    ): ApiOpenBaseNetworkModel\u003cFakeNetworkUser\u003e\n}\n```\n\n`login()`方法，其返回值为`ApiOpenBaseNetworkModel`，其**泛型为json模板的result值**。调用如下：\n\n```kotlin\nloginApi.login(account, password)\n```\n\n`loginApi.login()`方法，其返回值为`ApiOpenBaseNetworkModel`，这个数据不仅包含了**json模块的全部信息**，而且我们还得需要判断其**是否公司规则成功**。\n\n可以使用以下**转换方法**，转为自己想要的结果。\n\n### 转换XXXBaseNetworkModel\n\n```kotlin\nloginApi.login(account, password).toRuleSuccessData()\n```\n\n`toRuleSuccessData()`方法，将`ApiOpenBaseNetworkModel`，转换为**公司规则成功**，并且**返回其内部的result**，并且**此返回值不为空**。\n\n**目前支持的，所有转换方法，如下：**\n\n```kotlin\n/**\n * 网络成功-规则成功-内部数据-不可空\n */\nfun \u003cT\u003e BaseNetworkModel\u003cT\u003e.toRuleSuccessData(): T {\n    if (!isRuleSuccess()) {\n        throw RuleException(code(), message())\n    }\n    return data()!!\n}\n\n/**\n * 网络成功-规则成功-内部数据-可空\n */\nfun \u003cT\u003e BaseNetworkModel\u003cT\u003e.toRuleSuccessDataNullable(): T? {\n    if (!isRuleSuccess()) {\n        throw RuleException(code(), message())\n    }\n    return data()\n}\n\n/**\n * 网络成功-规则成功-全部数据\n */\nfun \u003cT\u003e BaseNetworkModel\u003cT\u003e.toRuleSuccess(): BaseNetworkModel\u003cT\u003e {\n    if (!isRuleSuccess()) {\n        throw RuleException(code(), message())\n    }\n    return this\n}\n// 网络成功-全部数据。则不需要调用此转换方法，直接返回即可。\n```\n\n可根据自己的需求，使用自己想要的转换方法，一般为`toRuleSuccessData()`（**网络成功-规则成功-内部数据-不可空**）。\n\n# 未来支持\n\n- 支持**Compose**\n- 优化**是否Login**相关逻辑\n- 优化**WebView**相关逻辑\n\n# 其它\n\n## 三方库\n\n### 自己\n\n- [TestPoint](https://github.com/zrq1060/TestPoint)\n- [SpanBuilder](https://github.com/zrq1060/SpanBuilder)\n\n### 三方\n\n- [Retrofit](https://github.com/square/retrofit)\n- [Glide](https://github.com/bumptech/glide)\n- [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)\n- [SmartRefreshLayout](https://github.com/scwang90/SmartRefreshLayout)\n- [MagicIndicator](https://github.com/hackware1993/MagicIndicator)\n\n## 参考\n\n- [nowinandroid](https://github.com/android/nowinandroid)\n- [architecture-samples](https://github.com/android/architecture-samples)\n\n\n**项目链接：** [architecture-android](https://github.com/zrq1060/architecture-android)，**欢迎大家点赞、收藏，以方便您后续查看。**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzrq1060%2Farchitecture-android","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzrq1060%2Farchitecture-android","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzrq1060%2Farchitecture-android/lists"}