改造 Android 官方架构组件 ViewModel

前言

Android 官方架构组件在今年 5 月份 Google I/O 大会上被公布, 直到 11 月份一直都是测试版, 由于工作比较繁忙, 期间我只是看过类似的文章, 但没有在实际项目中使用过, 更没有看过源码, 所以对这几个组件的使用很是生疏, 同时也觉得这几个组件非常高大上, 非常神秘!

直到 11 月份 Android 官方架构组件正式版发布, 并且 Google 也在 Support Library v26.1.0 以后的版本中内嵌了 Android 官方架构组件中的生命周期组件, 我想, 这是趋势, 既然 Google 这么推崇, 那我也是时候学习一波并将它们引入 MVPArms 框架了

Github : 你的 Star 是我坚持的动力 ✊

简单介绍

因为想将 Android 官方架构组件引入 MVPArms 框架之中, 所以我认真学习了 Android 官方架构组件中除了 Room 之外的所有源码, 以考察是否整个组件都适合引入 MVPArms 框架

在学习完源码过后, 发现 Android 官方架构组件其实并没有想象的那么高深, 原理反而是我们在日常开发中都会用到的知识点, 那我就在文章的开头先简单的介绍下 Android 官方架构组件中的这几个组件

Lifecycles

生命周期组件是 Android 官方架构组件中的核心组件, 它可以使各种实例作为观察者与 ActivityFragment 等具有生命周期特性的组件绑定在一起, LiveDataViewModel 都是基于此组件, 简而言之就是, 你将需要绑定生命周期的实例注册给该组件, 该组件就会在你指定的某个生命周期方法执行时通知这个实例

应用场景很多, 比如之前在 MVP 架构中, 你需要在 Activity 执行 onCreate 时, 让 Presenter 初始化一些操作, 这时就不用在 ActivityonCreate 中再调用 Presenter 的某个初始化方法了, 直接使用官方的生命周期组件即可完成, 在 Activity 执行 onDestroy 时需要释放一些对象的资源, 也可以使用到生命周期组件

LiveData

LiveData 具有两个功能, 第一个功能是观察者模式, 在 Value 发生变化时通知之前注册的所有观察者, 第二功能是基于生命周期组件与 ActivityFragment 等具有生命周期特性的组件绑定在一起, 在生命周期发生改变时停止或恢复之前的事件

简而言之就是, 当某个页面请求网络数据成功后需要同步 UI, 但这个页面已经不可见, 这时就会停止同步 UI 的操作

ViewModel

ViewModel 有两个功能, 第一个功能可以使 ViewModel 以及 ViewModel 中的数据在屏幕旋转或配置更改引起的 Activity 重建时存活下来, 重建后数据可继续使用, 第二个功能可以帮助开发者轻易实现 FragmentFragment 之间, ActivityFragment 之间的通讯以及共享数据

浅析官方架构组件

用法就不多说了, 此类文章和 Demo 太多了, 明白了它们的功能和应用场景后, 我们才知道它们是否真的适合自己的需求, 而不是盲目跟风, 下面我就来分析下我是如何考察新技术, 以及如何判断这些新技术是否有必要应用到自己的项目中

Lifecycles

上面介绍了生命周期组件的功能, 这里就来分析一下生命周期组件是否有必要引入我的框架 MVPArms

说到生命周期我就想到了我之前在 传统MVP用在项目中是真的方便还是累赘? 中讨论的一个内容

现在市面上流行的 MVP 架构有两种, 第一种是将 Activity 或 Fragment 作为 View, 抽象一个 Presenter 层出来, 第二种是将 Activity 或 Fragment 作为 Presenter, 抽象一个 View 层出来

第一种类型代表的框架有 MVPArms, 第二种类型代表的框架有 TheMVP, 当然第一种类型的 MVP 架构在市面上用的是最多的, 那么第二种类型的优点是什么呢?

我在上面这篇文章也说过, 主要优势有两个, 方便重用View, 以及 可直接与 Activity 或 Fragment 的生命周期做绑定, 这样就可以直接使用 Activity 或 Fragment 的生命周期, 不用再去做多余的回调, 当然也有缺点, 我在文章中也有介绍, 有兴趣的可以去看看

第一种类型的 MVP 架构是不具有可以和 ActivityFragment 的生命周期直接做绑定的优势的, 所以很是嫉妒第二种类型的 MVP 架构, 这也是两种类型的 MVP 架构最大的区别, 但你想的没错, 现在使用生命周期组件就可以使第一种类型的 MVP 架构很轻易的具有绑定生命周期的优势, 现在第一种类型的 MVP 架构将如虎添翼

经过以上的分析, 我认为生命周期组件对于我的框架来说是很有必要的, 这将使日常开发更加便捷

LiveData

LiveDataRxJava 都是基于观察者模式, 功能上也有重合, Google 在官方文档上也明确表示, 如果你正在使用 RxJava, Agera 等类似功能的库, 只要你能正确的处理数据流的生命周期, 就完全可以继续使用它们来替代 LiveData

Note: If you are already using a library like RxJava or Agera, you can continue using them instead of LiveData. But when you use them or other approaches, make sure you are handling the lifecycle properly such that your data streams pause when the related LifecycleOwner is stopped and the streams are destroyed when the LifecycleOwner is destroyed. You can also add the android.arch.lifecycle:reactivestreams artifact to use LiveData with another reactive streams library (for example, RxJava2).

从官方文档可以看出 Google 对此的建议就是 RxJava, Agera, LiveData 等类似功能的库, 你只使用一个即可

选择 RxJava 还是 LiveData ?

LiveDataRxJava 的功能的确过于重合, 我也十分赞同 Google 官方的建议, 两者之中选择其一就可以了, 没必要两者都引入项目, 而 MVPArms 框架, 也正好引入了 RxJava, 所以我也来分析分析在 MVPArms 框架中该选择 LiveData 还是 RxJava?

于是我认真的研究了其源码, LiveData 具有两个功能, 通知观察者更新数据和根据生命周期停止和恢复之前的事件, 而 Rxjava 加上 RxLifecycle, RxJava 加上 AutoDispose, 或 Rxjava 加上生命周期组件, 也可以轻易做到根据生命周期停止和恢复之前的事件, 在配上 Rxjava 强大的操作符, LiveData 能做的事 RxJava 都能做, LiveData 不能做的事 RxJava 也能做

并且 RxJava 不仅仅只是 RxJava, 他还是一个庞大的生态链, 他还有 RxCache, RxLifecycle, RxAndroid, RxPermission, Retrofit-Adapter 等大量并且强大的衍生库, 我们离开它做很多事都非常不便, 刚刚出生, 羽翼未丰的 LiveData 相比于 RxJava 将没有任何优势, 甚至显得非常简陋

因此 LiveDataRxJava 之间如果只能选择一个的话, 我没有任何理由选择 LiveData

ViewModel

ViewModel 中有一个功能让我十分惊艳, 也十分好奇, 它可以使 ViewModel 以及 ViewModel 中的数据在屏幕旋转或配置更改引起的 Activity 重建时存活下来, 重建后数据可继续使用, 这个功能十分实用且十分重要, 因为之前也没有一个官方解决方案, 所以我觉得很有必要将这个功能引入 MVPArms 框架

同样另外一个功能, 它还可以帮助开发者轻易实现 FragmentFragment 之间, ActivityFragment 之间的通讯以及共享数据, 同样也正是我所需要的官方解决方案

但在我继续深入研究, 准备将它引入到项目中时, 却发现 Google 将这个功能做了高度封装并限制了它的使用范围, 只能用于 ViewModel

但我想 Google 既然能让 MVVM 框架中的 ViewModel 具有这些功能, 那我为什么不能将这个功能扩展出来提供给 MVP 框架中的 Presenter, 乃至其他更多的模块?

于是我认真的研究了其源码, 准备通过修改源码并封装成库的方式, 让更多的开发者在更多的场景下能够使用到这些功能

改造 ViewModel 组件

要想改造 ViewModel 组件 自然要对它的整个源码分析一遍, 知道其原理, 才知道如何下手

分析源码

篇幅有限, 就来简单的分析下源码把, 源码其实也就几个类, 经过了层层封装, 核心代码就在一个叫做 HolderFragmentFragment 中,

在我看来 ViewModel 组件 的核心原理也就是 HolderFragment 中的一行代码实现的:

setRetainInstance(true);

setRetainInstance(boolean)Fragment 中的一个方法, 我想很多人应该都知道这个方法的意义

简单来说将这个方法设置为 true 就可以使当前 FragmentActivity 重建时存活下来, 如果不设置或者设置为 false, 当前 Fragment 会在 Activity 重建时同样发生重建, 以至于被新建的对象所替代

意思是只要将这个方法设置为 true, Fragment 以及 Fragment 之中的所有数据都会在 Activity 重建时存活下来

这时我们在 setRetainInstance(boolean) 为 true 的 Fragment 中放一个专门用于存储 ViewModelMap, 自然 Map 中所有的 ViewModel 都会幸免于 Activity 重建

于是我们让 Activity, Fragment 都绑定一个这样的 Fragment, 将 ViewModel 存放到这个 FragmentMap 中, ViewModel 组件 就这样实现了

如何改造

想要知道如何改造, 那我们就要明确这次改造的最终目的是什么, 我们的目的就是要让 ViewModel 组件 能用于 Presenter, 乃至其他更多的模块, 不止是用于 ViewModel

那为什么 Google 官方的 ViewModel 组件 不能用于其他模块呢, 通过阅读源码可以知道, 是因为 Google 把上文提到的 Map, 封装了起来, 并没有提供出去, 并且限制了 ViewModel 的构建方式

ViewModel 组件 让一个新的 ViewModel 必须继承于它的基类, 并且让开发者必须提供一个 Factory 指明当前 ViewModel 的构建方式, ViewModel 组件 会在合适的时机, 主动去根据 Factory 构建 ViewModel 实例, 并放入 Map

这时整个构建过程都被 ViewModel 组件 掌控并被限制于 ViewModel, 所以我需要做的就是将 MapViewModel 的构建方式扩展出来, 将更多的控制权交给外部的开发者

实践

经过上面的分析, 思路和方案都有了, 接下来就剩下如何把思路和方案实现了

于是我结合上文分析的思路和方案对官方源码进行了改造并做了适当的优化, LifecycleModel 就这样诞生了

这篇文章主要还是讲在完成一个目标前, 在从 0 到 1 期间进行的思路和分析的过程, 至于细节你如果感兴趣的话还是去看我的源码把, 哈哈, 注释很详细哦!

Github : 你的 Star 是我坚持的动力 ✊

总结

一个新技术是否真的适合自己还是需要自己去考察, 不应该盲目跟风, 如果你只知道这个技术很火然后去用它, 不知道为什么用它, 用它的好处, 那你就会一直陷入被动学习的窘境, 一直在学习, 但是总觉得自己跟不上时代的进步, 担惊受怕, 这是现代技术人大部分都存在的处境

至于最近闹的沸沸扬扬的简书 饱醉豚 事件, 自从简书 CEO 站台后, 已经不再是当事人一个人的事, 而是关乎到简书整个平台, 既然这个 CEO 这么傲气, 这个平台都不在乎我们这个群体, 我们也不再去关注这个平台就是了, 流量是跟着原创作者走还是跟着平台, 自己心里没点逼数吗?

简书以及简书 CEO 最好做出深刻的道歉, 否则我也会离开简书 (好像我更文频率也不是很高把? 咳咳… 我主打的是质量! 质量! 不是数量, 逃~)

踩坑

在实际项目中使用 ViewModel 组件 时我也遇到了一些问题, 浪费了我很多时间, 所以有必要分享出来让大家少走弯路

通过 Activity 获取 ViewModel 时遇到的坑:

  • 在 Application.ActivityLifecycleCallbacks 中的 onActivityCreated 方法中获取 ViewModel 时, Activity 每重建一次, 获取的 ViewModel 都是重新构建后的新实例, 并不能让 ViewModel 以及 ViewModel 中的数据幸免于 Activity 重建, 所以不要此方法中获取 ViewModel

  • 在 Activity 的 onDestroy 方法中不能获取 ViewModel, 会报错

通过 Fragment 获取 ViewModel 时遇到的坑:

  • 在 FragmentManager.FragmentLifecycleCallbacks 中的 onFragmentAttached 方法中获取 ViewModel 时也会出现和 Activity 一样的情况, 获取的 ViewModel 是重新构建后的新实例, ViewModel 以及 ViewModel 中的数据不能幸免于 Activity 重建, 所以也不要此方法中获取 ViewModel

  • 在 FragmentManager.FragmentLifecycleCallbacks 中的 onFragmentDestroyed 方法中也不能获取 ViewModel, 会报错

  • 在 Fragment 的 onDestroy 方法中不能获取 ViewModel, 会报错


Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我

– The end

解决Retrofit多BaseUrl及运行时动态改变BaseUrl?

前言

Hello,我是 JessYan,作为一个喜欢探索新颖解决方案的我,在 上篇文章 中,向大家介绍了怎样通过一行代码即可实现上传下载以及 Glide 进度监听,现在又给大家带来了另一项大家都很期待的问题的解决方案,这个问题起源于 MVPArms 的一个 Issues ,当然使用 Retrofit 时,多个 BaseUrl 以及动态切换 BaseUrl 这两个需求,在其他地方也经常被讨论,那么下面就来讲讲我的思路和解决方案

Github : 你的 Star 是我坚持的动力 ✊

gif

需求出现的场景

也许在日常开发中有些人已经遇到了这两个需求的场景,但为了让一些之前没遇到这些场景的朋友,也能看懂这篇文章,所以先在前面提一提

多个 BaseUrl 的需求场景

如果项目是聚合型 App ,比如像一些新闻资讯类客户端,可能数据源来自于多个平台,比如说知乎啊,豆瓣啊,今日头条啊,所以这样就会涉及到多个 BaseUrl

如果项目使用到多个三方服务提供商,比如图片的读取使用到一个服务商,文件的存储又使用到另一个服务商,这个也会存在一个 App 出现多个 BaseUrl

动态改变 BaseUrl 的需求场景

如果项目的 BaseUrl 会在 App 启动时,请求服务器,根据服务器的返回结果,来确定项目最终的 BaseUrl,就会涉及到运行时动态切换 BaseUrl

如果项目的某个三方服务提供商,并不是固定的,也许会出现变更的情况,比如存储服务从七牛迁移至其他云存储,那我们为了避免更改代码导致重新打包以及发版,就会从服务器获取三方服务提供商的 BaseUrl ,然后在运行时动态改变这个 BaseUrl

解决方案

其实官方 Api 早已经提供了解决方案来支持多个 BaseUrl 以及运行时动态改变 BaseUrl ,民间也同样有很多解决方案

官方静态解决方案

熟悉 Retrofit 的开发者应该知道 @Get , @Post 这些标注到每个接口方法上的注解不仅可以传相对路径,还可以传全路径,这样我们就可以做到不同的接口使用不同的 BaseUrl ,从而达到使用多个 BaseUrl 的需求,但是注解上的值只能是 Final 的常量,不能动态改变,所以我称这个解决方案为静态解决方案

官方动态解决方案

熟悉 Retrofit 的开发者也同样知道 @Url 这个标注到每个接口方法参数上的注解,它可以将全路径作为参数传进接口作为每次请求的 Url 地址,每次请求接口都可以将不同的全路径作为参数,从而达到支持多个 BaseUrl 以及在运行时动态改变 BaseUrl ,所以很多请求图片等资源的接口都是使用这个方案(咦,看样子这个官方解决方案不是同时解决我提到的这两个问题吗,别急,先往后面看!)

民间常用解决方案

之前也看过很多开源的聚合类 App 源码,像一些整合 知乎 , 豆瓣 , Gank 等多个平台数据的 App ,因为各自平台的域名不同,所以大多数这类 App 会给每个平台都各自创建一个 Retrofit 对象,即不同的 BaseUrl 使用不同的 Retrofit 对象来创建 ApiService 进行请求,这样只要新增一个不同的 BaseUrl ,那就需要重新创建一个新的 Retrofit 对象

这样也可以同时实现,支持多个 BaseUrl 以及运行时动态改变 BaseUrl 这两个需求,但是以个人的观点,创建多个其他配置属性一模一样,只是 BaseUrl 不一样的 Retrofit 对象,太过于浪费资源

民间大牛解决方案

之前偶然看到了一个 Retrofit 维护者, Square 公司的大牛的 解决方案,用来解决运行时动态改变 BaseUrl ,其实也算半官方的解决方案

提到这个解决方案时,不得不讲一个趣事,其实之前 Retrofit 默认是支持运行时动态改变 BaseUrl 的,以前是有一个名为 BaseUrl 的接口,而 Retrofit.Builder#baseUrl(BaseUrl) 方法当时传的参数就是这个 BaseUrl ,而不是现在的 HttpUrl ,这个接口内部就有一个方法返回 HttpUrl ,那时候只要实现 BaseUrl 后,动态改变这个方法的返回值,就可以实现动态改变 BaseUrl

但是这位大牛认为这样的做法不安全,所以提了一个 Pull Requests ,删掉了这个 BaseUrl 接口,并用上面的解决方案替代之,而亲爱的 JakeWharton 同意了他的观点,并合并了这个 PR 于是才有了现在的 Retrofit.Builder#baseUrl(HttpUrl) 这个不能动态改变 BaseUrlApi

Retrofit 比较早的老鸟,应该知道以前有一个这个 Api,我是说后来的版本怎么没了,原来毁在了这位兄台手上

这个方案也就是利用 Interceptor 拦截器,动态改变每个 RequestUrl 从而实现动态改变 BaseUrl,但他这个解决方案不能支持多 BaseUrl ,只要 host 一设置,直到下一次改变 Host 之前,后面的所有 Request 都必须使用同一个 Host ,还有一些弊端后面一起分析

几个方案的对比与分析

淘汰含有明显缺陷的方案

4个方案中,我首先淘汰的就是 民间常用解决方案 ,在前面已经明确了我的观点,因为我个人认为创建多个其他配置属性一模一样,只是 BaseUrl 不一样的 Retrofit 对象,太过于浪费资源,所以就算他能满足我的所有需求,除非真的没有更好的解决方案,否则我是不会选择它的

剩下的三个方案中, 官方静态解决方案 只能解决,2个需求中的支持多个 BaseUrl ,而对于动态改变 BaseUrl ,由于注解的 Value 只能为常量,所以对这个需求也是无能为力的(两个需求都满足,才表示可行)

谁是最优方案?

其实在前面已经说了 官方动态解决方案 就已经可以同时实现多 BaseUrl 和运行时动态改变 BaseUrl ,那为什么我不直接选择这个方案,还要继续分析呢?

答案也很简单,我认为这个方案,虽然灵活,但是灵活却给它带来了使用上的繁琐,每个接口每次调用都必须传入全路径作为参数,不仅繁琐而且接口一多还不好管理

民间大牛解决方案 可行? 但是我在前面已经说了这个不可行啊?

这个方案虽然可以支持运行时动态切换 BaseUrl 但是它是全局处理,一经使用改变的是所有请求的 Url ,所以它并不支持多 BaseUrl

并且更可怕的是,这个方案不仅不支持多 BaseUrl ,还会影响 官方静态解决方案官方动态解决方案 这两个支持多 BaseUrl 的方案,因为不管你注解里面声明的是什么全路径,它的 Interceptor 拦截器,都会强行将这个请求的 Url 改成它的 BaseUrl ,所以这个方案注定只适合只有一个 BaseUrl 但需要动态改变的项目

那岂不是 4 个解决方案都不可行?说这么久说个毛线啊?

方案全部淘汰?散会?

等等别急啊,虽然我站在我的角度, Pass 了文中提到的所有已存在的解决方案

但是大家仔细想想,如果网上已经存在完美的解决方案,那我还写这篇文章有什么意义?必定是没有我满意的解决方案,我才会自己动手去解决并分享啊,毕竟我是一个不愿意写重复内容的有为青年,只要是我写的内容肯定是会让大家学到不一样的知识三 ✊,不然不是砸自己招牌

好了,不逗大家了,开整!

别急,还有大招!

虽然在已有的解决方案当中没有找到让我满意的,但是在遇到问题时,冷静分析现有解决方案是很有必要的,理解前人的思路后才会对整个问题理解得更透彻,我的很多文章也都是以分析和解决思路为主,授人以鱼不如授人以渔,所以我不会直接告诉你答案,先分析一波,理清思路

这不,在分析 民间大牛解决方案 时,虽然最后发现这不是自己想要的解决方案,但是作为有发散思维的我,又是灵机一动,借助原有解决方案在上面这样一改不是就可行了?

如何改善原有方案?

上面的分析已经说了 民间大牛解决方案 ,可以在 Interceptor 拦截器中设置一个全局的 Host(Host 可以理解为 BaseUrl) ,拦截器会强行将这个 Host 应用到所有的请求上,改变该请求原有的 Url,这样导致了只会同时存在一个 Host

所以我在想,将这个唯一的 Host 变量改为集合,以存储多个 Host ,在将不同的 Host 应用到不同的请求上,不就可以支持多 BaseUrl

实践想法

说干就干,于是我自己建了一个全局的容器来存储多个 Host,这样我就可以在 App 运行时的任何时间,任何地点随意新增,修改,删除 Host

遇到问题

但是问题来了,我想要将不同的 Host 应用到不同的请求上,但我怎么知道什么请求需要什么样的 Host ,每个请求总要有个标记,让我知道他需要什么样的 Host

于是我就在想 Retrofit 有什么方法,可以在请求之前给每个请求加上不同的字符串标记,于是我很自然的想到了 Header ,Retrofit 正好有 @Headers 这个注解,可以给每个接口方法上加入自定义 Header

再次解决难点

我给需要不同 BaseUrl 的接口方法上加入了自定义的 Header ,以标明每个接口需要的 HostName ,而这个 Name 对应的值就是 Host,但这个值不是在 @Headers 中被指定的,它是可以动态改变的

存储 Host 的容器是一个 Map, key 就是这个 Name ,value 才是 Host ,拦截器每次拦截到请求时,会判断这个请求是否有这个自定义 Header, 有的话,拿到这个 Header 中标注的 Name,然后用这个 Name ,去那个存储 Host 的全局 Mapget(name),拿到对应的 Host 再应用到请求上不是就达到支持多个 BaseUrl 了?

如果想动态改变某个 Host 也简单,将新的 Host 以同样的 Name put(name) 进这个全局 Map ,到时候拦截器,使用这个 Name get(name) 出来的值,就已经是改变后最新的 Host ,在将这个 Host 应用到请求上不是就达到动态改变 BaseUrl 了?

这不,两个需求同时满足!

优化方案

这个方案就两步,给需要不同 BaseUrl 的请求设置 Header (想用 Retrofit 默认 BaseUrl 的接口,或者使用 官方静态解决方案, 官方动态解决方案 就不需要设置),在通过全局容器来管理 BaseUrl

针对于那种只有一个 BaseUrl 但需要动态改变的项目,本框架提供了一个 GlobalDomain 来优化这个场景,不需要给接口加 Header ,只需要一步,向全局容器 put(GlobalDomain) 你想要改变的 BaseUrl 就可以了

官方动态解决方案 给每个接口传全路径作为参数,要简单的多, 官方动态解决方案 注定只适合那种只有一两个需要动态改变 BaseUrl 的接口

总结

以上提到的解决方案,已经优化并封装成了三方库并上传至 Jcenter,方便大家使用

本解决方案主要适合,需要同时具备多 BaseUrl 以及动态改变 BaseUrl 的项目,或者只有一个 BaseUrl ,但需要动态改变 BaseUrl 的项目

如果对于只需要多 BaseUrl 不需要动态改变 BaseUrl 的项目,其实用 官方静态解决方案 就已经足够了,但我还是推荐用我的这个解决方案,因为需求都是会变的,如果一旦要加入动态改变 BaseUrl 的需求,如需要动态切换 生产环境 和 开发环境 ,那这时怎么办,一个个改掉每个接口注解里面的全路径?

Github : 具体使用看 Demo ,记得 Star !


Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我

– The end

一行代码实现Okhttp,Retrofit,Glide下载上传进度监听

前言

发表上篇文章 我一行代码都不写实现Toolbar!你却还在封装BaseActivity? 已是一个月前的事情,当时有人说我是标题党,也有人不认可我的内容,但是这也不并不妨碍我,两天夺得掘金当周周榜第一,并被 鸿洋公众号 转载,累计阅读量超过 3万

上篇文章的研究成果让 MVPArms 具备了 监听整个 App 所有 Activity 以及 Fragment 的生命周期(包括三方库),并可向其生命周期内插入代码 的功能,这次我又拿着最近的另一项研究成果向大家汇报,当然同样也是 MVPArms 上的新增功能

Github : 你的 Star 是我坚持的动力 ✊

gif

罗列需求

上传下载是大多数 APP 必备的功能,显示进度条也是提高用户体验的重要一环,当然作为 可配置化集成框架 MVPArms 的作者,我想再次提高开发者的使用体验以及开发效率,那我就必须提供一套解决方案

于是我打开 Github 简单的搜了一圈与 Retrofit , Okhttp , Glide 有关的进度监听库,库到是不少,但是都没有达到我想要的需求,于是我卷起衣袖,准备撸一个,当然,开撸之前要先简单梳理下自己的需求

  1. 这个库一定要支持多个平台,Okhttp , Retrofit , Glide 这三个必须同时支持
  2. 虽然支持这三个库,但是库里面并不能包含这三个库,让用户自己去引入,减小库的体积
  3. 使用一定要简单!!!,最好能一行代码搞定
  4. 侵入性低,并不需要改之前写好的网络请求代码,引入与不引入这个库,对之前的代码都不能有任何影响
  5. 低耦合,用户做网络请求的代码,一定不能和进度接收端的代码有太多关联
  6. App 的任何位置都能接受到某个网络请求的 进度信息
  7. 不仅仅需要满足,一个数据源对应一个进度接收端的一对一关系,还需要满足一个数据源对应多个进度接收端的,一对多关系,这样就可以同步更新多个不同位置的进度条
  8. 默认运行在主线程,让使用者少去切换线程的烦恼

需求分析及调研

爽一下子,写出了这么多需求,当产品经理就是一个字爽!

仔细一看这8个需求,瞬间懵逼了,妹的这不是坑自己吗?除了最后一项,我知道可以用 Handler 来实现,其他完全没思路啊,得了,作为一个优质男青年我得知难而进啊,先从第一个需求开始分析吧!

需求 1 (多平台支持)

写之前翻了下 Google 发现,Okhttp 实现上传下载进度监听,并不困难,只用重写 RequestBodyResponseBody ,并配合 Interceptor 将每个请求原有的 RequestBodyResponseBody 替换,就可以实现,都是模版代码,复制粘贴就可以了,而 Retrofit 底层使用的是 Okhttp,那就也可以同样实现进度监听

但是 Glide怎么实现进度监听呢? 我的第一反应就是既然 Retrofit 使用 Okhttp 请求网络就可以非常容易的实现,那将 Glide 的底层请求框架换成 Okhttp 也可以实现咯,作为一个如此牛逼的库,肯定有扩展的方式,于是马上去翻 Glide 的源码,印证了自己的想法,发现 Glide 底层是使用的 HttpConenction 去请求网络,并且这个类时可以被替换的,赶快 Google 了下

compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'

ok,找到解决方案,可用上面提供的类,将底层请求框架替换为 Okhttp ,这个框架最核心的地方已经找到实现方式,主要是通过 Okhttp 实现,如同吃了定心丸,瞬间舒坦

需求 2 (减小体积)

这个需求 Google 了下,也非常简单,用 provided 引入依赖框架,打包时引入的框架就不会包含进去

需求 3 (一行代码实现)

对于这种对外 Api 设计上的需求,我们应该把主体功能实现了,再慢慢优化到想达到的目标所以先分析下面的需求

需求 4 (侵入性低)

因为需求 1 已经提到,实现上传和下载进度监听的关键就是,在 Interceptor 中将每个请求原有的 RequestBodyResponseBody 替换成重写后的

如何识别需要监听进度的请求?

替换是简单,但是不是每个请求都需要监听上传和下载进度,不可能每个请求都替换啊,开始我想到的是给需要监听进度的请求生成个标记,然后在 Interceptor 中解析到这个标记,就说明这个请求需要监听上传或下载进度,然后就开始替换之

于是我想到最简单的方式就是在请求的时候加一个自定义的 Header ,这样就不用再定义其他的类, Interceptor 遍历所有 Header 发现有这个自定义 Header ,就可以替换

但是这样并没有解决需求 4,因为这样让用户比平时请求时多了个操作,如果想让之前的代码具有进度监听功能,就要一个个挨着改,增加了劳动量,而且这个操作是针对于我这个库而产生的,当用户并不想使用这个库的时候,会牵扯到修改之前的代码,这样还增加了侵入性

Url 作为标记

一个念头一闪而过,还要什么标记, Url 是唯一的, 不就可以作为标记吗!!!

需求 5 (低耦合) ,需求 6 (任何位置都可接收),以及 需求 7 (一对多)

借用 EventBus 思想

为什么把这三个需求放在一起呢,因为这三个需求让我想到了 EventBus ,多个观察者使用同一个标记将自己注册进一个容器,被观察者使用这个标记 Post 一个事件,然后从这个容器中拿出所有使用这个标记注册过的观察者,挨个通知,这样既解耦,并且只要知道这个标记,在 App 任何位置都可以监听,也支持一对多

加上需求 4,中提到的使用 Url 作为标记,那我就可以做到之前请求的代码一个也不用改,只用写接收端的代码即可实现以上的需求

构思 Api

既然谈到 EventBus ,那我就用 EventBusApi 来设计,用户只用一行代码,传入一个 标记 和一个 事件 即可实现上传和下载进度监听,没错 标记 就是 Url , 事件 就是用于获取进度信息的 监听器,这样也就满足了 需求 3 的一行代码实现的需求

Like this

ProgressManager.post(标记,事件);

用户调用这一行代码后,我会将 Url 作为 Key,监听器 作为 value 放入一个全局唯一的 Map

等等?说好一对多的呢?所以这个 value 必须是 List< 监听器 > ,这样就满足了一对多的条件了

内部如何通知监听器?

我们把所有需要监听的 Url监听器 都注册进了这个容器,那我们什么时候该去通知 监听器 进度信息呢,当然是在 RequestBodyResponseBody 中开始写入或读取二进制流的时候,因为只有他们第一时间知道,读取和写入的时间,现在只需要把对应 Url 的所有 监听器 放入他的 Body 中就可以了

因为 需求 4 中提到,我们并不知道哪些请求是需要监听上传或下载进度,哪些是不需要的,但是现在我们就可以通过 Url 来辨别,因为我们可以在 Interceptor 中拿到 RequestUrl

之前我们已经将 Url 作为 Key 注册进了容器,如果容器里面 Contain 这个 Url 那就是说明这个请求,是需要监听上传或下载进度的,那我们就给他替换成重写后的 Body 并将监听器传入,重写后的 Body 在发生二进制流的 读取 或 写入 时不断的遍历这个 Url 的所有 监听器,调用 监听器 的监听方法,并传入进度信息,就可以执行使用者的更新逻辑,这就大功告成了

需求 8 (主线程执行)

这个很简单,使用 Handler.post(Runnable)Runnable 中调用 监听器 的方法就可以了

框架细节优化

无需手动注销

大家都知道 EventBus 注册观察者后,在不需要接受事件时,需要手动注销,但是应用到我这个库中,事件的接受可能不需要这么严谨,所以为了免去使用者多余的步骤,我就是使用 WeakHashMap 代替之前的 Map 容器,这个 WeakHashMap 会在 Java虚拟机 回收内存时,找到没被使用的 Key,将此条目整个移除,所以不需要手动 remove()

加锁

在上面提到用户只需要一行代码,将 Url监听器 加入容器,但是这行代码,可能是在不同线程中被调用的,而且这行代码内的一些逻辑在多线程中是不安全的,所有这时我需要加入线程锁,这个对于三方库很重要,因为你无法预知一些用户的操作

向使用者抛出清晰的错误

因为我在 需求 2 中已经提到,此库只会用 provided 引入 Okhttp ,所以 Okhttp 是不会被打进 arr 包里的,所以如果使用者在自己的项目中没有引入 Okhttp 是会报 NoClassDefFoundError 这个错误的,但是这个错误会让使用者不知道真实的出错原因,让使用者误以为是这个库的导致的,所以我会在库初始化的时候, Class.forName(“okhttp3.OkHttpClient”); 如果找不到 Okhttp 的这个类,说明使用者没有引入 Okhttp ,然后我会抛出一个解释非常清晰的错误

提高性能

因为上面提到过我会在 Body ,开始读取或写入二进制流时,不断的遍历所有监听器并调用它的监听方法,来达到一对多的同步更新

但是这样 监听器 达到一定数量就会出现性能问题,并且在遍历时,搞不好使用者也会,不断的添加新的监听器,在遍历时改变容器的长度是容易发生错误的

所以我在将 List 传入 Body 时,将这个 List.toArray() ,数组分配的是连续的内存区域并且长度是固定的,所以索引效率占有优势,则使用数组来遍历,由于数组长度是固定的,所以也不会出现遍历时长度变化的问题

区分同一个Url的多个进度

因为 App 用户可能在前一个进度还没上传或下载完的情况下,继续使用同一个 Url 开始新的请求,如果框架使用者在上层不去做去除重复点击的操作,那同一个 Url 就会同时存在多个正在执行的进度更新,这时就需要有标识符来区分到底是哪个进度信息(这个 Url 的所有正在执行的进度更新都会调用之前以这个 Url 注册过的监听器),所以我在 Body ,创建时会将 System.currentTimeMillis() 作为唯一 ID ,保存起来,每次将进度信息和 Id 一起传给使用者

总结

其实这个库本来就比较简单,实现的核心方式在很多地方都是能复制粘贴到的,但经过我这么一封装还是要比之前的方式,简单优雅不少,而写这篇文章的目也是想分享下,如何分析需求,以及如何封装优化一个小型的库,当然平时也要多阅读源码,不断积累和借鉴优秀的思想在创作时灵感才会源源不断,比如我这个库就是借鉴的 EventBus 的思想,在写代码时要敢于想敢于尝试较于之前不同的新思想,才会不断进步

Github : 具体实现还得看源码不是? 记得给 Star ✊ 感谢!


Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我

– The end

我一行代码都不写实现Toolbar!你却还在封装BaseActivity?

前言

距离 上篇文章 的发表时间已经过去两个多月了,这两个月时间里我没写文章但一直在更新着我的 MVPArms 框架,让他逐渐朝着 可配置化集成框架 发展

就在前段时间我在 鸿洋公众号 上看到了一篇文章,大概是介绍怎么封装 BaseActivity ,让 Activity 通过几行代码就可以实现 ToolBar

刚好我的 MVPArms 框架也更新了一个功能:

通过非继承 Activity Fragment 来实现以前需要封装进 BaseActivity BaseFragment 通过继承来实现的一些公共逻辑,以及监听整个 App 所有 Activity 以及 Fragment 的生命周期(包括三方库),并可向其生命周期内插入代码

那我就来说说我怎么在不使用继承的情况下让 Activty 一行代码都不写就能实现 Toolbar

为什么我提倡少封装 BaseActvity 少用继承

BaseActivity 封装多了,除了不好管理外,还有最重要的一点就是, Java 只能单继承,当如果你的 Activity 需要使用到某个三方库,那个三方库必须让你继承于它的 Activity 但是你又需要你自己封装的 BaseActivity 的某些特性,这时你怎么办? 你不能改三方库的 Activity 所以你只有改你的 BaseActivity 让它去继承三方库的 Activity,但是当改了 BaseActivity 后,发现有很多继承于 BaseActivityActivity 并不需要这个三方库,却被迫继承了它的 Activity ,这时你又要重新封装一个 BaseActivity

当这种情况发生的多了,你的 BaseActiviy 也越来越多,这样就恶性循环了,所以我不仅提倡 App 开发者少封装 BaseActivity 少用继承,也提倡 三方库 开发者少封装 BaseActivity 少用继承,为什么呢?因为当 App 开发者的 Activity 需要使用到两个三方库,两个三方库都需要继承它的 Activity,这时你让 App 开发者怎么办?所以作为一个可配置化集成框架作者,我不能让开发者去直接改我的 BaseActivity 我必须通过其他扩展的方式去解决这个问题

进入正题

好了进入正题,要想解决上面提到的问题,我们就要思考我们为什么一定要封装 BaseActivity 通过继承来实现一些 Activity 的公共逻辑,而不能将公共逻辑封装到其他类里面?

答案很简单,因为我们必须使用到 Activity 的一些生命周期,在对应的生命周期里执行对应的逻辑,这个就是我们不能通过封装其他类来实现的原因,找到了问题关键,那我们就从生命周期上下手来解决问题

ActivityLifecycleCallbacks

提到 Activity 的生命周期,这时我就要介绍一个接口了,它叫 ActivityLifecycleCallbacks ,不知道有同学之前了解过它吗?不了解不要紧,我现在来介绍介绍它

ActivityLifecycleCallbacksApplication 中声明的一个内部接口,我们来看看它的结构

public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity, Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity, Bundle outState);
        void onActivityDestroyed(Activity activity);
    }


这个接口有什么用呢?

Application 提供有一个 registerActivityLifecycleCallbacks() 的方法,需要传入的参数就是这个 ActivityLifecycleCallbacks 接口,作用和你猜的没错,就是在你调用这个方法传入这个接口实现类后,系统会在每个 Activity 执行完对应的生命周期后都调用这个实现类中对应的方法,请记住是每个!

这个时候我们就会想到一个需求实现,关闭所有 Activity !你还在通过继承 BaseActivityBaseActivityonCreate 中将这个 Activity 加入集合???

那我现在就告诉你这样的弊端,如果你 App 中打开有其他三方库的 Activity ,这个三方库肯定不可能继承你的 BaseActivity ,这时你怎么办?怎么办?

这时 ActivityLifecycleCallbacks 就派上用场了, App 中的所有 Activity 只要执行完生命周期就一定会调用这个接口实现类的对应方法, 那你就可以在 onActivityCreated 中将所有 Activity 加入集合,这样不管你是不是三方库的 Activity 我都可以遍历集合 finish 所有的 Activity

使用 ActivityLifecycleCallbacks 实现 ToolBar

设置 NoActionBar 主题

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    ...
</application>

创建 ToolBar 的布局并引用

大家不要纠结我的 Toolbar 布局方式,这里只是实现思想,你可以自己改成自己的布局方式

<me.jessyan.art.widget.autolayout.AutoToolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="120px"
    android:background="?attr/colorPrimary"
    app:contentInsetStart="0dp"
    >

	// toolbar 中的返回按钮
    <com.zhy.autolayout.AutoRelativeLayout
        android:id="@+id/toolbar_back"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="left"
        >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30px"
            android:layout_marginRight="30px"
            android:layout_centerVertical="true"
            android:src="@mipmap/login_return"/>

    </com.zhy.autolayout.AutoRelativeLayout>

	// toolbar 中的标题名
    <TextView
        android:id="@+id/toolbar_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18dp"
        android:textColor="#fff"
        android:layout_gravity="center"
        tools:text="MVPArt"/>


</me.jessyan.art.widget.autolayout.AutoToolbar>

在你需要 ToolbarActivity 布局中 include 上面的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

	//将 Toolbar 包裹进来
    <include layout="@layout/include_title"/>

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="15px"
        android:paddingTop="15px"
        >

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"
            tools:listitem="@layout/recycle_list"
            />

    </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

实现 ActivityLifecycleCallbacks 并注册给 Application

下面只是对 ToolBar 简单的设置,你可以自己配置更多复杂的功能,你想象力有多丰富,这里就有多强大

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                //这里全局给Activity设置toolbar和title,你想象力有多丰富,这里就有多强大,以前放到BaseActivity的操作都可以放到这里
                if (activity.findViewById(R.id.toolbar) != null) { //找到 Toolbar 并且替换 Actionbar
                    if (activity instanceof AppCompatActivity) {
                        ((AppCompatActivity) activity).setSupportActionBar((Toolbar) activity.findViewById(R.id.toolbar));
                        ((AppCompatActivity) activity).getSupportActionBar().setDisplayShowTitleEnabled(false);
                    } else {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            activity.setActionBar((android.widget.Toolbar) activity.findViewById(R.id.toolbar));
                            activity.getActionBar().setDisplayShowTitleEnabled(false);
                        }
                    }
                }
                if (activity.findViewById(R.id.toolbar_title) != null) { //找到 Toolbar 的标题栏并设置标题名
                    ((TextView) activity.findViewById(R.id.toolbar_title)).setText(activity.getTitle());
                }
                if (activity.findViewById(R.id.toolbar_back) != null) { //找到 Toolbar 的返回按钮,并且设置点击事件,点击关闭这个 Activity
                    activity.findViewById(R.id.toolbar_back).setOnClickListener(v -> {
                        activity.onBackPressed();
                    });
                }
            }

            ...
            
        });
    }
}

在 AndroidManifest.xml 中设置 Label

以后你想让一个 Activity 实现 ToolBar ,只用做两件事情:

  1. 在布局文件中
 <include layout="@layout/include_title"/>
  1. AndroidManifest.xml 给这个 Activity 设置 Label ,这个 Label 就是标题名
        <activity
            android:name=".mvp.ui.activity.SplashActivity"
            android:label="@string/app_name"
            />

再介绍给大家一个全局配置所有 Activity 进入退出过度动画的方法,设置 Theme 属性

 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowAnimationStyle">@style/AnimaActivity</item>
    </style>
    <style name="AnimaActivity">
        <item name="android:activityOpenEnterAnimation">@anim/translate_right_to_center</item>
        <item name="android:activityOpenExitAnimation">@anim/translate_center_to_left</item>
        <item name="android:activityCloseEnterAnimation">@anim/translate_left_to_center</item>
        <item name="android:activityCloseExitAnimation">@anim/translate_center_to_right</item>
    </style>

Activity 中你根本不用继承任何 Activity ,不用写任何一行代码,就可以实现很多繁琐复杂的功能

很多公共逻辑都可以写到 ActivityLifecycleCallbacks 中,只要敢于尝试,你想象力有多丰富,这里就有多强大

扩展

因为所有 Activity 在执行对应生命周期时, ActivityLifecycleCallbacks 对应的方法都会被调用,有些 Activity 可能不需要 Toolbar ,比如三方库的 Activity ,虽然在 onActivityCreated 方法中,判断了 ToolBarId 找不到就不执行设置 ToolBar 的逻辑,但是未免不够优雅

自定义接口

其实我们可以让 Activity 实现对应的自定义接口, 在 onActivityCreatedinstanceof 这个自定义接口,如果不实现这个自定义接口,就说明不需要设置 ToolBar ,这样就优雅很多

储存数据

ActivityLifecycleCallbacks 中,所有 Activity 执行对应的生命周期后,它对应的方法都会被调用,所以我们必须区分这个 Activity 到底是哪个 Activity ,所以 ActivityLifecycleCallbacks 每个方法都会传入 Activity 做为参数,我们就可以用来区分 Activity

public void onActivityCreated(Activity activity, Bundle savedInstanceState){
	if (activity instanceof xxxActivity){
		....
	}

}

但是又有一个问题出现这个 ActivityLifecycleCallbacks 是公用的,当一个 ActivityonCreate 方法产生了一个对象 ,我们需要在这个 Activity 执行 onDestroy 时用到这个对象,怎么办?因为每个 Activity 都要产生这个对象,我们不可能把这个对象存储在 ActivityLifecycleCallbacks 中啊

现在就可以用到 Activity.getIntent 来存储一些数据, Intent 中持有一个 Bundle 对象可以存储一些数据,

举个例子

我们需要使用 ActivityLifecycleCallbacks 实现给所有 Activity 执行 ButterKnife.bind(activity)

Bundle 中可以存储 Parcelable 对象

public class ActivityBean extends Parcelable {
	private Unbinder unbinder;
	public void setUnbinder(Unbinder unbinder){
		thid.unbinder = unbinder;
	}
	
	public Unbinder getUnbinder(){
		return unbinder;
	}
}

ActivityLifecycleCallbacks 执行对应逻辑

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                ActivityBean bean = new ActivityBean();
            	Unbinder unbinder = ButterKnife.bind(activity);
            	bean.setUnbinder(unbinder);
                activity.getIntent().putExtra("ActivityBean", bean);
           }

            ...
            
            @Override
            public void onActivityDestroyed(Activity activity) {
				ActivityBean bean = activity.getIntent().getParcelableExtra("ActivityBean");
				bean.getUnbinder().unbind();
            }
        }
            
        });
    }
}

需要Activity初始化某些事,或者提供某些数据

BaseActivity 有些时候需要,子 Activity 实现某些方法,或者提供某些数据,如需要子 Activity 实现 initView 返回 setContentView() 中的布局 ID ,实现 initData 初始化一些数据,这样就可以不需要 Activity 再重写 onCreate ,达到规范的目的, 这样使用 ActivityLifecycleCallbacks 同样能做到,那我该怎么做呢?

只需要 Activity 实现某个自定义接口

public interface IActivity {

    int initView();

    void initData();

}

然后在 ActivityLifecycleCallbacksonActivityCreated 中调用这些方法,就可以实现

public class WEApplication extends BaseApplication{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            if (activity instanceof IActivity) {
               activity.setContentView(((IActivity)activity).initView());
               ((IActivity)activity).initData();          
            }
              
           }

            ...
       
        }
            
        });
    }
}

注意事项

由于 ActivityLifecycleCallbacks 中所有方法的调用时机都是在 Activity 对应生命周期的 Super 方法中进行的,所以在 ActivityonCreate 方法中使用 setContentView 必须在 super.onCreate(savedInstanceState); 之前,不然在 onActivityCreated 方法中 findViewById 会发现找不到

@Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_home);
        super.onCreate(savedInstanceState);
    }


也可以结合上面的方式使用自定义接口,调用 initView 后,在 findViewById() 找到 ToolBar

一不小心实现了这么多以前必须写到 BaseActivity 中通过继承才能达到的功能,发现没有 BaseActivity ,也并没有什么不舒服的地方,突然感觉自己好牛逼 🤒,赶快关注我吧,虽然我并不会经常更新博客,但是我更新的文章在质量上绝对有保证!

总结

值得注意的是 ActivityLifecycleCallbacks 可以注册多个,可以针对不同情况添加各种 ActivityLifecycleCallbacks 按照需要进行组合从而达到不同的需求 ,和 OkhttpInterceptor 类似

现在整个 App 的所有 ActivityFragment 只要生命周期调用都会被拦截,应用到我框架上就可以动态的向所有 ActivityFragment 的对应生命周期插入任意代码,比如说 LeakCanaryRefWatcher.watch(fragment) 也可以直接插入到三方库的 Fragment 中,并且如果代码有任何改动也不用再去改基类,有点Aop的意思

以上提到的思想以及解决方案已经使用到了我的 MVPArms 框架中,想知道更详细的用法可以去看看我的框架实现,我上面提到的所有的实现,其实都是最简单的一些需求,相信已经颠覆了以前的实现方式了,而且更加优雅(因为我是站在三方库设计者的角度提出这个功能的,我必须将惟一继承的机会留给其他 Activity ,你如果相较于之前的方式觉得不够优雅,那就当我没说),更不用担心 Java 单继承的束缚

但是千万不要以为, ActivityLifecycleCallbacks 就只能实现这些简单的需求,它还可以用到更多更复杂的功能之上,还是我之前的话, 只要敢于尝试,你想象力有多丰富,这里就有多强大 ,思想以及解决方案已经介绍的很清楚了,至于更多需求的实现就靠大家去尝试咯,虽然我不敢保证以前封装 BaseActivity 通过继承实现的所有功能都能被 ActivityLifecycleCallbacks 所替代,但是我想大多数功能还是能实现的

以前大家都是分享自己封装的 BaseActivity ,说不定有一天大家就开发分享自己写的 ActivityLifecycleCallbacks 呢! 当以前的方式已经不能满足我们的需求,敢于跳出传统的方式,尝试不同的解决方案,才能扩宽自己的视野,增长自己的技术, MVPArms 正在不断的努力着!


这里还要说一句,每个人的思路不一样,考虑的角度也不一样,你认同我也好, 不认同我也好,都不会影响我的脚步,至少我是在用我的思路创新,解决一些我认为有必要解决的问题,和上一篇的文章一样,我就是喜欢使用不一样的思路解决同样的问题,不管你是否觉得可行,我至少用这个你觉得不可行的思路实现了我想达到的效果,仁者见仁智者见智,如果我们思维没有碰撞,那也请珍惜我的劳动成果

对于一些评论我再说一句, registerActivityLifecycleCallbacks() 内部是把 ActivityLifecycleCallbacks 加到一个集合中,所以 ActivityLifecycleCallbacks 可以添加多个,并且 ActivityLifecycleCallbacks 只是在项目初始化的时候被装到集合中,并不会初始化任何东西,和添加监听器一个道理,使用的是观察者模式,所以不要说 Application 代码这么多会怎么怎么样, OkhttpInterceptor 的代码更多,也是在 Okhttp 初始化时被添加,你觉得会有什么影响吗?


Hello 我叫Jessyan,如果您喜欢我的文章,可以在以下平台关注我

– The end