RN拆包解決方案(三) RCTBridge緩存

場(chǎng)景

目前項(xiàng)目中RN模塊已經(jīng)改造成了拆包方式,每次在初始化的時(shí)候先加載common代碼,然后進(jìn)入相具體業(yè)務(wù)頁面加載business代碼,雖然business的代碼只有幾十k左右,但是沒有預(yù)加載的情況下,等待加載完畢也需要一些時(shí)間,雖然是瞬間的,用戶還是能感受到頁面白屏情況;而且,在用戶關(guān)閉頁面后再次打開相同頁面是需要重新創(chuàng)建RCTBridge的,沒有復(fù)用之前的RCTBridge實(shí)例;因此,不妨考慮下使用緩存管理的模式,將已經(jīng)加載完的RCTBridge緩存起來,供相同模塊所在的頁面復(fù)用,用戶在二次打開的時(shí)候沒有任何白屏感知,和原生交互完全一致。

緩存帶來的問題

  • RCTBridge實(shí)例在加載完common+business代碼后會(huì)占用2M內(nèi)存,如果實(shí)例保存太多,將會(huì)出現(xiàn)內(nèi)存OOM
  • 因?yàn)槭菑?fù)用RCTBridge實(shí)例的,js代碼需要避免使用全局變量,因?yàn)橐坏┒芜M(jìn)入頁面已經(jīng)改變的值是不會(huì)重置的

緩存策略

使用hashtable+雙向鏈表來存儲(chǔ)RCTBridge實(shí)例,count或cost超出閾值,會(huì)按照LRU策略清理沒有使用的RCTBridge實(shí)例;LRU是近期最少使用的算法,它的核心思想是當(dāng)緩存滿時(shí),會(huì)優(yōu)先淘汰那些近期最少使用的緩存對(duì)象,提升緩存命中;我們先來看下LRU策略運(yùn)作方式:


示例

LRU緩存實(shí)現(xiàn)類似于一個(gè)特殊的棧,把訪問過的元素放置到棧頂(若棧中存在,則更新至棧頂;若棧中不存在則直接入棧),然后如果棧中元素?cái)?shù)量超過限定值,則刪除棧底元素(即最近最少使用的元素)

存儲(chǔ)結(jié)構(gòu)

使用hashtable可以在在時(shí)間復(fù)雜度O(1)內(nèi)完成數(shù)據(jù)訪問;使用雙向鏈表實(shí)現(xiàn),可以在時(shí)間復(fù)雜度O(1)內(nèi)完成刪除和插入的操作;保存方式如下:


示例

key和value

  • key: businessURL + commonURL
  • value: LinkedNode

按照拆包的模式下,common包和business包會(huì)分別進(jìn)行codepush熱更新檢查,詳見codepush支持多bundle更新重構(gòu),那么安裝完畢以后兩者其中之一的bundle路徑有可能改變,那么原來通過緩存存儲(chǔ)的RCTBridge和現(xiàn)有common+business加載完成的RCTBridge是有差異的,不能單純的只靠bundle名來作為key值,而必須通過businessURL + commonURL成對(duì)的bundle路徑作為key值。
使用LinkedNode作為雙向鏈表的數(shù)據(jù)載體,存儲(chǔ)前后關(guān)系,頭部數(shù)據(jù)prev和尾部數(shù)據(jù)的next值為空;具體結(jié)構(gòu)如下:

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

實(shí)際情況分析

傳統(tǒng)的memryCache在存入新的數(shù)據(jù)時(shí),count或cost超出閾值的情況下使用LRU淘汰策略,如果對(duì)象釋放不需要再額外執(zhí)行invalidate等代碼,一般都是由系統(tǒng)自帶的GC自動(dòng)處理內(nèi)存,就算有別的控制器正在使用也不會(huì)立即釋放,只有當(dāng)對(duì)象處于無引用狀態(tài)下才會(huì)參與釋放流程;然而在我們的拆包項(xiàng)目中,RCTBridge實(shí)例被創(chuàng)建并使用后,內(nèi)部創(chuàng)建了一些計(jì)時(shí)器等模塊造成循環(huán)引用,需要執(zhí)行invalidate才能斷開釋放內(nèi)存,如果某個(gè)控制器正在使用RCTBridge實(shí)例,且剛好出現(xiàn)LRU淘汰策略執(zhí)行了invalidate,那么該控制器就會(huì)出現(xiàn)意想不到的后果,所以必須手動(dòng)對(duì)RCTBridge實(shí)例的使用情況進(jìn)行計(jì)算,確保沒有控制器使用的情況下才會(huì)參與LRU淘汰策略;

既然是手動(dòng)對(duì)RCTBridge實(shí)例的使用情況進(jìn)行計(jì)算,那么執(zhí)行LRU淘汰策略的時(shí)機(jī)改成當(dāng)某個(gè)實(shí)例使用次數(shù)變成0的情況下執(zhí)行更加合理,試想,每次存入新的實(shí)例情況下,其他實(shí)例也處于正在使用狀態(tài)就算執(zhí)行淘汰策略也是浪費(fèi);

實(shí)現(xiàn)流程

(1)每次存儲(chǔ)的時(shí)候?qū)?dāng)前LinkedNode記錄在棧頂,記錄最后一個(gè)LinkedNode;
(2)當(dāng)某個(gè)RCTBridge實(shí)例使用次數(shù)變成0的情況下觸發(fā)檢查緩存使用情況,從最后一個(gè)LinkedNode向前遍歷,一旦找到未使用的RCTBridge實(shí)例將其清理且調(diào)用invalidate;
(3)當(dāng)執(zhí)行LRU淘汰策略以后,需要對(duì)刪除的LinkedNode前后兩個(gè)LinkedNode重新創(chuàng)建鏈接關(guān)系,如果刪除的是最后一個(gè)LinkedNode需要將前一個(gè)LinkedNode作為最后一個(gè)標(biāo)識(shí)。

LRU算法注意要點(diǎn)

  • 在存入數(shù)據(jù)的時(shí)候,發(fā)現(xiàn)key已經(jīng)存在,直接獲取到LinkedNode將其記錄在棧頂,放置到棧頂以后要將prev清空
  • 如發(fā)現(xiàn)需要置頂?shù)腖inkedNode原先處于棧尾,將LinkedNode的prev數(shù)據(jù)作為新的棧尾
  • 雙向鏈表每次重新建立鏈接關(guān)系,需要考慮線程安全問題,通過加鎖的方式解決
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、簡(jiǎn)介 Ehcache是一個(gè)用Java實(shí)現(xiàn)的使用簡(jiǎn)單,高速,實(shí)現(xiàn)線程安全的緩存管理類庫,ehcache提供了用內(nèi)...
    小程故事多閱讀 44,483評(píng)論 9 59
  • 理論總結(jié) 它要解決什么樣的問題? 數(shù)據(jù)的訪問、存取、計(jì)算太慢、太不穩(wěn)定、太消耗資源,同時(shí),這樣的操作存在重復(fù)性。因...
    jiangmo閱讀 3,120評(píng)論 0 11
  • 1.List<T>和List<?>的區(qū)別? List<T>是泛型,List<?>是泛型類型通配符,相當(dāng)于List<...
    小酷哥閱讀 976評(píng)論 0 2
  • 一 基礎(chǔ)篇 1.1 Java基礎(chǔ) 面向?qū)ο蟮奶卣鞒橄?將一類對(duì)象的共同特征總結(jié)出來構(gòu)建類的過程。繼承:對(duì)已有類的一...
    essential_note閱讀 759評(píng)論 0 0
  • 在上篇文章01 初識(shí)緩存-了解緩存中簡(jiǎn)單了介紹了下緩存的歷程以及幾種常見的技術(shù)進(jìn)行簡(jiǎn)單介紹,本著學(xué)習(xí)的目的本節(jié)針對(duì)...
    花神子閱讀 862評(píng)論 0 4

友情鏈接更多精彩內(nèi)容