使用 promise 重构 Android 异步代码

news2024/11/26 2:25:11

背景

业务当中写Android异步任务一直是一项挑战,以往的回调和线程管理方式比较复杂和繁琐,造成代码难以维护和阅读。在前端领域中JavaScript其实也面临同样的问题,Promise 就是它的比较主流的一种解法。 在尝试使用Promise之前我们也针对Android现有的一些异步做了详细的对比。

文章思维导图

image.png

What:什么是Promise?

对于Android开发的同学,可能很多人不太熟悉Promise,它主要是前端的实践,所以先解析概念。
Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。

最简单例子(JavaScript)

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
}).then(function(value) {
    console.log('resolved.');
}).catch(function(error) {
    console.log('发生错误!', error);
});

实例化一个Promise对象,构造函数接受一个函数作为参数,该参数分别是resolvereject
resolve函数:将Promise 对象状态从pending 变成 resolved
reject函数:将Promise 对象状态从 pending 变成 rejected
then函数:回调 resolved状态的结果
catch函数:回调 rejected状态的结果

可以看到Promise的状态是非常简单且清晰的,这也让它在实现异步编程减少很多认知负担。

Why:为什么要考虑引入Promise

前面说的Promise 不就是 JavaScript 异步编程的一种思想吗,那这跟 Android 开发有什么关系? 虽然前端终端领域有所不同,但面临的问题其实是大同小异的,比如常见的异步回调导致回调地狱,逻辑处理不连贯等问题。
从事Android开发的同学应该对以下异步编程场景比较熟悉:

  • 单个网络请求
  • 多个网络请求竞速
  • 等待多个异步任务返回结果
  • 异步任务回调
  • 超时处理
  • 定时轮询

这里可以停顿思考一下,如果利用 Android常规的方式去实现以上场景,你会怎么做?你的脑子可能有以下解决方案:

  • 使用 Thread 创建
  • 使用 Thread + Looper + Handler
  • 使用 Android 原生 AsyncTask
  • 使用 HandlerThread
  • 使用 IntentService
  • 使用 线程池
  • 使用 RxJava 框架

以上方案都能在Android中实现异步任务处理,但或多或少存在一些问题和适用场景,我们详细剖析下各自的优缺点:
image.png
通过不同的异步实现方式的对比,可以发现每种实现方式都有适用场景,我们面对业务复杂度也是不一样的,每一种解决方案都是为了降低业务复杂度,用更低成本的方式来编码,但我们也知道代码写出来是给人看的,是需要持续迭代和维护,类似RxJava 这种框架于我们而言太复杂了,繁琐的操作符容易写出不易维护的代码,简单易理解应该是更好的追求,而不是炫技,所以我们才会探索用更轻量更简洁的编码方式来提升团队的代码一致性,就目前而言使用 Promise 来写代码将会有以下好处:

  • 解决回调地狱:Promise 可以把一层层嵌套的 callback 变成  .then().then()... ,从而使代码编写和阅读更直观
  • 易于处理错误:Promise 比 callback 在错误处理上更清晰直观
  • 非常容易编写多个异步操作的代码

How:怎么使用 Promise 重构业务代码?

这里由于我们的Java版本的Promise组件未开源,所以本部分只分析重构Case使用案例。

重构case1: 如何实现一个带超时的网络接口请求?

这是一段未重构前的获取付款码的异步代码:


可以看到以上代码存在以下问题:

  • 需要定义异步回调接口
  • 很多 if-else 判断,圈复杂度较高
  • 业务实现了一个超时类,为了不受网络库默认超时影响
  • 逻辑不够连贯,不易于维护

使用 Promise重构后:


可以看到有以下变化:

  • 消除了异步回调接口,链式调用让逻辑更连贯更清晰了
  • 通过 Promise 包装了网络请求调用,统一返回 Promise
  • 指定了 Promise 超时时间,无需额外实现繁琐的超时逻辑
  • 通过 validate 方法 替代 if - else 的判断,如果需要还可以定义校验规则
  • 统一处理异常错误,逻辑变得更加完备

重构case2:如何更优雅的实现长链接降级短链接?

重构前的做法:


代码存在以下问题:

  • 处理长链接请求超时,通过回调再处理降级逻辑
  • 使用Handler实现定时器轮询请求异步结果并处理回调
  • 处理各种逻辑判断,代码难以维护
  • 不易于模拟超时降级,代码可测试性差

使用Promise重构后:


第一个Promise处理长链接Push监听 ,设置5s超时,超时异常发生回调except方法,判断throwable 类型,如果为PromiseTimeoutException实例对象,则执行降级短链接。短链接是另外一个Promise,通过这种方式将逻辑都完全结果,代码不会割裂,逻辑更连贯。
短链接轮训查单逻辑使用Promise实现:

  • 最外层Promise,控制整体的超时,即不管轮询的结果如何,超过限定时间直接给定失败结果
  • Promise.delay(),这个比较细节,我们认定500ms轮询一定不会返回结果,则通过延迟的方式来减少一次轮询请求
  • Promise.retry(),真正重试的逻辑,限定了最多重试次数和延时逻辑,RetryStrategy定义的是重试的策略,延迟(delay)多少和满足怎样的条件(condition)才允许重试

这段代码把复杂的延时、条件判断、重试策略都通过Promise这个框架实现了,少了很多临时变量,代码量更少,逻辑更清晰。

重构case3:实现 iLink Push支付消息和短链接轮训查单竞速

后面针对降级策略重构成竞速模型,采用Promise.any很轻松得实现代码重构,代码如下图所示。

总结

本文提供一种异步编程的思路,借鉴了Promise思想来重构了Android的异步代码。通过Promise组件提供的多种并发模型能够更优雅的解决绝大部分的场景需求。

防踩坑指南

如果跟Activity或Fragment生命周期绑定,需要在生命周期结束时,取消掉promise的线程运行,否则可能会有内存泄露;这里可以采用AbortController来实现更优雅的中断 Promise。

并发模型

● 多任务并行请求
Promise.all():接受任意个Promise对象,并发执行异步任务。全部任务成功,有一个失败则视为整体失败。
Promise.allSettled(): 任务优先,所有任务必须执行完毕,永远不会进入失败状态。
Promise.any():接受任意个Promise对象,并发执行异步任务。等待其中一个成功即为成功,全部任务失败则进入错误状态,输出错误列表。
● 多任务竞速场景
Promise.race(): 接受任意个Promise对象,并发执行异步任务。时间是第一优先级,多个任务以最先返回的那个结果为准,此结果成功即为整体成功,失败则为整体失败。

扩展思考

  1. Promise 最佳实践
  1. 避免过长的链式调用:虽然Promise可以通过链式调用来避免回调地狱,但是如果Promise的链过长,代码的可读性和维护性也会变差。
  2. 及时针对Promise进行abort操作:Promise使用不当可能会造成内存泄露,比如未调用abort,页面取消未及时销毁proimse。
  3. 需要处理except异常回调,处理PromiseException.
  4. 可以使用validation来实现规则校验,减少if-else的规则判断
  1. Java Promise 组件实现原理
  1. 状态机实现(pending、fulfilled、rejected)
  2. 默认使用 ForkJoinPool 线程池,适合计算密集型任务。针对阻塞IO类型,可以使用内置ThreadPerTaskExecutor 简单线程池模型。
  1. Promise vs Kotlin协程

Promise 链式调用,代码清晰,上手成本较低;底层实现仍然是线程,通过线程池管理线程调度
Koitlin 协程,更轻量的线程,使用比较灵活,可以由开发者控制,比如挂起和恢复
刷掌业务相对比较简单,轻量的操作比较少,所以使用基本的线程池就能满足需求,如果需要频繁创建线程和切换,可以考虑使用协程来减少线程池的开销。

  1. 可测试性的思考

根据 Promise 的特点,可以通过Mock状态(resolve、reject、outTime)来实现模拟成功,拒绝、超时;
实现思路:
● 自定义注解类辅助定位Hook点
● 使用ASM字节码对Promise 进行代码插桩

附录

● Promise - JavaScript | MDN
● Promises/A+

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1188833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker镜像原理之联合文件系统

镜像是什么? 镜像是一种轻量级、可执行的独立软件保,用来打包软件运行环境和基于运行环境开发的软件 他包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件 所有应用都不要服务器去部署,都可以直接打包 do…

Docker快速搭建Drupal内容管理系统并远程访问

🎬 鸽芷咕:个人主页 🔥个人专栏:《Linux深造日志》《C干货基地》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal…

C语言实现将一个数组逆序输出,使用指针数组操作

完整代码&#xff1a; // 将一个数组逆序输出&#xff0c;使用指针数组操作 #include<stdio.h>//将一个数组逆序输出 void reverse(int *arr,int len){//头指针int *startarr;//尾指针int *endarrlen-1;//通过交换数组中前后所有的数&#xff0c;来使数组逆序while (sta…

多无人机在线路径规划的新算法

南京航空航天大学自动化学院使用NOKOV度量动作捕捉系统获取多架无人机的精确位置信息&#xff0c;实现多架无人机协同实时路径规划。 研究背景 近年来&#xff0c;无人机越来越多地应用于执行战场侦察、目标识别、跟踪打击等任务。 由多架无人机协同执行任务&#xff0c;通过…

彻底删除Ubuntu双系统(联想小新2022)

彻底卸载Ubuntu双系统 以里联想小新pro16 i9-12900h为例子 把开机启动项设为默认Windows启动 以联想电脑为例子&#xff0c;关机后一直点击Fn F2进入Bios把windows启动项移到最上面&#xff0c;这样可以开机默认启动windows了删除ubuntu系统分区 使用磁盘管理软件 DiskGeniu…

《C++ Primer》第8章 IO库

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 8.1 IO类&#xff08;P278&#xff09; 我们目前使用过的 IO 对象&#xff08;cin 、cout&#xff09;都是关联到控制台窗口、操纵 char 数据的。有时&#xff0c;我们需要对命名文件或者 string IO 操作。…

iOS如何通过在线状态来监听其他设备登录的状态

前提条件 1、完成 3.9.1 或以上版本 SDK 初始化 2、了解环信即时通讯 IM API 的 使用限制。 3、已联系商务开通在线状态订阅功能 实现方法 你可以通过调用 subscribe 方法订阅自己的在线状态&#xff0c;从而可以监听到其他设备在登录和离线时的回调&#xff0c;示例代码如下…

【源码】自制链接表管理器

hi&#xff0c;大家好呀&#xff01; 前几天更新了个视频&#xff0c;教大家做了一个链接表的管理器&#xff0c;今天把文字内容给到大家&#xff0c;至于什么原因需要自己做一个链接表管理器&#xff0c;我在视频中有讲到&#xff0c;因为系统自带的链接表管理器没有筛选功能…

回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测

回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测 目录 回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现PCA-PLS主成分降维结合偏小二乘回归预测&#xff08;完整源码和数据) 1.输…

直面LED Driver测试挑战,助力显示屏行业变中求变!

杭州亚运会开幕式惊艳世界&#xff0c;引发社会各界一致赞誉&#xff01;在大气浪漫的舞台效果中&#xff0c;LED屏、裸眼3D屏凭借“硬核科技”出圈&#xff0c;为大家带来科技、活力、诗意的“中国式浪漫”观赏体验。而这美轮美奂的LED呈现效果背后&#xff0c;主要依靠的是LE…

Echarts仪表盘自定义数值样式

需求描述 原图样式 需求样式 解决方案 {name: 数值,type: gauge,startAngle: 180,endAngle: 0,radius: 30%,center: [50%, 55%], // 默认全局居中 min: 0,max: 4,zlevel: 2,splitNumber: 0,axisLine: { // 坐标轴线 lineStyle: {color: [[1, ]], // 属性lineStyle控制线…

TSINGSEE视频智能分析系统AI算法针对遛狗不拴绳行为的监管方案

一、背景与需求 近期&#xff0c;一则恶犬咬伤女童的新闻上了热搜&#xff0c;因为狗主人没有给狗拴绳&#xff0c;导致小区内一女童被大型犬撕咬&#xff0c;女童全身多处咬伤&#xff0c;已入院治疗。该新闻曝出后立刻引发社会关注。遛狗不拴绳行为也再一次引发热议。因为狗主…

spring基础,编写第一个程序

spring基础 前言SpringSpring概述Spring的8大模块Spring特点学习spring6软件版本Spring的入门程序第一个Spring程序 小结 前言 控制反转&#xff0c;是面向对象编程中的一种设计思想&#xff0c;可以用来降低代码之间的耦合度&#xff0c;符合依赖倒置原则。 控制反转的核心是…

06、SpringBoot+微信支付 -->商户定时查订单状态、用户取消订单(关闭订单API)、查询订单API--到微信支付平台查询订单

目录 Native 下单、取消订单订单功能完善需求1&#xff1a;商户定时查单前端代码&#xff1a;后端代码&#xff1a;测试&#xff1a;swagger 测试&#xff1a; 需求2&#xff1a;用户取消订单&#xff08;关闭订单API&#xff09;需求&#xff1a;代码&#xff1a;前端&#xf…

Linux安装Python3.10与部署flask项目实战详细记录

java开发新手入门Python,创建flask后端服务对外提供访问.记录一下在阿里云服务器部署flask项目的操作过程,简单介绍一下使用的阿里云服务器系统配置:ubantu16.04,其他内核版本操作部分命令会有所区别,下面开始详细操作过程! 1.pycharm创建flask项目并打包 2.Python3.…

grafana 密码忘记怎么重置

1.重置密码的命令&#xff1a; grafana-cli admin reset-admin-password 新的密码

【JavaEESpring】Spring, Spring Boot 和Spring MVC的关系以及区别

Spring, Spring Boot 和Spring MVC的关系以及区别 Spring&#xff1a;简单来说&#xff0c;Spring 是一个开发应用框架&#xff0c;什么样的框架呢&#xff1f;轻量级、一站式、模块化&#xff0c;其目的是用于简化企业级应用程序开发 Spring的主要功能: 管理对象, 以及对象之…

收银系统有哪些分类 收银软件有哪些类型

收银系统有以下这几种分类&#xff0c;你都清楚么&#xff1a; 一、按是否能数据共享可分为&#xff1a;单机版收银系统、网络版收银系统 这里需要注意&#xff0c;很多地方说单机也能收银&#xff0c;其实单机和网络版只是数据存储地方不同&#xff0c;单机版需要在线收银还是…

2023第六届泰迪杯数据分析,第五届泰迪杯数据分析技能赛B题源码图片分享

需要B题源码以及第六届带队”指导“请私信本人&#xff0c;团队包含技能赛双一等&#xff0c;数学建模省一&#xff0c;泰迪杯挖掘国一&#xff0c;研究生队友。 去年一等作品可视化图如下&#xff0c;私信获取源码

javaSE学习笔记(五)集合框架-Collection,List,Set,Map,HashMap,Hashtable,ConcurrentHashMap

目录 四、集合框架 1.集合概述 集合的作用 集合和数组的区别 集合继承体系 数组和链表 数组集合 链表集合 2.Collection 方法 集合遍历 并发修改异常 3.List List集合的特有功能&#xff08;核心是索引&#xff09; 集合遍历 并发修改异常产生解决方案ListIterato…