RN 到底能够实现那些功能,有哪些功能不需要原生就能实现?RN 中的定时任务怎么处理?怎么实现才能不影响 APP 的运行?本场 Chat 将会带领大家完成一个轻量级的定时任务,解决 RN 中的定时任务执行问题。
本场 Chat 主要内容:
- 注册定时任务的入口;
- 启动定时任务循环;
- 实现定时任务基类;
- 测试定时任务;
- 更多扩展想法。
尽管使用的 RN 框架已经具备了很多功能,一些高级的功能也能够使用原生接入的方法使得 js 开发的情况能够实现更加高级的功能。
假如你需要一个定时任务,同时这个定时任务又不想依赖原生的方法。这个时候你就需要考虑一下利用 js 的方式来实现定时任务了。
单纯的定时器其实也能够达到这些目的,但是定时器很耗性能不说,在有些时候甚至会变成灾难。
简单来说,定时任务基本跟 UI 无关,没必要放在主线程去做。一个单独的线程去搞定才是更合理的做法。
注册另外一个入口RN 的 AppRegistry
对象包含管理整个 APP 入口的方法,同时也包含了启动一个单独的进程的方法。这里利用这个这个放启动一个单独的处理异步任务的进程。
在启动页或者其他合适的地方,注册并启动它。这里可以使用 runApplication
方法启动并传入一些参数。
这里注意一下,这个视图里的东西千万不要直接刷新UI相关的数据,所有的内容都应该是内存相关的。
//注册AppRegistry.registerRunnable('RunableTask', TaskRun)//启动AppRegistry.runApplication('RunableTask', {});
简单构成
我们打算做一个定时任务,它有以下几个功能和特点:
- 全局内只会存在一个定时对象在执行。
- 定时任务精确到秒就可以了。
- 需要做的内容可以自由定制。
在我们理清了自己的想法之后就要动手来做了。定时任务的第一步就是不断执行,判断时机是否到来。
调用 init
方法,启动我们自己的定时器刷新方法。这里我暂时定义 1 秒刷新一次,低于 1 秒间隔的定时器将不在执行。这里也是为了防止部分人设置低间隔的定时器导致 APP 崩溃。
使用 react - native 内部提供的 requestAnimationFrame
方法,每次刷新的时候先判断一次是否可以执行任务。
const tasks = new Map();let currDate = Date.now();/** * 初始化 */exports.init = function () { global.requestAnimationFrame(run);}/** * 执行,每秒执行一次 * 保证秒级工作正确 */function run() { const now = Date.now(); if (now > currDate + 1000) { currDate = now; tasks.forEach(item => item.preStart(now)); } global.requestAnimationFrame(run);}
tips:这里介绍另外一个思路。将任务要执行的时间化成一枚时间戳,放在任务队列里。这个队列是按照从小到大的顺序排列。 每次都判断一次第一项的时间是否到了。如果到了就取出来并执行相应的任务。 这个任务好处就是计算少,坏处就是容易出现不稳定的情况。
任务基类为了使得我们的任务可以自定义化,所以这里规定每个任务必须继承自我们自己开发的基类。这个基类了时间的计算、任务循环、任务执行等几个内部方法。继承之后只需要管任务本身即可,也可以通过覆写的方式修改任务执行时机等。
所有的任务都继承这个基类,定时器运行的时候也会通过基类内部的方法去判断是否需要执行方法。这里对外实现了一个 timer 是时间间隔,单位是毫秒。还有一个 start 方法用来给每个任务使用的。
通过每次执行 preStart 方法判断下次执行的时间到了没有来决定是否执行自定义方法。如果到了同时还要计算下一次的时间并存好。
/** * 基础工作类 */class Jobs { //给个名字 name = "base"; /** * 下次执行时间戳 */ nextTime = 0; /** * 重载:时间间隔 */ timer = 0; /** * 预启动 */ preStart(now) { if (this.timer < 1000) return; if (this.nextTime > now) return; if (this.nextTime === 0) return this.nextTime = now + this.timer; this.nextTime += this.timer; this.start(now); } /** * 重载:执行一次设置的方法 */ start() { }}
tips:喜欢方法的也可以将 class 的方式转化成方法的形式,只需要传入时间间隔和任务执行方法即可。
实现并加入这里还需要实现一个加入到任务执行的逻辑队列中。加入和取消可以有。需要的话还可以自己实现一个只执行一次的方法。
将设置好的任务加入全局的 map 示例中,运行的时候会从这个示例中取需要的任务出来。
/** * 添加一个工作 * @param {*} name 名称 * @param {*} time 时间 * @param {*} fn 执行函数 */const addJob = (name, Job) => tasks.set(name, new Job())exports.addJob = addJob;/** * 取消任务 * @param {*} name */const cancelJob = name => tasks.delete(name);exports.cancelJob = cancelJob;
测试任务
这里模拟一次定时发送统计消息。
另外一个地方实现了将每次需要发送的消息存入内存中,这里只负责取出需要发送的消息。
设置 3 秒执行一次发送任务,如果数量比较少就修改定时任务的间隔。后面的任务延迟发送。
如果后台接收一次大量的数据,也可以将多次统计合并为一次统计,请求的个数会更少一些。
/** * 定时触发统计方法 */class TrackJob extends Jobs { timer = 3000; start() { let list = TackList(); if (list.length > 10) { this.timer = 3000; } else { this.timer = 5000; } list.forEach(item => { request.trackData(item.url, item.data); log("埋点事件", item.data); }) }}addJob("Track", TrackJob);
更多扩展
目前的任务已经满足初步的定时任务方式了。不过做人不应该想着满足。这里就稍微再想想更多的扩展方式。
首先当然是调用原生模块了。毕竟再怎么做也是内部 js 循环,性能上还是比较差劲的。原生只需要不断触发回调,js 根据情况执行不同的方法即可。又兼顾性能,又有更多的自由。
其次还有固定日期执行。老是一个时间间隔毕竟还不全面,只有加入固定时间执行任务才能算的上一个完整的定时任务。
以及任务的自主取消。毕竟并不是所有的任务都是需要一直不断执行的,可能有些任务只需要执行固定几次就不再执行了。让任务自己取消自己,岂不快哉。
最后,希望这篇文章能够启发一些智慧,给大家带来一些不同的想法。
本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。
阅读全文: http://gitbook.cn/gitchat/activity/5b600ee7baffd578157e4dd4
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。