JSPatch作為熱更新技術(shù)的黑科技,已經(jīng)不是什么前沿的新聞了,像騰訊、美團(tuán)等大公司也在使用JSPatch。前段時(shí)間蘋果對使用這些像JSPatch,weex等熱更新技術(shù)下發(fā)警告通知或強(qiáng)制下架的事,技術(shù)圈里讓很多小伙伴們坐不住了,鬧的沸沸揚(yáng)揚(yáng),這個(gè)15年就問世的框架具備很多之前的類似框架所不具備的優(yōu)點(diǎn),更加的小巧便捷,并且處于持續(xù)維護(hù)中,不僅如此,還由此成為了一個(gè)生態(tài)圈,bang神還為此開發(fā)了oc轉(zhuǎn)js的代碼轉(zhuǎn)換器、可以自動(dòng)提示的JSPatchX插件、以及基于這個(gè)技術(shù)的JSPatchPlatform平臺(tái)。總之讓大伙可以很方便的進(jìn)行patch。作為技術(shù)方面的一個(gè)小探索,抱著學(xué)習(xí)的態(tài)度,初次測試一下效果。
如何混淆JSPatch熱修復(fù)框架以繞過蘋果的機(jī)器檢測
- HotFix概述
- 集成JSPatch
<h3>HotFix概述</h3>
對于iOS,這種HotFix方案大致可以分為四種:
- WaxPatch(Alibaba)
- Dynamic Framework(Apple)
- React Native(Facebook)
- JSPatch(Tencent)
WaxPatch
WaxPatch是一個(gè)通過Lua語言編寫的iOS框架,不僅允許用戶使用 Lua 調(diào)用 iOS SDK和應(yīng)用程序內(nèi)部的 API, 而且使用了 OC runtime 特性調(diào)用替換應(yīng)用程序內(nèi)部由 OC 編寫的類方法,從而達(dá)到HotFix的目的。
WaxPatch的優(yōu)點(diǎn)在于它支持iOS6.0,同時(shí)性能上比較的優(yōu)秀,但是缺點(diǎn)也是非常的明顯,不符合Apple3.2.2的審核規(guī)則即不可動(dòng)態(tài)下發(fā)可執(zhí)行代碼,但通過蘋果JavaScriptCore.framework或WebKit執(zhí)行的代碼除外;同時(shí)Wax已經(jīng)長期沒有人維護(hù)了,導(dǎo)致很多OC方法不能用Lua實(shí)現(xiàn),比如Wax不支持block;最后就是必須要內(nèi)嵌一個(gè)Lua腳本的執(zhí)行引擎才能運(yùn)行Lua腳本;Wax并不支持arm64框架。
Dynamic Framework
動(dòng)態(tài)的Framework,其實(shí)就是動(dòng)態(tài)庫;首先我介紹一下關(guān)于動(dòng)態(tài)庫和靜態(tài)庫的一些特性以及區(qū)別。
不管是靜態(tài)庫還是動(dòng)態(tài)庫,本質(zhì)上都是一種可執(zhí)行的二進(jìn)制格式,可以被載入內(nèi)存中執(zhí)行。
iOS上的靜態(tài)庫可以分為.a文件和.framework,動(dòng)態(tài)庫可以分為.dylib(xcode7以后變成了.tdb)和.framework。
- 靜態(tài)庫: 鏈接時(shí)完整地拷貝至可執(zhí)行文件中,被多次使用就有多份冗余拷貝。
- 動(dòng)態(tài)庫: 鏈接時(shí)不復(fù)制,程序運(yùn)行時(shí)由系統(tǒng)動(dòng)態(tài)加載到內(nèi)存,供程序調(diào)用,系統(tǒng)只加載一次,多個(gè)程序共用,節(jié)省內(nèi)存。
靜態(tài)庫和動(dòng)態(tài)庫是相對編譯期和運(yùn)行期的:靜態(tài)庫在程序編譯時(shí)會(huì)被鏈接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要改靜態(tài)庫;而動(dòng)態(tài)庫在程序編譯時(shí)并不會(huì)被鏈接到目標(biāo)代碼中,只是在程序運(yùn)行時(shí)才被載入,因?yàn)樵诔绦蜻\(yùn)行期間還需要?jiǎng)討B(tài)庫的存在。
- 總結(jié):同一個(gè)靜態(tài)庫在不同程序中使用時(shí),每一個(gè)程序中都得導(dǎo)入一次,打包時(shí)也被打包進(jìn)去,形成一個(gè)程序。而動(dòng)態(tài)庫在不同程序中,打包時(shí)并沒有被打包進(jìn)去,只在程序運(yùn)行使用時(shí),才鏈接載入(如系統(tǒng)的框架如UIKit、Foundation等),所以程序體積會(huì)小很多。
好,所以Dynamic Framework其實(shí)就是我們可以通過更新App所依賴的Framework方式,來實(shí)現(xiàn)對于Bug的HotFix,但是這個(gè)方案的缺點(diǎn)也是顯而易見的它不符合Apple3.2.2的審核規(guī)則,使用了這種方式是上不了Apple Store的,它只能適用于一些越獄市場或者公司內(nèi)部的一些項(xiàng)目使用,同時(shí)這種方案其實(shí)并不適用于BugFix,更適合App線上的大更新。所以其實(shí)我們項(xiàng)目中的引入的那些第三方的Framework都是靜態(tài)庫,我們可以通過file這個(gè)命令來查看我們的framework到底是屬于static還是dynamic。
React Native
React Native支持用JavaScript進(jìn)行開發(fā),所以可以通過更改JS文件實(shí)現(xiàn)App的HotFix,但是這種方案的明顯的缺點(diǎn)在于它只適合用于使用了React Native這種方案的應(yīng)用。
JSPatch
JSPatch是只需要在項(xiàng)目中引入極小的JSPatch引擎,就可以使用JavaScript語言調(diào)用Objective-C的原生接口,獲得腳本語言的能力:動(dòng)態(tài)更新iOS APP,替換項(xiàng)目原生代碼、快速修復(fù)bug。但是JSPatch也有它的自己的缺點(diǎn),主要在由于它要依賴javascriptcore,framework,而這個(gè)framework是在iOS7.0以后才引入進(jìn)來,所以JSPatch是不支持iOS6.0的,同時(shí)由于使用的是JS的腳本技術(shù),所以在內(nèi)存以及性能上面是要低于Wax的。
<h3>集成JSPatch</h3>
JSPatch 需要使用者有一個(gè)后臺(tái)可以下發(fā)和管理腳本,并且需要處理傳輸安全等部署工作,JSPatch 平臺(tái)幫你做了這些事,提供了腳本后臺(tái)托管,版本管理,保證傳輸安全等功能,讓你無需搭建一個(gè)后臺(tái),無需關(guān)心部署操作,只需引入一個(gè) SDK 即可立即使用 JSPatch。
Github 開源的是 JSPatch 核心代碼,使用完全免費(fèi)自由,若打算自己搭建后臺(tái)下發(fā) JSPatch 腳本,可以直接使用 github 上的核心代碼,與 JSPatch 平臺(tái)上的 SDK 無關(guān)。JSPatch 平臺(tái)的 SDK 在核心代碼的基礎(chǔ)上增加了向平臺(tái)請求腳本/傳輸解密/版本管理等功能,只用于這個(gè)平臺(tái)。
1.從官網(wǎng)上下載提供的SDK API包來后,導(dǎo)入工程,在TARGETS -> Build Phases -> Link Binary With Libraries -> + 添加 libz.dylib 和 JavaScriptCore.framework
2.生成和配置RSA密鑰
自定義 RSA 密鑰對 RSA 密鑰的作用詳見安全問題。目前為了更高的安全性,平臺(tái)強(qiáng)制要求所有補(bǔ)丁下發(fā)都使用自定義 RSA 密鑰,生成 RSA 密鑰,在 Mac 終端上執(zhí)行 openssl,再執(zhí)行以下三句命令,生成 PKCS8 格式的 RSA 公私鑰,執(zhí)行過程中提示輸入密碼,密碼為空(直接回車)就行。
openssl >
genrsa -out rsa_private_key.pem 1024
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM –nocrypt
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
生成的公私鑰,在上傳布丁時(shí)要用:
用JSPatch?官網(wǎng)工具中提供的RSA配置工具,拖入公鑰文件直接生成配置代碼
注冊賬號成功后,在我的App中添加新應(yīng)用,應(yīng)用的圖標(biāo)生成是填寫了已上架應(yīng)用的Appkey,這里只是測試,就沒必要了,確定之后會(huì)生成平臺(tái)應(yīng)用的AppKey
3.在 AppDelegate.m中按順序調(diào)用
startWithAppKey
、setupRSAPublicKey
、sync
方法,可以把 [JSPatch sync] 放在 -applicationDidBecomeActive: 里,每次喚醒都能同步更新 JSPatch 補(bǔ)丁,不需要等用戶下次啟動(dòng)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/**
*AppKey:JSPatch添加應(yīng)用時(shí)生成的AppKey
*RSAPublicKey:剛才生成的公鑰RSA字符串
*/
[JSPatch startWithAppKey:@"834abc498b14c64b"];
[JSPatch setupRSAPublicKey:@"-----BEGIN PUBLIC KEY-----RSABLABLABLA45/44DJFJJNKSDLKS-----END PUBLIC KEY-----"];
//用來檢測回調(diào)的狀態(tài),是更新或者是執(zhí)行腳本之類的,相關(guān)信息,會(huì)打印在你的控制臺(tái)
[JSPatch setupCallback:^(JPCallbackType type, NSDictionary *data, NSError *error) {
NSLog(@"error-->%@",error);
switch (type) {
case JPCallbackTypeUpdate: {
NSLog(@"更新腳本 %@ %@", data, error);
break;
}
case JPCallbackTypeRunScript: {
NSLog(@"執(zhí)行腳本 %@ %@", data, error);
break;
}
case JPCallbackTypeCondition: {
NSLog(@"條件下發(fā) %@ %@", data, error);
break;
}
case JPCallbackTypeGray: {
NSLog(@"灰度下發(fā) %@ %@", data, error);
break;
}
default:
break;
} }];
[JSPatch setupDevelopment];
[JSPatch sync];
return YES;
}
4.在ViewController中創(chuàng)建一個(gè)laber,聲明一個(gè)test方法用來給laber賦值
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UILabel *textLaber;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textLaber = [[UILabel alloc] initWithFrame:CGRectMake(0, 50, self.view.frame.size.width, 60)];
_textLaber.textAlignment = NSTextAlignmentCenter;
_textLaber.backgroundColor = [UIColor cyanColor];
[self.view addSubview:_textLaber];
[self test];
}
- (void)test{
self.textLaber.text = @"像瘋了一樣";
}
@end
5.創(chuàng)建main.js, 保存
console.log('run success')
defineClass("ViewController", {
test: function() {
self.textLaber().setText("內(nèi)容就這樣改變了");
},
})
6.就是發(fā)布布丁了,布丁文件就是上面的main.js文件,RSA密鑰就是生成的,**rsa_public_key.pem **公鑰文件,因?yàn)橹皇菧y試,所以勾選開發(fā)預(yù)覽選項(xiàng),
- 開發(fā)預(yù)覽:是用來測試開發(fā)用的
- ?全量下發(fā):給所有安裝布丁的人下發(fā)
- ?條件下發(fā):可以根據(jù)JSPatch設(shè)定的userId設(shè)定篩選條件下發(fā)
- 灰度下發(fā):按人數(shù)灰度可以指定補(bǔ)丁對多少個(gè)用戶生效,超過設(shè)置的人數(shù)后不會(huì)再生效。灰度人數(shù)可以修改增加,但不能減少,可以逐漸增加灰度人數(shù),直到全量發(fā)布。
點(diǎn)擊提交之后,顯示發(fā)布成功
7.再看看我們的demo,?打印臺(tái)收到如下消息就說明布丁更新加載成功
即使這樣,你還會(huì)發(fā)現(xiàn),laber的值并沒有改變啊,好吧,因?yàn)檠a(bǔ)丁是先下載再生效的,所以下一次運(yùn)行你才能看到效果,后續(xù)我會(huì)不斷去踩坑,這是我們在main.js 中設(shè)的值
可坑能踩的坑
- 布丁腳本加載成功,卻出錯(cuò)MD5加密之類的,當(dāng)然就是你的?RSA公私鑰有問題嘍,重新生成一份
- JSPatch網(wǎng)站上的版本要一定要和工程里的一樣
- label的名字別寫錯(cuò)了
- Swift一定要在方法和屬性前加dynamic,如果不是繼承自NSObject的Swift類不能被動(dòng)態(tài)替換
- Swift替換類和方法要比OC在類/方法名之前添加工程名
- 如果項(xiàng)目跑起來控制臺(tái)輸出沒有找到文檔就是網(wǎng)站上配置錯(cuò)了
相關(guān)連接:
JSPatch 基礎(chǔ)用法
JSPatch實(shí)現(xiàn)原理詳解:讓JS調(diào)用/替換任意OC方法