开源地址: https://github.com/ittianyu/relight
- 减少内存泄漏:新手很容易在线程切换的地方写出导致内存泄漏的代码,但如果把线程切换交给框架来做,出错的概率就大大降低。
- 减少 crash:根据我的开发经历,大部分 crash 都是空指针导致的。一般线程回调里最容易出现问题,当UI销毁后,子线程依旧去操作UI,容易导致 crash。 本框架有完善的生命周期,UI销毁后,框架对子线程做了强制的停止操作,大大减少 crash 的概率。
- 最少依赖:仅依赖 lifecycle 和 support lib.
- 实现精简:只有几十个类
提示:这两个依赖库在 Android Studio 新建的项目里几乎都包含,也就是几乎 0 依赖。
- 侵入性低:不需要修改任何现有代码
- 无缝嵌入:可间接当做 View 使用,无论之前使用 MVP 还是 MVC,往里面加一个 View 根本不影响你的结构。
- 对原生开发友好:你几乎不需要学习框架 api 就可以开始使用。
- 熟悉 react 和 flutter 的非常容易上手
具体可往下滑,查看基础教程。
MVVM 的强大之处在于 UI 和 逻辑 分离,处理逻辑时不需要关心 UI,写 UI 时不需要管数据从哪获取。
要更新时,你直接对数据进行修改,就会自动触发重新渲染。 并不需要担心性能问题,因为默认情况下,原来的 View 并不会被抛弃掉,仅仅会触发一次 update 操作。
public class StatefulUserWidget extends StatefulWidget<View, UserWidget> {
private UserBean user = UserDataSource.getInstance().getUser();
public StatefulUserWidget(Context context, Lifecycle lifecycle) {
super(context, lifecycle);
}
@Override
protected State<UserWidget> createState(Context context, Lifecycle lifecycle) {
return StateUtils.create(new UserWidget(context, lifecycle, user));
}
@Override
public void initWidget(UserWidget widget) {
widget.setOnClickListener(v -> setState(() -> {
user = UserDataSource.getInstance().getUser();
}));
update();
}
@Override
public void update() {
super.update();
widget.setUser(user);
}
}
在 initWidget
方法中对 widget
设置了一个点击事件,点击后重新获取数据,自动触发 UI 的更新。
其实就是调用了 setState
方法来触发更新,类似于 react 和 flutter,更新数据的操作需要放到该方法中,否则不会触发更新。
本框架的设计思想类似于 flutter
的 "Everything's a Widget",即把所有的东西都视为控件。
各个控件之间保持独立,容器控件可以组合一个或多个控件,每个控件都有独立的生命周期。
因此,控件的复用性大大提高。
得益于谷歌新引进的 lifecycle,让每个 widget 都可以拥有完整的生命周期,甚至数据也可以拥有生命周期。
对于客户端编程来说,最麻烦的是各种异步调用和状态同步。
多线程编程很难,稍有不慎,轻则内存泄漏,重则直接蹦溃。
本框架内部做了处理异步请求,并在 onDestroy
时,自动取消子线程的操作,防止内存泄漏 或者 异步导致的空指针问题。
本库提供了如下方法支持数据修改,各位开发者可自行选择合适的方法。
setState
:同步执行数据修改操作(适用于非耗时的数据修改操作,无线程切换性能消耗)setStateAsync
:异步执行的数据修改操作,并在UI销毁时自动停止异步线程setStateAsyncWithCache
:类似于setStateAsync
,对缓存提供支持。
有了它,你可以同步的方式去发网络请求。 合并多个请求的数据变得异常轻松(比如 先请求a,在请求b,合并结果变成c)。
生活 缓存很难。 一千个应用有一千种缓存。
我见过网上很多缓存方案非常粗糙,大部分是直接在网络层通过拦截器来做。 因为这样不用侵入到业务代码。 但是,这样做的弊端也很大,不够灵活。 虽然像 okhttp 这样的网络库提供了对缓存的支持,比如可以设置只使用缓存,或者只使用网络,但这依然不够灵活。
如果想精准控制缓存,那就不得不自己在代码里为每一个请求都加上缓存的逻辑。 你会发现这就导致相同的缓存逻辑写了无数遍,这简直是噩梦。
不过因为本库有异步支持,所以处理缓存也变得简单多了。 至于你想怎么使用缓存,交给你自己判断吧,我们提供了一个策略接口,你只需要实现它即可。
无数据页面、 错误页面、 加载中页面、 下拉刷新、 加载更多 在应用中很常见。
实现起来却不方便了,常见的做法是 BaseActivity BaseFragment,但我表示不希望看见它们,曾今我觉得 base 是很好的逻辑抽象和封装,后来发现自从有了 base,迁移和复用几乎变成了 0。 base 使得它们紧紧的耦合在一起。 如果你不明白我在说什么,我给你举个例子:
我想从项目 A 中抽出一个页面和逻辑差不多的 Activity,以便于在项目 B 中使用,这个时候最常见的就是 复制 XxxActivity.java 到 B 项目,然后后面你懂的。
但本库对这几种页面状态提供了高度的封装,你不必再依赖于 Base
。
不仅仅是 activity,甚至一个 button,你都可以让他拥有如上的这几种状态。
具体用法参考 进阶教程 1 3 4
不知道你是否烦恼过,产品跟你说,用户可能狂按按钮,让你加个判断,减少不必要的请求。 听起来需求很简单,防止重复点击就行,但可达鸭眉头一皱,发现事情并不简单。 一个按钮防重复点击也就几行代码,但几十个几百个按钮呢? 你说可以抽出一个 BaseButton? 那点击的如果是个 text 或 fab 这样的控件呢? 确实 base 可以解决很多重复代码,但相应的你要把对应的控件全部换成 base,工作量也很大。
本库贴心的为大家提供了请求过滤器,默认就过滤重复的请求,虽然不是在 UI 上过滤,但同一个 task 的请求是不会重复执行的,这点可以放心。 如果你有其他过滤需求,还可以自定义实现一个过滤器。
请求失败重试也是很常见的需求,但实现并不简单,基本有2种做法:
- 如果在代码层面做,就需要在请求失败的回调里重新发起一次,还要记录次数,很是麻烦。
- 如果在网络层做,你就得对网络层进行一次封装,提供一个方法设置重试次数。 然而,这种方法弊端很大,不能和业务很好的联系。 因为网络层并不知道什么时候应该重试,网络请求失败就重试? 还是返回内容里面标识不成功就重试呢?
本库同样提供了重试支持,因为有了异步支持,重试对框架来说,就是一个循环,然而这个循环框架都帮你写好了,你只需告诉框架重试次数和什么时候应该重试就可以了。
动态换肤或样式修改也是一个很常见的需求,然而为了实现这样的需求,往往需要开发者在代码里提前写好根据配置修改的UI的代码。
本库同样提供了支持,你可以通过一个 json 来对 wiget 进行属性修改。 所以换个皮肤或改个样式都是分分钟的事啦。
搞前端的应该很清楚这是什么,就是所有渲染都是在一个页面上展示,页面跳转都是通过前端路由来控制。 对应到客户端,就是所有 UI 都在一个 activity 中展示。
这样做有什么意义? 安卓插件化最大的问题是四大组件需要提前在 manifest
中注册,虽然目前有一些开源项目通过底层 hook 方式解决了这个问题,但是以后的安卓版本就不清楚会不会把这个限制了。
而且目前的插件化都需要对资源进行合并,这就使得成功率下降。
如果是单页面应用,动态下发字节码执行也变得有可能。 而且这样成功率理论上接近 100%。 打算有时间尝试一下。
我的意思就是像前端那样具有随时更新的能力,不知道会不会被封杀,逃。。。
【可选】 添加 java8 支持
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
...
}
添加 maven 仓库
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
添加依赖
def support_version = '28.0.0'
def lifecycle_version = '1.1.1'
implementation 'com.github.ittianyu:relight:0.2.2'
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"
// Support library depends on this lightweight import
implementation "android.arch.lifecycle:runtime:$lifecycle_version"
如果开启了 java8
// alternately - if using Java8, use the following instead of compiler
implementation "android.arch.lifecycle:common-java8:$lifecycle_version"
使用了 xml 支持,必须加入混淆,未使用的可以不加。
-keep class * extends com.ittianyu.relight.widget.Widget {*;}
目的:学习 AndroidWidget 的简单用法。
目的:学习 StatefulWidget 的简单用法。
目的:学习 TextWidget 的简单用法,熟悉非 xml 的方式写界面。
目的:学习 LinearWidget 的简单用法。
目的:学习 FrameWidget 的简单用法
目的:学习 RelativeWidget 的简单用法
目的:学习 setStateAsync 的使用。
目的:学习 LceeWidget 的使用。
目的:学习 AsyncState 的更新策略。
目的:学习 LceermWidget 的使用。
目的:学习 RmWidget 的使用。
目的:学习请求重试api。
目的:学习缓存机制。
目的:学习 Widget 中 startActivity 的用法。
目的:了解 Widget xml 可视化支持
前面说过本框架的设计思想是 "Everything's a Widget",Widget
是带有生命周期的原子性控件,
大致分为三类:native、stateful、stateless。
底层 widget。 直接涉及原生 view 的渲染。
- AndroidWidget:所有 native 控件的基类,含有生命周期和 native 构建方法
- BaseAndroidWidget:继承 AndroidWidget,封装了常用的 native 的属性和设置方法
- ViewGroupWidget:继承 BaseAndroidWidget,类似于 ViewGroup,用于包容其他 AndroidWidget
- FrameWidget:封装 FrameLayout
- LinearWidget:封装 LinearLayout
- RelativeWidget:封装 RelativeLayout
- BaseTextWidget:封装了继承自 TextView 的所有 View 的常用属性和设置方法
- TextWidget:封装 TextView
- ImageWidget:封装 ImageView
- ButtonWidget:封装 Button
- EditWidget:封装 EditText
- RecyclerWidget:封装 RecyclerView
- SwipeRefreshWidget:封装 SwipeRefreshLayout
带有状态的控件。
- StatefulWidget:所有 stateful 控件的基类,带有生命周期
- LceeWidget:封装了 Loading、Content、Empty、Error 四种常见状态的控件
- RmWidget:封装了 Refresh、LoadMore 两种常见状态的控件
- LceermWidget:封装了 Loading、Content、Empty、Error、Refresh、LoadMore 六种常见状态的控件
无状态的控件。
- StatelessWidget:所有 stateless 控件的基类,带有生命周期
框架内部采用线程池来执行异步操作,考虑到不同的应用有不同的需求, 所以,开发者可以自行设置相应的线程池策略(建议在初始化时设置)。
ThreadPool.set(executorService);
默认使用的是 Executors.newCachedThreadPool()
,也就是一段时间内没有异步任务时,
自动释放内部的线程,符合大部分应用的需求。
- render: 外部通过调用
render
方法,获得一个 View,进行渲染 - update: 当
StatefulWidget
状态变更时,被动触发
需要实现一个 Widget<T> build()
方法,来完成 Widget
的构建
render(first call) -> build -> widget.render -> initWidget
setState -> willUpdate -> update -> didUpdate
onDestroy -> dispose
需要实现一个 State<T> createState(Context context, Lifecycle lifecycle)
方法 来构建一个 State
对象
render(first call) -> createState -> state.init -> state.build -> widget.render -> initWidget
state.setState -> state.update -> widget.update
构造方法 -> createView
render(first call) -> initView -> initEvent -> initData
带有生命周期的 Widget
- 调用顺序
render(first call) -> bind lifecycle
需要注意的是,bind lifecycle
在控件初始化完之后才调用
- 生命周期
通过绑定 Lifecycle
来让 Widget
获得完整的生命周期
onStart
onResume
onPause
onStop
onDestroy
带有常用 View 属性设置的 native widget
initView -> initProps
onStart -> onViewAttachedToWindow -> updateProps(when has LayoutParams)
initView
是在 render
之后触发的
render(first call) -> children.render -> super.render(render self) -> add children to ViewGroup
addChildren -> updateChildrenProps -> updateProps
- 基础框架
- 异步支持
- 重试支持
- 过滤支持
- 缓存支持
- 完善 BaseAndroidWidget 基础属性 和 api
- startActivity 支持
- xml 支持
- 单元测试支持
- CoroutineState(kotlin 协程)
- 应用状态管理(类似 redux mobx)
- Android Studio 模版
- FrameWidget
- LinearWidget
- RelativeWidget
- RecyclerWidget
- TextWidget
- ImageWidget
- SwipeRefreshWidget
- ButtonWidget
- ToolbarWidget
- EditWidget
- FloatingActionButtonWidget
- ScrollWidget
- HorizontalScrollWidget
- DrawerWidget
- LceeWidget
- LceermWidget
- RmWidget
- ListWidget
- HorizontalListWidget
- Route、Navigator
- material
- ChipWidget
- ChipGroupWidget
- MaterialButtonWidget
- TextInputLayout
- TextInputEditText
- 优势
- 快速开始
- 入门教程
- 进阶教程
- Widgets
- 异步线程策略
- 内部结构
- 目录
- 英文版
- To Do List
感谢 贵州穿青人 一直以来的支持和帮忙