移动app中展示的数据多数都是通过服务器接口获取的,当接口数据与用户相关时,服务端接口会要求客户端把用户信息通过接口发送到服务器。普遍的做法是把用户登录后的token数据发送给服务器的接口。考虑到安全问题,token都有过期时间,token过期后服务端就不能通过这个token查询用户的具体信息了。为了刷新过期token,服务端会提供一个刷新token的接口给客户端使用。
private val refreshTokenFlow = flow { if (expiredToken) { cachedToken = serverApi.refreshToken("token-0") expiredToken = false } emit(cachedToken) }
expiredToken变量代表token是否过期,实际开发过程中这个变量的值应该根据过期时间计算得出的。cachedToken变量保存着最新的token值,请求用户相关信息的接口时可以把这个token传递给服务端用于查询。我们可以看到cachedToken的值是通过调用服务端提供的刷新接口获取的。这个flow最终发射的是最新的token值,同时我们也看到这个flow调用刷新接口的逻辑只有token过期时才会被调用。
private fun getDataFlow(token: String) = flow { emit(serverApi.getDataViaToken(token)) }
由于请求用户数据的接口依赖token的值,所以这个flow是通过方法生成的。flow的生成也比较简单,它直接调用服务端的接口并将数据发射出去。
private val userDataFlow = refreshTokenFlow.flatMapConcat{token-> getDataFlow(token) }
flatMapConcat方法将前面定义的两个流拼接在一起,这时我们要是收集拼接后的userDataFlow,refreshTokenFlow会被收集,flatMapConcat方法接收到 refreshTokenFlow发射的token后开始收集getDataFlow方法返回的flow。这样连个flow的依赖关系通过flatMapConcat完美实现了。
private val userDataFlow = refreshTokenFlow.flatMapConcat{token-> getDataFlow(token) }.retryWhen { cause, attempt -> if (attempt > 1) { false } else if(cause is InvalidTokenException) { expiredToken = true true }else{ false } }.catch { msgView.text = it.message }
基于第三步的flow拼接我们添加了retryWhen和cach两个块。retryWhen块用于实现重试机制,参数cause是前面流程发生的异常,参数attempt代表重试的次数,返回值true代表进行重试,返回值false代表不进行重试。在retryWhen块中我们可以通过cause的类型来判断是否要重试,当cause为InvalidTokenException时代表token过期,所以进行重试并且重置了token过期expiredToken。为了避免无限地进行重试,这里限制重试次数为一次。catch块处理不进行重试时的逻辑,一般会将出错的信息显示到界面上。
通过flow的方式实现token刷新机制相较于传统的方式有如下优势: