美文网首页
RN拆包解决方案(三) RCTBridge缓存

RN拆包解决方案(三) RCTBridge缓存

作者: gy先生 | 来源:发表于2020-01-21 12:35 被阅读0次

    场景

    目前项目中RN模块已经改造成了拆包方式,每次在初始化的时候先加载common代码,然后进入相具体业务页面加载business代码,虽然business的代码只有几十k左右,但是没有预加载的情况下,等待加载完毕也需要一些时间,虽然是瞬间的,用户还是能感受到页面白屏情况;而且,在用户关闭页面后再次打开相同页面是需要重新创建RCTBridge的,没有复用之前的RCTBridge实例;因此,不妨考虑下使用缓存管理的模式,将已经加载完的RCTBridge缓存起来,供相同模块所在的页面复用,用户在二次打开的时候没有任何白屏感知,和原生交互完全一致。

    缓存带来的问题

    • RCTBridge实例在加载完common+business代码后会占用2M内存,如果实例保存太多,将会出现内存OOM
    • 因为是复用RCTBridge实例的,js代码需要避免使用全局变量,因为一旦二次进入页面已经改变的值是不会重置的

    缓存策略

    使用hashtable+双向链表来存储RCTBridge实例,count或cost超出阈值,会按照LRU策略清理没有使用的RCTBridge实例;LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象,提升缓存命中;我们先来看下LRU策略运作方式:


    示例

    LRU缓存实现类似于一个特殊的栈,把访问过的元素放置到栈顶(若栈中存在,则更新至栈顶;若栈中不存在则直接入栈),然后如果栈中元素数量超过限定值,则删除栈底元素(即最近最少使用的元素)

    存储结构

    使用hashtable可以在在时间复杂度O(1)内完成数据访问;使用双向链表实现,可以在时间复杂度O(1)内完成删除和插入的操作;保存方式如下:


    示例

    key和value

    • key: businessURL + commonURL
    • value: LinkedNode

    按照拆包的模式下,common包和business包会分别进行codepush热更新检查,详见codepush支持多bundle更新重构,那么安装完毕以后两者其中之一的bundle路径有可能改变,那么原来通过缓存存储的RCTBridge和现有common+business加载完成的RCTBridge是有差异的,不能单纯的只靠bundle名来作为key值,而必须通过businessURL + commonURL成对的bundle路径作为key值。
    使用LinkedNode作为双向链表的数据载体,存储前后关系,头部数据prev和尾部数据的next值为空;具体结构如下:

    @interface LinkedNode : NSObject
        @property (nonatomic, copy)  NSString* key;
        @property (nonatomic, Strong)  RCTBridge* value;
        @property (nonatomic, Strong)  LinkedNode* next;
        @property (nonatomic, Strong)  LinkedNode* prev;
    @end
    

    实际情况分析

    传统的memryCache在存入新的数据时,count或cost超出阈值的情况下使用LRU淘汰策略,如果对象释放不需要再额外执行invalidate等代码,一般都是由系统自带的GC自动处理内存,就算有别的控制器正在使用也不会立即释放,只有当对象处于无引用状态下才会参与释放流程;然而在我们的拆包项目中,RCTBridge实例被创建并使用后,内部创建了一些计时器等模块造成循环引用,需要执行invalidate才能断开释放内存,如果某个控制器正在使用RCTBridge实例,且刚好出现LRU淘汰策略执行了invalidate,那么该控制器就会出现意想不到的后果,所以必须手动对RCTBridge实例的使用情况进行计算,确保没有控制器使用的情况下才会参与LRU淘汰策略;

    既然是手动对RCTBridge实例的使用情况进行计算,那么执行LRU淘汰策略的时机改成当某个实例使用次数变成0的情况下执行更加合理,试想,每次存入新的实例情况下,其他实例也处于正在使用状态就算执行淘汰策略也是浪费;

    实现流程

    (1)每次存储的时候将当前LinkedNode记录在栈顶,记录最后一个LinkedNode;
    (2)当某个RCTBridge实例使用次数变成0的情况下触发检查缓存使用情况,从最后一个LinkedNode向前遍历,一旦找到未使用的RCTBridge实例将其清理且调用invalidate;
    (3)当执行LRU淘汰策略以后,需要对删除的LinkedNode前后两个LinkedNode重新创建链接关系,如果删除的是最后一个LinkedNode需要将前一个LinkedNode作为最后一个标识。

    LRU算法注意要点

    • 在存入数据的时候,发现key已经存在,直接获取到LinkedNode将其记录在栈顶,放置到栈顶以后要将prev清空
    • 如发现需要置顶的LinkedNode原先处于栈尾,将LinkedNode的prev数据作为新的栈尾
    • 双向链表每次重新建立链接关系,需要考虑线程安全问题,通过加锁的方式解决

    相关文章

      网友评论

          本文标题:RN拆包解决方案(三) RCTBridge缓存

          本文链接:https://www.haomeiwen.com/subject/fokezctx.html