JSPatch 是騰訊微信團(tuán)隊(duì)牛人bang開(kāi)源的一種通過(guò)JavaScript調(diào)用iOS原生代碼來(lái)實(shí)現(xiàn)熱修復(fù)或者動(dòng)態(tài)添加功能模塊的開(kāi)源項(xiàng)目,代碼中的邏輯主要是如何通過(guò)JavaScript調(diào)用iOS原生代碼。
對(duì)于開(kāi)發(fā)者來(lái)說(shuō),如果想使用它,有兩種方式:
一是使用騰訊對(duì)應(yīng)JSPatch平臺(tái)和相應(yīng)sdk,
二是在源碼上添加與自己平臺(tái)(需要自己實(shí)現(xiàn))交互的邏輯,實(shí)現(xiàn)腳本的下發(fā)功能。
最簡(jiǎn)便的方式自然是第一種方案,這樣可以最快的接入這個(gè)功能,開(kāi)發(fā)量最小。因?yàn)榈诙N方案涉及到平臺(tái)的的代碼邏輯和客戶端的代碼邏輯,需要處理腳本的版本管理、安全傳輸、CS的鑒權(quán)等多方面內(nèi)容才能保證客戶端的腳本是安全的。本文也將主要是以第一種方案為例進(jìn)行介紹:
平臺(tái)和SDK怎么用?
JSPatch平臺(tái)需要首先注冊(cè)用戶app信息,然后會(huì)得到對(duì)應(yīng)的appkey。可以看到這些配置成功后的頁(yè)面(企業(yè)飛信是我們部門的產(chǎn)品,歡迎試用并吐槽), 可以看到appstore上的產(chǎn)品和appkey關(guān)聯(lián)起來(lái)(其實(shí)JSPatch的平臺(tái)不關(guān)注是否將腳本下發(fā)到了這個(gè)app,只是下發(fā)到這個(gè)appkey的請(qǐng)求源上),平臺(tái)左側(cè)邊欄的幾個(gè)功能方便跟蹤熱修復(fù)的動(dòng)態(tài)和歷史,右邊排列著每個(gè)熱修復(fù)的版本,點(diǎn)進(jìn)去可以針對(duì)這個(gè)版本下發(fā)新的腳本。
下載sdk,配置到自己app工程中, 根據(jù)平臺(tái)的wiki說(shuō)明,將相關(guān)使用的代碼邏輯添加到其中,要添加新的類來(lái)專門管理JSPatch sdk的接口調(diào)用邏輯,下面是調(diào)用實(shí)例,其中包含了本地調(diào)試、腳本下發(fā)和配置log、灰度下發(fā)等相關(guān)邏輯:
// QFHotfixManager.m
// 熱修復(fù)
//
// Created by 陳明明 on 2016/11/7.
// Copyright ? 2016年 中移(杭州)信息技術(shù)有限公司. All rights reserved.
//
#import "QFHotfixManager.h"
#import <JSPatchPlatform/JSPatch.h>
static const NSString *HotfixAppkey = @"63bdb20dcce4f4fb";
// 是否使用本地腳本
#define TestScriptInBundle (1)
@implementation QFHotfixManager
+ (void)runHotfixScript
{
[JSPatch setupLogger:^(NSString *msg) {
//msg 是 JSPatch log 字符串,用你自定義的logger打出
NSLog(@"JSPatch %@", msg);
}];
#ifdef TestScriptInBundle
[JSPatch testScriptInBundle];
#else
// 條件下發(fā)
[JSPatch setupUserData:@{@"userId": USER_PHONENUM}];
[JSPatch startWithAppKey:HotfixAppkey];
[JSPatch sync];
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
;
}];
#endif
#ifdef DEBUG
[JSPatch setupDevelopment];
#endif
}
@end
基礎(chǔ)設(shè)施建立完畢,我們可以通過(guò)一個(gè)簡(jiǎn)單的腳本驗(yàn)證下平臺(tái)下發(fā)到腳本部署全過(guò)程:
本地建立一個(gè)main.js,內(nèi)容為空,然后上傳到平臺(tái),建立一個(gè)版本號(hào),并將xcode中的版本號(hào)改為這個(gè)版本號(hào),然后啟動(dòng)工程,setupLogger方法打印log來(lái)跟蹤腳本下發(fā)和配置,log中會(huì)顯示下發(fā)的版本號(hào)等信息。
熱修復(fù)代碼如何寫?
流程沒(méi)問(wèn)題了,真的發(fā)生線上故障,我們?cè)撊绾螌憻嵝迯?fù)代碼?(如果有JavaScript基礎(chǔ)的話,會(huì)更輕松點(diǎn)。)其實(shí)相關(guān)信息在github上基本都有,
首先把基礎(chǔ)語(yǔ)法學(xué)好:
https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95#1-require
日常調(diào)試時(shí)如果有問(wèn)題,可以在看看有沒(méi)有相關(guān)信息:
https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
還有問(wèn)題可以在issue問(wèn)題集中搜索一下:
https://github.com/bang590/JSPatch/wiki/%E5%A6%82%E4%BD%95%E6%8E%92%E6%9F%A5%E9%97%AE%E9%A2%98
https://www.google.com.hk/search?q=私有變量%20site%3Agithub.com%2Fbang590%2FJSPatch
把其中的關(guān)鍵字 {私有變量} 替換成你想問(wèn)的問(wèn)題就可以了。
最后還是無(wú)法解決可以到qq群 207283178 中試試。
結(jié)合個(gè)人實(shí)踐,總結(jié)了從寫腳本到腳本發(fā)布到驗(yàn)證的流程將在下節(jié)一并展示。
相對(duì)完善的調(diào)試和發(fā)布流程是怎么樣的?
一般來(lái)說(shuō),使用熱修復(fù)來(lái)解決的線上故障是頻率較高的crash或嚴(yán)重影響業(yè)務(wù)的bug,而且熱修復(fù)面對(duì)的是最終用戶,我們要保證熱修復(fù)的準(zhǔn)確性,既包含可以確定地解決問(wèn)題,也包含不會(huì)導(dǎo)致其他問(wèn)題,不能通過(guò)下發(fā)多次腳本才最終解決,這樣對(duì)產(chǎn)品的信任度產(chǎn)生很大影響。所以這就需要一個(gè)相對(duì)完善的調(diào)試和發(fā)布流程,需要認(rèn)真對(duì)待這類bug,下圖展示大概流程,后面講詳細(xì)描述:
主要有三大階段:
第一階段:確定問(wèn)題階段
- 定位問(wèn)題復(fù)雜度,如果代碼改動(dòng)量巨大設(shè)計(jì)類多,這種修改可能會(huì)引入其他問(wèn)題,需要慎重考慮是否熱修復(fù)?是否下個(gè)版本修復(fù)?
- 確定問(wèn)題解決方式,盡量用相對(duì)簡(jiǎn)單,改動(dòng)代碼方式較少的方式,這樣可以避免影響面過(guò)大。但也要注意下個(gè)發(fā)布版本中的代碼要通過(guò)最好的改動(dòng)方式來(lái)解決。
這里也建議做好類中方法的封裝,盡量避免超長(zhǎng)方法的出現(xiàn),一方面不方便維護(hù),另一方面熱修復(fù)碰到這種超長(zhǎng)方法心里就罵那個(gè)方法的作者吧。 - 評(píng)估代碼改動(dòng)影響面并同步給測(cè)試,這一步一定要盡量細(xì)致,這樣測(cè)試才能通過(guò)更多的case全面的測(cè)試這個(gè)問(wèn)題,及是否影響其他方面。
第二階段:通過(guò)熱修復(fù)解決問(wèn)題
- 編寫腳本:我們首先通過(guò)JSPatch提供的mac工具JSPatchConvertor, 對(duì)要熱修復(fù)的代碼快速轉(zhuǎn)化一下;
- 這個(gè)工具還不完善,所以需要根據(jù)基礎(chǔ)語(yǔ)法review一遍,修改一遍;
- 再用在線工具驗(yàn)證語(yǔ)法準(zhǔn)確性;
- 開(kāi)發(fā)人員需要經(jīng)過(guò)本地調(diào)試,先驗(yàn)證熱修復(fù)腳本正確性及是否帶來(lái)其他問(wèn)題,
- 確定沒(méi)問(wèn)題,再通過(guò)SDK的開(kāi)發(fā)模式, 再一次來(lái)驗(yàn)證下發(fā)到客戶端的腳本正確性;
- 讓測(cè)試人員測(cè)試條件發(fā)布(按人)的版本:條件使用測(cè)試的賬號(hào),測(cè)試通過(guò);
- (更穩(wěn)妥可以先灰度下發(fā)(按比例))全量下發(fā),測(cè)試人員再次驗(yàn)證其他賬號(hào),并看用戶的實(shí)際使用情況是否已經(jīng)修復(fù)。
全量下發(fā)時(shí),標(biāo)注好描述,便于后期在歷史補(bǔ)丁中快速定位發(fā)布的腳本。
第三階段:善后
- 雖然平臺(tái)有歷史補(bǔ)丁可以瀏覽每次發(fā)布的補(bǔ)丁,但我們也要做好熱修復(fù)腳本的代碼管理,將其納入svn或git記錄中。
- 針對(duì)熱修復(fù)的問(wèn)題,我們native上也需要徹底用更好維護(hù)的方式解決,app的下個(gè)發(fā)布store版本測(cè)試人員驗(yàn)證這個(gè)問(wèn)題在native層面是否也完全修復(fù)。
熱修復(fù)要有節(jié)操
熱修復(fù)啟用前后,app的展現(xiàn)形式也許會(huì)讓用戶困惑,怎么app一下就好了,所以涉及到我們?cè)撊绾翁崾窘o用戶熱修復(fù)?這只是用戶體驗(yàn)的一方面,可能還會(huì)有流量方面對(duì)用戶的困擾(大多數(shù)情況下代碼少的話腳本是KB為單位還好),所以app需要更有節(jié)操的使用熱修復(fù):
最粗糙的方式就是不提示,也是最開(kāi)始集成最常見(jiàn)的形式,將熱修復(fù)的啟動(dòng)放在 didFinishLaunchingWithOptions:方法中,只要每次app(crash后或殺掉進(jìn)程)重新啟動(dòng)便自動(dòng)與平臺(tái)溝通是否有熱修復(fù)腳本,只要有下發(fā)便立刻執(zhí)行熱修復(fù);
為了更及時(shí)修復(fù)問(wèn)題,每次回到前臺(tái) applicationDidBecomeActive:根據(jù)一定時(shí)間策略去檢查是否要熱修復(fù),并且給出熱修復(fù)的提示, 具體可以參考這個(gè)博客;
為了給用戶更好的體驗(yàn),我們可能需要更加人性化的熱修復(fù),比如可以參考12306這個(gè)方式:
另外,我們還可以根據(jù)熱修復(fù)情況分兩種: 強(qiáng)制熱修復(fù),選擇性熱修復(fù)。 當(dāng)前強(qiáng)制性熱修復(fù)時(shí),通過(guò)toast提示正在熱修復(fù),用戶無(wú)法進(jìn)一步操作app(即使重啟app);選擇性熱修復(fù)主要是考慮熱修復(fù)可能占用一定流量,或者當(dāng)前問(wèn)題可能不影響某些用戶的正常使用,這時(shí)我們可以提示熱修復(fù)腳本大概占用的流量,及熱修復(fù)可以解決的問(wèn)題,用戶可以點(diǎn)擊確定使用,或者不使用,當(dāng)用戶點(diǎn)擊不使用,不再提示這個(gè)版本的熱修復(fù)。
踩過(guò)的小坑
本地調(diào)試時(shí),JSPatch的配置的.js文件名字必須是main.js。將main.js文件(注意一定要utf-8編碼)放在項(xiàng)目工程目錄下,并添加到項(xiàng)目中。
注意:
- 本地調(diào)試目前是沒(méi)有l(wèi)og打印的,所以需要自己在瀏覽器中看是否已經(jīng)加載,打斷點(diǎn)看執(zhí)行情況;
- main.js文件一定要utf-8編碼;
......