簡單認識:kerkee 是一個多主體共存型 Hybrid 框架,具有跨平台、用戶體驗好、性能高、擴展性好、靈活性強、易維護、規范化、集成雲服務、具有Debug環境、徹底解決跨域問題。該框架從開發者角度支持三種團隊開發模式:Web開發者 、Native開發者 、Web開發者和Native團隊共同合作的開發團隊 。
下面我將從 Web開發者和Native(iOS)團隊共同合作的開發團隊 模式來分析使用該框架。
官網地址:http://www.kerkee.com
Github 源碼地址:https://github.com/kercer
QQ交流群:110710084
二、簡單的使用:
kerkee在iOS上的快速上手指南
1、建立新項目 kerkeeHDDemo
在項目目錄中建立 Podfile
文件:
platform :ios, '7.0'inhibit_all_warnings!pod ‘kerkee’, ’~> 1.0.1’
2、使用 CocoaPods 來導入 kerkee 框架,使用終端 cd 到你的 Podfile
文件所在的目錄:
cd $PodfilePathpod install
3、運行 Podfile
同目錄中的 kerkeeHDDemo.xcworkspace
即可;
4、在項目中添加 html 代碼:(本人對 html 不熟悉,相信懂的人肯定懂)
kerkeeTest.html :
[object Object] [object Object]
上面有三種方式來處理按鈕的點擊事件:
i. 直接在 中處理
ii. 使用 function 處理;
iii. 使用 js 來處理 function。
代碼解釋:
kerkeeTest.js :
var kerkeeJSManager;function buttonClick2(s1) { kerkeeJSManager.jsToOc(s1); }
5、在項目中添加 iOS 代碼:
HDJSToOCManager.h:
#import #import "KCJSObject.h"#import "KCArgList.h"// 這個類作為 和 js 交互橋梁類@interface HDJSToOCManager : KCJSObject- (void)jsToOc:(KCWebView*)aWebView argList:(KCArgList*)args; - (void)mutualJSOC:(KCWebView*)aWebView argList:(KCArgList*)args;@end
HDJSToOCManager.m:
#import "HDJSToOCManager.h"#import #import "KCJSBridge.h"@implementation HDJSToOCManager- (NSString*)getJSObjectName{ // 這個 和 js 中的變量要保持一致 return @"kerkeeJSManager"; }// js 中 可以調用 jsToOc() 來調用- (void)jsToOc:(KCWebView*)aWebView argList:(KCArgList*)args{ NSLog(@"JS調用OC args : %@", args); } - (void)mutualJSOC:(KCWebView*)aWebView argList:(KCArgList*)args{ NSLog(@"JS調用OC,OC回調JS args : %@", args); NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setObject:@"success" forKey:@"info"]; NSString *json = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:dic options:0 error:nil] encoding:NSUTF8StringEncoding]; KCAutorelease(json); //回調,callbackId,kerkee.js 內部已經處理好 [KCJSBridge callbackJS:aWebView callBackID:[args getObject:@"callbackId"] jsonString:json]; }@end
這個類主要是生成和 js 對應的變量 kerkeeJSManager ,是 iOS 這邊 iOS 和 js 交互橋梁
ViewController.m:
- (void)viewDidLoad { [super viewDidLoad]; // 將 HDJSToOCManager對象 和 js中的 kerkeeJSManager (詳見HDJSToOCManager)綁定 [KCJSBridge registObject:[[HDJSToOCManager alloc] init]]; NSString *file = [[NSBundle mainBundle] pathForResource:@"kerkeeTest" ofType:@"html"]; NSURL *url = [NSURL URLWithString:file]; NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url]; m_webView = [[KCWebView alloc] initWithFrame:self.view.bounds]; [m_webView loadRequest:request]; [self.view addSubview:m_webView]; // 在m_webView 加載 “kerkee.js” 代碼,具體代碼見 KCApiBridge.m 中的 init 方法 (這個時候需要在項目中添加“kerkee.js” 文件) m_jsBridge = [[KCJSBridge alloc] initWithWebView:m_webView delegate:self]; UIButton *bb = [UIButton buttonWithType:UIButtonTypeInfoLight]; bb.frame = CGRectMake(100, 400, 50, 50); [bb addTarget:self action:@selector(ocToJs) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:bb]; } - (void)ocToJs{ [KCJSBridge callJSFunction:@"ocToJs" withJSONObject:@{@"hhh" : @"www"} WebView:m_webView]; } #pragma mark -- #pragma mark KCWebViewProgressDelegate -(void)webView:(KCWebView*)webView identifierForInitialRequest:(NSURLRequest*)initialRequest{ } #pragma mark - UIWebView Delegate - (void)webViewDidFinishLoad:(UIWebView *)aWebView{ NSString *scrollHeight = [aWebView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; NSLog(@"scrollHeight: %@", scrollHeight); NSLog(@"webview.contentSize.height %f", aWebView.scrollView.contentSize.height); } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ } - (BOOL)webView:(UIWebView *)aWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ return YES; }
上面的代碼並不多,其核心代碼之一在 KCApiBridge 中,可以看到, KCApiBridge 在初始化的時候找到 kerkee.js 並且使用- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; 在 iOS 的 UIWebView 注入 kerkee.js 代碼。這份 js 才是真正的將 iOS 的橋梁 HDJSToOCManager 實體和 js變量 kerkeeJSManager 的關鍵代碼。在 這裡 可以看到作者在demo中的 kerkee.js 源碼。因為我對 js 也是入門階段,所以選擇在源碼上改動之後,代碼如下:
;(function(window){ if (window.WebViewJSBridge) return; window.WebViewJSBridge = { }; console.log("--- kerkee init begin---"); var browser={ versions:function(){ var u = navigator.userAgent, app = navigator.appVersion; return { trident: u.indexOf('Trident') > -1, //IE presto: u.indexOf('Presto') > -1, //opera webKit: u.indexOf('AppleWebKit') > -1, //apple&google kernel gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,//firfox mobile: !!u.match(/AppleWebKit.*Mobile.*/), //is Mobile ios: !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), //is ios android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1, //android iPhone: u.indexOf('iPhone') > -1 , //iPhone or QQHD iPad: u.indexOf('iPad') > -1, //iPad iPod: u.indexOf('iPod') > -1, //iPod webApp: u.indexOf('Safari') == -1, //is webapp,no header and footer weixin: u.indexOf('MicroMessenger') > -1, //is wechat qq: u.match(/sQQ/i) == " qq" //is qq }; }(), language:(navigator.browserLanguage || navigator.language).toLowerCase() } var global = this || window; var ApiBridge ={ msgQueue : [], callbackCache : [], callbackId : 0, processingMsg : false, isReady : false, isNotifyReady : false }; ApiBridge.create = function() { ApiBridge.bridgeIframe = document.createElement("iframe"); ApiBridge.bridgeIframe.style.display = 'none'; document.documentElement.appendChild(ApiBridge.bridgeIframe); }; ApiBridge.prepareProcessingMessages = function() { ApiBridge.processingMsg = true; }; ApiBridge.fetchMessages = function() { if (ApiBridge.msgQueue.length > 0) { var messages = JSON.stringify(ApiBridge.msgQueue); ApiBridge.msgQueue.length = 0; return messages; } ApiBridge.processingMsg = false; return null; }; ApiBridge.callNative = function(clz, method, args, callback) { var msgJson = {}; msgJson.clz = clz; msgJson.method = method; if (args != undefined) msgJson.args = args; if (callback) { var callbackId = ApiBridge.getCallbackId(); ApiBridge.callbackCache[callbackId] = callback; if (msgJson.args) { msgJson.args.callbackId = callbackId.toString(); } else { msgJson.args = { "callbackId" : callbackId.toString() }; } } if (browser.versions.ios) { if (ApiBridge.bridgeIframe == undefined) { ApiBridge.create(); } // var msgJson = {"clz": clz, "method": method, "args": args}; ApiBridge.msgQueue.push(msgJson); if (!ApiBridge.processingMsg) ApiBridge.bridgeIframe.src = "kcnative://go"; } else if (browser.versions.android) { // android return prompt(JSON.stringify(msgJson)); } }; ApiBridge.log = function(msg) { ApiBridge.callNative("ApiBridge", "JSLog", { "msg" : msg }); } ApiBridge.getCallbackId = function() { return ApiBridge.callbackId++; } ApiBridge.onCallback = function(callbackId, obj) { if (ApiBridge.callbackCache[callbackId]) { ApiBridge.callbackCache[callbackId](obj); // ApiBridge.callbackCache[callbackId] = undefined; // //如果是注冊事件的話,不能undefined; } } ApiBridge.onBridgeInitComplete = function(callback) { ApiBridge.callNative("ApiBridge", "onBridgeInitComplete", {}, callback); } ApiBridge.onNativeInitComplete = function(callback) { ApiBridge.isReady = true; console.log("--- kerkee onNativeInitComplete end ---"); if (callback) { callback(); ApiBridge.isNotifyReady = true; console.log("--- device ready go--- "); } } ApiBridge.compile = function(aIdentity, aJS) { var value; var error; try { value = eval(aJS); } catch (e) { var err = {}; err.name = e.name; err.message = e.message; err.number = e.number & 0xFFFF; error = err; } ApiBridge.callNative("ApiBridge", "compile", { "identity" : aIdentity, "returnValue" : value, "error" : error }); } var _Configs = { isOpenJSLog:false, sysLog:{}, isOpenNativeXHR:true, sysXHR:{} }; _Configs.sysLog = global.console.log; _Configs.sysXHR = global.XMLHttpRequest; var kerkee = {}; /***************************************** * JS和iOS 交互接口 *****************************************/ kerkee.jsToOc = function(s1) { ApiBridge.callNative("kerkeeJSManager", "jsToOc", { "s1" : s1 }); }; kerkee.mutualJSOC = function(aString, callback) { ApiBridge.callNative("kerkeeJSManager", "mutualJSOC", { "aString" : aString }, callback); } global.kerkeeJSManager = kerkee; kerkee.openJSLog = function() { _Configs.isOpenJSLog = true; global.console.log = ApiBridge.log; } kerkee.closeJSLog = function() { _Configs.isOpenJSLog = false; global.console.log = _Configs.sysLog; } kerkee.onDeviceReady = function(handler) { ApiBridge.onDeviceReady = handler; if (ApiBridge.isReady && !ApiBridge.isNotifyReady && handler) { console.log("-- device ready --"); handler(); ApiBridge.isNotifyReady = true; } }; kerkee.invoke = function(clz, method, args, callback) { if (callback) { ApiBridge.callNative(clz, method, args, callback); } else { ApiBridge.callNative(clz, method, args); } }; kerkee.onSetImage = function(srcSuffix, desUri) { // console.log("--- kerkee onSetImage ---"); var obj = document.querySelectorAll('img[src$="' + srcSuffix + '"]'); for (var i = 0; i < obj.length; ++i) { obj[i].src = desUri; } }; /***************************************** * XMLHttpRequest實現 *****************************************/ var _XMLHttpRequest = function() { this.id = _XMLHttpRequest.globalId++; _XMLHttpRequest.cache[this.id] = this; this.status = 0; this.statusText = ''; this.readyState = 0; this.responseText = ''; this.headers = {}; this.onreadystatechange = undefined; ApiBridge.callNative('XMLHttpRequest', 'create', { "id" : this.id }); } _XMLHttpRequest.globalId = 0; _XMLHttpRequest.cache = []; _XMLHttpRequest.setProperties = function(jsonObj) { var id = jsonObj.id; if (_XMLHttpRequest.cache[id]) { var obj = _XMLHttpRequest.cache[id]; if (jsonObj.hasOwnProperty('status')) { obj.status = jsonObj.status; } if (jsonObj.hasOwnProperty('statusText')) { obj.statusText = jsonObj.statusText; } if (jsonObj.hasOwnProperty('readyState')) { obj.readyState = jsonObj.readyState; } if (jsonObj.hasOwnProperty('responseText')) { obj.responseText = jsonObj.responseText; } if (jsonObj.hasOwnProperty('headers')) { obj.headers = jsonObj.headers; } if (_XMLHttpRequest.cache[id].onreadystatechange) { _XMLHttpRequest.cache[id].onreadystatechange(); } } } _XMLHttpRequest.prototype.open = function(method, url, async) { ApiBridge.callNative('XMLHttpRequest', 'open', { "id" : this.id, "method" : method, "url" : url, "scheme" : window.location.protocol, "host" : window.location.hostname, "port" : window.location.port, "href" : window.location.href, "referer" : document.referrer != "" ? document.referrer : undefined, "useragent" : navigator.userAgent, "cookie" : document.cookie != "" ? document.cookie : undefined, "async" : async, "timeout" : this.timeout }); } _XMLHttpRequest.prototype.send = function(data) { if (data != null) { ApiBridge.callNative('XMLHttpRequest', 'send', { "id" : this.id, "data" : data }); } else { ApiBridge.callNative('XMLHttpRequest', 'send', { "id" : this.id }); } } _XMLHttpRequest.prototype.overrideMimeType = function(mimetype) { ApiBridge.callNative('XMLHttpRequest', 'overrideMimeType', { "id" : this.id, "mimetype" : mimetype }); } _XMLHttpRequest.prototype.abort = function() { ApiBridge.callNative('XMLHttpRequest', 'abort', { "id" : this.id }); } _XMLHttpRequest.prototype.setRequestHeader = function(headerName, headerValue) { ApiBridge.callNative('XMLHttpRequest', 'setRequestHeader', { "id" : this.id, "headerName" : headerName, "headerValue" : headerValue }); } _XMLHttpRequest.prototype.getAllResponseHeaders = function() { var strHeaders = ''; for ( var name in this.headers) { strHeaders += (name + ": " + this.headers[name] + " "); } return strHeaders; } _XMLHttpRequest.prototype.getResponseHeader = function(headerName) { var strHeaders; var upperCaseHeaderName = headerName.toUpperCase(); for ( var name in this.headers) { if (upperCaseHeaderName == name.toUpperCase()) strHeaders = this.headers[name] } return strHeaders; } _XMLHttpRequest.deleteObject = function(id) { if (_XMLHttpRequest.cache[id]) { _XMLHttpRequest.cache[id] = undefined; } } global.ApiBridge = ApiBridge; global.kerkee = kerkee; global.XMLHttpRequest = _XMLHttpRequest; kerkee.register = function(_window) { _window.ApiBridge = window.ApiBridge; _window.kerkee = window.kerkee; _window.console.log = window.console.log; _window.XMLHttpRequest = window.XMLHttpRequest; _window.open = window.open; }; ApiBridge.onBridgeInitComplete(function(aConfigs) { if (aConfigs) { if (aConfigs.hasOwnProperty('isOpenJSLog')) { _Configs.isOpenJSLog = aConfigs.isOpenJSLog; } if (aConfigs.hasOwnProperty('isOpenNativeXHR')) { _Configs.isOpenNativeXHR = aConfigs.isOpenNativeXHR; } } if (_Configs.isOpenJSLog) { global.console.log = ApiBridge.log; } ApiBridge.onNativeInitComplete(ApiBridge.onDeviceReady); }); })(window);
如果單純的只是 js 和 iOS 的交互,只需要關注 下面 的代碼即可:(當然你也可以自己設置多個 kerkeeJSManager 橋梁及 更多的 交互方法)
/***************************************** * JS和iOS 交互接口 *****************************************/kerkee.jsToOc = function(s1) { ApiBridge.callNative("kerkeeJSManager", "jsToOc", { "s1" : s1 }); }; kerkee.mutualJSOC = function(aString, callback) { ApiBridge.callNative("kerkeeJSManager", "mutualJSOC", { "aString" : aString }, callback); }global.kerkeeJSManager = kerkee;
6、編譯運行:
7、demo下載地址:GitHub地址
三、總結:
這裡只是簡單的介紹了 iOS 使用 kerkee 框架來加載 html 實現 js 和 iOS 交互 ,如果只是單純的為了簡單的交互,可以看我的另外一篇博客 :JS和OC相互調用 ,這裡介紹了幾個更加輕量級的框架實現 js 和 iOS 交互。但是 js 和 iOS 交互 功能在kerkee 框架的一小部分,更多高性能、支持跨平台、擴展性好、易維護等等優秀的特性,我會慢慢閱讀源碼來說明。
該博客更好的閱讀體驗地址