{"id":20255449,"url":"https://github.com/ittianyu/relight","last_synced_at":"2025-04-09T22:19:43.021Z","repository":{"id":41067047,"uuid":"152031471","full_name":"ittianyu/relight","owner":"ittianyu","description":"A light MVVM framework for Android. 一个轻量级的安卓MVVM框架","archived":false,"fork":false,"pushed_at":"2019-03-14T05:13:53.000Z","size":678,"stargazers_count":275,"open_issues_count":3,"forks_count":50,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-02T20:09:54.718Z","etag":null,"topics":["android","framework","mvvm"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ittianyu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-08T06:49:12.000Z","updated_at":"2025-03-04T12:57:31.000Z","dependencies_parsed_at":"2022-08-11T01:30:22.292Z","dependency_job_id":null,"html_url":"https://github.com/ittianyu/relight","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ittianyu%2Frelight","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ittianyu%2Frelight/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ittianyu%2Frelight/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ittianyu%2Frelight/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ittianyu","download_url":"https://codeload.github.com/ittianyu/relight/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119596,"owners_count":21050797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","framework","mvvm"],"created_at":"2024-11-14T10:38:44.070Z","updated_at":"2025-04-09T22:19:43.002Z","avatar_url":"https://github.com/ittianyu.png","language":"Java","readme":"\n![](./images/mvvm.png)\n\n开源地址: https://github.com/ittianyu/relight\n\n## 优势 ##\n\n#### 稳定 ####\n- 减少内存泄漏：新手很容易在线程切换的地方写出导致内存泄漏的代码，但如果把线程切换交给框架来做，出错的概率就大大降低。\n- 减少 crash：根据我的开发经历，大部分 crash 都是空指针导致的。一般线程回调里最容易出现问题，当UI销毁后，子线程依旧去操作UI，容易导致 crash。 本框架有完善的生命周期，UI销毁后，框架对子线程做了强制的停止操作，大大减少 crash 的概率。\n\n#### 轻量 ####\n- 最少依赖：仅依赖 [lifecycle](https://developer.android.google.cn/topic/libraries/architecture/lifecycle) 和 [support lib](https://developer.android.google.cn/topic/libraries/support-library/features).\n- 实现精简：只有几十个类\n\n提示：这两个依赖库在 Android Studio 新建的项目里几乎都包含，也就是几乎 0 依赖。\n\n#### 接入成本低 ####\n- 侵入性低：不需要修改任何现有代码\n- 无缝嵌入：可间接当做 View 使用，无论之前使用 MVP 还是 MVC，往里面加一个 View 根本不影响你的结构。\n\n#### 简单 ####\n- 对原生开发友好：你几乎不需要学习框架 api 就可以开始使用。\n- 熟悉 react 和 flutter 的非常容易上手\n\n具体可往下滑，查看基础教程。\n\n#### 解耦 ####\nMVVM 的强大之处在于 UI 和 逻辑 分离，处理逻辑时不需要关心 UI，写 UI 时不需要管数据从哪获取。\n\n要更新时，你直接对数据进行修改，就会自动触发重新渲染。 并不需要担心性能问题，因为默认情况下，原来的 View 并不会被抛弃掉，仅仅会触发一次 update 操作。\n```\npublic class StatefulUserWidget extends StatefulWidget\u003cView, UserWidget\u003e {\n    private UserBean user = UserDataSource.getInstance().getUser();\n\n    public StatefulUserWidget(Context context, Lifecycle lifecycle) {\n        super(context, lifecycle);\n    }\n\n    @Override\n    protected State\u003cUserWidget\u003e createState(Context context, Lifecycle lifecycle) {\n        return StateUtils.create(new UserWidget(context, lifecycle, user));\n    }\n\n    @Override\n    public void initWidget(UserWidget widget) {\n        widget.setOnClickListener(v -\u003e setState(() -\u003e {\n            user = UserDataSource.getInstance().getUser();\n        }));\n\t\tupdate();\n    }\n\n    @Override\n    public void update() {\n        super.update();\n        widget.setUser(user);\n    }\n}\n```\n在 `initWidget` 方法中对 `widget` 设置了一个点击事件，点击后重新获取数据，自动触发 UI 的更新。\n其实就是调用了 `setState` 方法来触发更新，类似于 [react](http://react-china.org) 和 [flutter](https://flutterchina.club)，更新数据的操作需要放到该方法中，否则不会触发更新。\n\n#### 高复用 ####\n\n本框架的设计思想类似于 `flutter` 的 \"Everything's a Widget\"，即把所有的东西都视为控件。\n各个控件之间保持独立，容器控件可以组合一个或多个控件，每个控件都有独立的生命周期。\n因此，控件的复用性大大提高。\n\n#### 便捷的生命周期 ####\n\n得益于谷歌新引进的 [lifecycle](https://developer.android.google.cn/topic/libraries/architecture/lifecycle)，让每个 widget 都可以拥有完整的生命周期，甚至数据也可以拥有生命周期。\n\n#### 异步支持 (同步发请求) ####\n\n对于客户端编程来说，最麻烦的是各种异步调用和状态同步。\n多线程编程很难，稍有不慎，轻则内存泄漏，重则直接蹦溃。\n本框架内部做了处理异步请求，并在 `onDestroy` 时，自动取消子线程的操作，防止内存泄漏 或者 异步导致的空指针问题。\n\n本库提供了如下方法支持数据修改，各位开发者可自行选择合适的方法。\n\n* `setState`：同步执行数据修改操作(适用于非耗时的数据修改操作，无线程切换性能消耗)\n* `setStateAsync`：异步执行的数据修改操作，并在UI销毁时自动停止异步线程\n* `setStateAsyncWithCache`：类似于 `setStateAsync` ，对缓存提供支持。\n\n有了它，你可以同步的方式去发网络请求。 合并多个请求的数据变得异常轻松（比如 先请求a，在请求b，合并结果变成c）。\n\n#### 缓存支持 ####\n\n\u003cs\u003e生活\u003c/s\u003e 缓存很难。 一千个应用有一千种缓存。\n我见过网上很多缓存方案非常粗糙，大部分是直接在网络层通过拦截器来做。 因为这样不用侵入到业务代码。 但是，这样做的弊端也很大，不够灵活。 虽然像 okhttp 这样的网络库提供了对缓存的支持，比如可以设置只使用缓存，或者只使用网络，但这依然不够灵活。\n\n如果想精准控制缓存，那就不得不自己在代码里为每一个请求都加上缓存的逻辑。 你会发现这就导致相同的缓存逻辑写了无数遍，这简直是噩梦。\n\n不过因为本库有异步支持，所以处理缓存也变得简单多了。 至于你想怎么使用缓存，交给你自己判断吧，我们提供了一个策略接口，你只需要实现它即可。 \n\n#### 页面状态管理 ####\n\n无数据页面、 错误页面、 加载中页面、 下拉刷新、 加载更多 在应用中很常见。\n\n实现起来却不方便了，常见的做法是 BaseActivity BaseFragment，但我表示不希望看见它们，曾今我觉得 base 是很好的逻辑抽象和封装，后来发现自从有了 base，迁移和复用几乎变成了 0。 base 使得它们紧紧的耦合在一起。 如果你不明白我在说什么，我给你举个例子：\n\n我想从项目 A 中抽出一个页面和逻辑差不多的 Activity，以便于在项目 B 中使用，这个时候最常见的就是 复制 XxxActivity.java 到 B 项目，然后后面你懂的。\n\n但本库对这几种页面状态提供了高度的封装，你不必再依赖于 `Base`。\n不仅仅是 activity，甚至一个 button，你都可以让他拥有如上的这几种状态。\n\n具体用法参考 进阶教程 1 3 4\n\n#### 请求过滤 ####\n\n不知道你是否烦恼过，产品跟你说，用户可能狂按按钮，让你加个判断，减少不必要的请求。\n听起来需求很简单，防止重复点击就行，但可达鸭眉头一皱，发现事情并不简单。\n一个按钮防重复点击也就几行代码，但几十个几百个按钮呢？ \n你说可以抽出一个 BaseButton？ 那点击的如果是个 text 或 fab 这样的控件呢？\n确实 base 可以解决很多重复代码，但相应的你要把对应的控件全部换成 base，工作量也很大。\n\n本库贴心的为大家提供了请求过滤器，默认就过滤重复的请求，虽然不是在 UI 上过滤，但同一个 task 的请求是不会重复执行的，这点可以放心。 如果你有其他过滤需求，还可以自定义实现一个过滤器。\n\n#### 重试支持 ####\n\n请求失败重试也是很常见的需求，但实现并不简单，基本有2种做法：\n- 如果在代码层面做，就需要在请求失败的回调里重新发起一次，还要记录次数，很是麻烦。\n- 如果在网络层做，你就得对网络层进行一次封装，提供一个方法设置重试次数。 然而，这种方法弊端很大，不能和业务很好的联系。 因为网络层并不知道什么时候应该重试，网络请求失败就重试？ 还是返回内容里面标识不成功就重试呢？\n\n本库同样提供了重试支持，因为有了异步支持，重试对框架来说，就是一个循环，然而这个循环框架都帮你写好了，你只需告诉框架重试次数和什么时候应该重试就可以了。\n\n\n#### 动态属性设置能力 ####\n\n动态换肤或样式修改也是一个很常见的需求，然而为了实现这样的需求，往往需要开发者在代码里提前写好根据配置修改的UI的代码。\n\n本库同样提供了支持，你可以通过一个 json 来对 wiget 进行属性修改。\n所以换个皮肤或改个样式都是分分钟的事啦。\n\n#### 单页面应用(测试) ####\n\n搞前端的应该很清楚这是什么，就是所有渲染都是在一个页面上展示，页面跳转都是通过前端路由来控制。 对应到客户端，就是所有 UI 都在一个 activity 中展示。\n\n这样做有什么意义？ 安卓插件化最大的问题是四大组件需要提前在 `manifest` 中注册，虽然目前有一些开源项目通过底层 hook 方式解决了这个问题，但是以后的安卓版本就不清楚会不会把这个限制了。\n而且目前的插件化都需要对资源进行合并，这就使得成功率下降。\n\n如果是单页面应用，动态下发字节码执行也变得有可能。 而且这样成功率理论上接近 100%。 打算有时间尝试一下。\n\n我的意思就是像前端那样具有随时更新的能力，不知道会不会被封杀，逃。。。\n\n## 快速开始 ##\n\n#### 引入库 ####\n\n【可选】 添加 java8 支持\n```\nandroid {\n...\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n...\n}\n```\n\n添加 maven 仓库\n```\nallprojects {\n    repositories {\n        ...\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n添加依赖\n```\ndef support_version = '28.0.0'\ndef lifecycle_version = '1.1.1'\n\nimplementation 'com.github.ittianyu:relight:0.2.2'\nimplementation \"com.android.support:appcompat-v7:$support_version\"\nimplementation \"com.android.support:design:$support_version\"\n\n// Support library depends on this lightweight import\nimplementation \"android.arch.lifecycle:runtime:$lifecycle_version\"\n```\n\n如果开启了 java8\n```\n// alternately - if using Java8, use the following instead of compiler\nimplementation \"android.arch.lifecycle:common-java8:$lifecycle_version\"\n```\n\n\n#### 混淆 ####\n\n使用了 xml 支持，必须加入混淆，未使用的可以不加。\n```\n-keep class * extends com.ittianyu.relight.widget.Widget {*;}\n```\n\n\n## 入门教程 ##\n\n#### [1. AndroidWidget](./docs/base/1.AndroidWidget.md) ####\n\n目的：学习 AndroidWidget 的简单用法。\n\n#### [2. StatefulWidget](./docs/base/2.StatefulWidget.md) ####\n\n目的：学习 StatefulWidget 的简单用法。\n\n#### [3. TextWidget](./docs/base/3.TextWidget.md) ####\n\n目的：学习 TextWidget 的简单用法，熟悉非 xml 的方式写界面。\n\n#### [4. LinearWidget](./docs/base/4.LinearWidget.md) ####\n\n目的：学习 LinearWidget 的简单用法。\n\n#### [5. FrameWidget](./docs/base/5.FrameWidget.md) ####\n\n目的：学习 FrameWidget 的简单用法\n\n#### [6. RelativeWidget](./docs/base/6.RelativeWidget.md) ####\n\n目的：学习 RelativeWidget 的简单用法\n\n#### [7. setStateAsync](./docs/base/7.setStateAsync.md) ####\n\n目的：学习 setStateAsync 的使用。\n\n## 进阶教程 ##\n\n#### [1. LceeWidget](./docs/medium/1.LceeWidget.md) ####\n\n目的：学习 LceeWidget 的使用。\n\n#### [2. UpdateStrategy](./docs/medium/2.UpdateStrategy.md) ####\n\n目的：学习 AsyncState 的更新策略。\n\n#### [3.LceermWidget](./docs/medium/3.LceermWidget.md) ####\n\n目的：学习 LceermWidget 的使用。\n\n#### [4.RmWidget](./docs/medium/4.RmWidget.md) ####\n\n目的：学习 RmWidget 的使用。\n\n#### [5.Retryable](./docs/medium/5.Retryable.md) ####\n\n目的：学习请求重试api。\n\n#### [6.Cache](./docs/medium/6.Cache.md) ####\n\n目的：学习缓存机制。\n\n#### [7.StartActivity](./docs/medium/7.StartActivity.md) ####\n\n目的：学习 Widget 中 startActivity 的用法。\n\n#### [8.xml](./docs/medium/8.xml.md) ####\n\n目的：了解 Widget xml 可视化支持\n\n\n## Widgets ##\n\n前面说过本框架的设计思想是 \"Everything's a Widget\"，`Widget` 是带有生命周期的原子性控件，\n大致分为三类：native、stateful、stateless。\n\n#### native ####\n底层 widget。 直接涉及原生 view 的渲染。\n\n* AndroidWidget：所有 native 控件的基类，含有生命周期和 native 构建方法\n* BaseAndroidWidget：继承 AndroidWidget，封装了常用的 native 的属性和设置方法\n* ViewGroupWidget:继承 BaseAndroidWidget，类似于 ViewGroup，用于包容其他 AndroidWidget\n* FrameWidget：封装 FrameLayout\n* LinearWidget：封装 LinearLayout\n* RelativeWidget：封装 RelativeLayout\n* BaseTextWidget：封装了继承自 TextView 的所有 View  的常用属性和设置方法\n* TextWidget：封装 TextView\n* ImageWidget：封装 ImageView\n* ButtonWidget：封装 Button\n* EditWidget：封装 EditText\n* RecyclerWidget：封装 RecyclerView\n* SwipeRefreshWidget：封装 SwipeRefreshLayout\n\n#### stateful ####\n\n带有状态的控件。 \n\n* StatefulWidget：所有 stateful 控件的基类，带有生命周期\n* LceeWidget：封装了 Loading、Content、Empty、Error 四种常见状态的控件\n* RmWidget：封装了 Refresh、LoadMore 两种常见状态的控件\n* LceermWidget：封装了 Loading、Content、Empty、Error、Refresh、LoadMore 六种常见状态的控件\n\n#### stateless ####\n\n无状态的控件。\n\n* StatelessWidget：所有 stateless 控件的基类，带有生命周期\n\n## 异步线程策略 ##\n\n框架内部采用线程池来执行异步操作，考虑到不同的应用有不同的需求，\n所以，开发者可以自行设置相应的线程池策略(建议在初始化时设置)。\n```\nThreadPool.set(executorService);\n```\n\n默认使用的是 `Executors.newCachedThreadPool()`，也就是一段时间内没有异步任务时，\n自动释放内部的线程，符合大部分应用的需求。\n\n## 核心方法调用顺序 ##\n\n#### Widget ####\n\n- render: 外部通过调用 `render` 方法，获得一个 View，进行渲染\n- update: 当 `StatefulWidget` 状态变更时，被动触发\n\n#### StatelessWidget ####\n需要实现一个 `Widget\u003cT\u003e build()` 方法，来完成 `Widget` 的构建\n\n```\nrender(first call) -\u003e build -\u003e widget.render -\u003e initWidget\n```\n\n#### state ####\n\n```\nsetState -\u003e willUpdate -\u003e update -\u003e didUpdate\n\nonDestroy -\u003e dispose\n```\n\n#### StatefulWidget ####\n需要实现一个 `State\u003cT\u003e createState(Context context, Lifecycle lifecycle)` 方法 来构建一个 `State` 对象\n\n```\nrender(first call) -\u003e createState -\u003e state.init -\u003e state.build -\u003e widget.render -\u003e initWidget\n\nstate.setState -\u003e state.update -\u003e widget.update\n```\n\n#### AndroidWidget ####\n\n```\n构造方法 -\u003e createView\n\nrender(first call) -\u003e initView -\u003e initEvent -\u003e initData\n```\n\n#### Lifecycle ####\n带有生命周期的 Widget\n\n* 调用顺序\n\n```\nrender(first call) -\u003e bind lifecycle\n```\n\n需要注意的是，`bind lifecycle` 在控件初始化完之后才调用\n\n* 生命周期\n\n通过绑定 `Lifecycle` 来让 `Widget` 获得完整的生命周期\n```\nonStart\nonResume\nonPause\nonStop\nonDestroy\n```\n\n#### BaseAndroidWidget ####\n带有常用 View 属性设置的 native widget\n\n```\ninitView -\u003e initProps\n\nonStart -\u003e onViewAttachedToWindow -\u003e updateProps(when has LayoutParams)\n```\n\n`initView` 是在 `render` 之后触发的\n\n#### ViewGroupWidget ####\n\n```\nrender(first call) -\u003e children.render -\u003e super.render(render self) -\u003e add children to ViewGroup\n\naddChildren -\u003e updateChildrenProps -\u003e updateProps\n```\n\n\n## To Do List ##\n\n#### 框架 ####\n\n- [x] 基础框架\n- [x] 异步支持\n- [x] 重试支持\n- [x] 过滤支持\n- [x] 缓存支持\n- [x] 完善 BaseAndroidWidget 基础属性 和 api\n- [x] startActivity 支持\n- [x] xml 支持\n- [ ] 单元测试支持\n- [ ] CoroutineState(kotlin 协程)\n- [ ] 应用状态管理(类似 redux mobx)\n- [ ] Android Studio 模版\n\n#### 基础控件 ####\n\n- [x] FrameWidget\n- [x] LinearWidget\n- [x] RelativeWidget\n- [x] RecyclerWidget\n- [x] TextWidget\n- [x] ImageWidget\n- [x] SwipeRefreshWidget\n- [x] ButtonWidget\n- [x] ToolbarWidget\n- [x] EditWidget\n- [x] FloatingActionButtonWidget\n- [x] ScrollWidget\n- [x] HorizontalScrollWidget\n- [ ] DrawerWidget\n\n#### 高级控件 ####\n\n- [x] LceeWidget\n- [x] LceermWidget\n- [x] RmWidget\n- [x] ListWidget\n- [x] HorizontalListWidget\n- [x] Route、Navigator\n\n#### 主题控件 ####\n\n- material\n\t- [x] ChipWidget\n\t- [x] ChipGroupWidget\n\t- [x] MaterialButtonWidget\n\t- [x] TextInputLayout\n\t- [x] TextInputEditText\n\n\n#### 文档 ####\n\n- [x] 优势\n- [x] 快速开始\n- [x] 入门教程\n- [x] 进阶教程\n- [x] Widgets\n- [x] 异步线程策略\n- [x] 内部结构\n- [ ] 目录\n- [ ] 英文版\n- [x] To Do List\n\n## 致谢 ##\n\n感谢 [贵州穿青人](https://github.com/liyujiang-gzu) 一直以来的支持和帮忙\n\n\n## 版权 ##\n\n[Apache License 2.0](https://github.com/ittianyu/relight/blob/master/LICENSE)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fittianyu%2Frelight","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fittianyu%2Frelight","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fittianyu%2Frelight/lists"}