WebKit内核的研究与应用

概述

本篇文章将阐述,WebKit核新的类和协议MessageHandler消息处理器JS->OC,OC->JS 交互流程WKNavagationDelegate协议WKUIDelegate协议等类或方法分析WebKit内核的原理及应用说明。

重点注意

WebKit 的函数和方法只能在App的主线程或者主队列中调用。

WebKit核新的类和协议

WKWebView:网页的渲染与展示,通过WKWebViewConfiguration可以进行配置。

WKWebViewConfiguration:这个类专门用来配置WKWebView。

WKPreference:这个类用来进行M相关设置。

WKProcessPool:这个类用来配置进程池,与网页视图的资源共享有关。

WKUserContentController:这个类主要用来做native与JavaScript的交互管理。

WKUserScript:用于进行JavaScript注入。

WKScriptMessageHandler:这个类专门用来处理JavaScript调用native的方法。

WKNavigationDelegate:网页跳转间的导航管理协议,这个协议可以监听网页的活动。

WKNavigationAction:网页某个活动的示例化对象。

WKUIDelegate:用于交互处理JavaScript中的一些弹出框。

WKBackForwardList:堆栈管理的网页列表。

WKBackForwardListItem:每个网页节点对象。

使用WKWebViewConfiguration对WebView进行配置

WebbView初始化

初始化函数

- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration;

加载方式,建议使用苹果推荐使用的函数

// 加载HTML文件,推荐使用
- (WKNavigation *)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;
// 加载一个请求,推荐使用
- (WKNavigation *)loadRequest:(NSURLRequest *)request;

// 加载本地文件,如Html文件等,不推荐使用
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
// // 加载本地文件,如Html文件等,不推荐使用
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));

WKWebViewConfiguration内容配置

  • WKPerference偏好设置
//进行偏好设置
WKPreferences * preference = [[WKPreferences alloc]init];    
//最小字体大小 当将javaScriptEnabled属性设置为NO时,可以看到明显的效果
preference.minimumFontSize = 0;    
//设置是否支持javaScript 默认是支持的
preference.javaScriptEnabled = YES;    
//设置是否允许不经过用户交互由javaScript自动打开窗口
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;
  • WKUserContentControllernativeJavaScript的交互管理
    WKUserContentController 最重要的对象方法就是addScriptMessageHandler:name:,我把这个功能简称为MessageHandler(消息处理器)。- addScriptMessageHandler:name:有两个参数,第一个参数是userContentController的代理对象(通常是self),第二个参数是JS里发送postMessage的对象(相当于一个nativejs相互识别的一个标识)。使用MessageHandler功能,就必须要实现WKScriptMessageHandler协议。
//设置内容交互控制器 用于处理JavaScript与native交互
WKUserContentController * userController = [[WKUserContentController alloc]init];    
//设置处理代理并且注册要被js调用的方法名称
[userController addScriptMessageHandler:self name:@"name"];    
//js注入,注入一个测试方法。
NSString *javaScriptSource = @"function userFunc(){window.webkit.messageHandlers.name.postMessage( {\"name\":\"HS\"})}";    
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:javaScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
// forMainFrameOnly:NO(全局窗口),yes(只限主窗口)[userController addUserScript:userScript];
config.userContentController = userController;
  • 在native代理的回调方法中,会获取到JavaScript传递进来的消息
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{    
//这里可以获取到JavaScript传递进来的消息
}
  • WKScriptMessage类是JavaScript传递的对象实例
//传递的消息主体
@property (nonatomic, readonly, copy) id body;
//传递消息的WebView
@property (nullable, nonatomic, readonly, weak) WKWebView *webView;
//传递消息的WebView当前页面对象
@property (nonatomic, readonly, copy) WKFrameInfo *frameInfo;
//消息名称,也就是我们注册回调的“name”
@property (nonatomic, readonly, copy) NSString *name;
  • 注入JS脚本
- (void)addUserScript:(WKUserScript *)userScript;
  • WKWebsiteDataStore:WebKit框架采用其本身的缓存框架

@interface WKWebsiteDataStore : NSObject
//获取默认的存储器 此存储器为持久性的会被写入磁盘
+ (WKWebsiteDataStore *)defaultDataStore;
//获取一个临时的存储器
+ (WKWebsiteDataStore *)nonPersistentDataStore;
//存储器是否是临时的
@property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
//所有可以存储的类型
+ (NSSet *)allWebsiteDataTypes;
@end

//设置数据存储store
config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];

//设置是否将网页内容全部加载到内存后再渲染
    config.suppressesIncrementalRendering = NO;    //设置HTML5视频是否允许网页播放 设置为NO则会使用本地播放器
    config.allowsInlineMediaPlayback =  YES;    //设置是否允许ariPlay播放
    config.allowsAirPlayForMediaPlayback = YES;    //设置视频是否需要用户手动播放  设置为NO则会允许自动播放
    config.requiresUserActionForMediaPlayback = NO;    //设置是否允许画中画技术 在特定设备上有效
    config.allowsPictureInPictureMediaPlayback = YES;    //设置选择模式 是按字符选择 还是按模块选择/*
    typedef NS_ENUM(NSInteger, WKSelectionGranularity) {
        //按模块选择
        WKSelectionGranularityDynamic,
        //按字符选择
        WKSelectionGranularityCharacter,
    } NS_ENUM_AVAILABLE_IOS(8_0);
    */
    config.selectionGranularity = WKSelectionGranularityCharacter;    //设置请求的User-Agent信息中应用程序名称 iOS9后可用
    config.applicationNameForUserAgent = @"HS";

JS->OC,OC->JS 交互流程

Native 实现方式

    //防止频繁IO操作,造成性能影响
    static NSString *nativejsSource;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        nativejsSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"NativeApi" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil];
    });
    //添加自定义的脚本
    WKUserScript *js = [[WKUserScript alloc] initWithSource:nativejsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [self.webView.configuration.userContentController addUserScript:js];

    //注册分享回调
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeShare"];
    
    // 注册添加联系人的回调
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"nativeChoosePhoneContact"];

JS端实现方式

/**
 * Native为H5提供的Api接口
 *
 * @type {js对象}
 */
var DANativeApi = (function() {

    var NativeApi = {
        /**
         * 分享
         * @param  {js对象} shareInfo 分享信息和回调
         * @return {void}           无同步返回值,异步返回分享结果 true or false
         */
        share: function(shareInfo) {
            if (shareInfo == undefined || shareInfo == null || typeof(shareInfo) !== "object") {
                alert("参数" + JSON.stringify(shareInfo) + "不合法");
            } else {
                alert("分享的参数为" + JSON.stringify(shareInfo));
            }
            //调用native端
            _nativeShare(shareInfo);
        },
        /**
         * 从通讯录选择联系人
         * @return {void} 无同步返回值,异步返回选择的结果
         */
        choosePhoneContact: function(param) {
            //具体是否需要判断
            //调用native端
            _nativeChoosePhoneContact(param);
        }
    }

    //下面是一些私有函数
    /**
     * Native端实现,适用于WKWebView,UIWebView如何实现,小伙伴自己动脑筋吧~
     * @param  {js对象} shareInfo 分享的信息和回调
     * @return {void}           无同步返回值,异步返回
     */
    function _nativeShare(shareInfo) {
        //用于WKWebView,因为WKWebView并没有办法把js function传递过去,因此需要特殊处理一下
        //把js function转换为字符串,oc端调用时 ()(true); 即可
        //如果有回调函数,且为function
        var callbackFunction = shareInfo.result;
        if (callbackFunction != undefined && callbackFunction != null && typeof(callbackFunction) === "function") {
            shareInfo.result = callbackFunction.toString();
        }
        //js -> oc 
        // 至于Android端,也可以,比如 window.jsInterface.nativeShare(JSON.stringify(shareInfo));
        window.webkit.messageHandlers.nativeShare.postMessage(shareInfo);
    }
    /**
     * Native端实现选择联系人,并异步返回结果
     * @param  {[type]} param [description]
     * @return {[type]}       [description]
     */
    function _nativeChoosePhoneContact(param) {
        var callbackFunction = param.completion;
        if (callbackFunction != undefined && callbackFunction != null && typeof(callbackFunction) === "function") {
            param.completion = callbackFunction.toString();
        }
        //js -> oc 
        window.webkit.messageHandlers.nativeChoosePhoneContact.postMessage(param);
    }

    //闭包,把Api对象返回
    return NativeApi;
})();

/*

//调用时,分享
DANativeApi.share({
    title: document.title,
    desc: "",
    url: location.href,
    imgUrl: "",
    result: function(res) {
        // body...
        alert("分享结果为:" + JSON.stringify(res));
    }
});

//选择联系人
DANativeApi.choosePhoneContact({
    completion: function(res) {
        alert("选择联系人的结果为:" + JSON.stringify(res));
    }
});
 */

释放回调

- (void)dealloc
{
// 注册的回调要及时释放,防止强引用,循环引用
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeShare"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeChoosePhoneContact"];
}

OC调用JS

// 将分享结果返回给js
    NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];

实现WKScriptMessageHandler协议方法(JS调用OC)

#pragma mark - WKScriptMessageHandler 
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// 分享
 if ( [message.name isEqualToString:@"nativeShare"]) {

        // JS ->OC
        // 第一步,处理分享数据
        NSDictionary *shareData = message.body;
        NSLog(@"%@分享的数据为: %@", message.name, shareData);

        // 第二步,弹出分享的图层,执行分享操作
        
        // 第三部,模拟异步回调,将分享状态反馈给JS端
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            //读取js function的字符串
            NSString *jsFunctionString = shareData[@"result"];

            //拼接调用该方法的js字符串
            NSString *callbackJs = [NSString stringWithFormat:@"(%@)(%d);", jsFunctionString, NO];    //后面的参数NO为模拟分享失败

            //执行回调OC->JS,调用JS端的方法,将分享状态告知js,然后evaluateJavaScript:completionHandler的回调处理JS对于分享状态反馈的回应。
            [self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
                if (!error) {
                    NSLog(@"模拟回调,分享失败");
                }
            }];
        });
    }
   
    //选择联系人
    else if ([message.name isEqualToString:@"nativeChoosePhoneContact"]) {
     
        [self selectContactCompletion:^(NSString *name, NSString *phone) {
            NSLog(@"选择完成");
            //读取js function的字符串
            NSString *jsFunctionString = message.body[@"completion"];
            //拼接调用该方法的js字符串
            NSString *callbackJs = [NSString stringWithFormat:@"(%@)({name: '%@', mobile: '%@'});", jsFunctionString, name, phone];
            //执行回调
            [self.webView evaluateJavaScript:callbackJs completionHandler:^(id _Nullable result, NSError * _Nullable error) {
                
            }];
        }];
    
    }
}

WKWebView中的属性和方法解析

常用的属性和方法

//设置导航代理
@property (nullable, nonatomic, weak) id  navigationDelegate;
//设置UI代理
@property (nullable, nonatomic, weak) id  UIDelegate;
//导航列表
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
//通过url加载网页视图
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
//通过文件加载网页视图
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL NS_AVAILABLE(10_11, 9_0);
//通过HTML字符串加载网页视图
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
//通过data数据加载网页视图
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);
//渲染导航列表中的某个网页节点
- (nullable WKNavigation *)goToBackForwardListItem:(WKBackForwardListItem *)item;
//网页标题
@property (nullable, nonatomic, readonly, copy) NSString *title;
//网页的url
@property (nullable, nonatomic, readonly, copy) NSURL *URL;
//网页是否正在加载中
@property (nonatomic, readonly, getter=isLoading) BOOL loading;
//加载进度 可以监听这个属性的值配合UIProgressView来设计进度条
@property (nonatomic, readonly) double estimatedProgress;
//是否全部是安全连接
@property (nonatomic, readonly) BOOL hasOnlySecureContent;
//证书列表
@property (nonatomic, readonly, copy) NSArray *certificateChain;
//是否可以回退
@property (nonatomic, readonly) BOOL canGoBack;
//是否可以前进
@property (nonatomic, readonly) BOOL canGoForward;
//回退网页
- (nullable WKNavigation *)goBack;
//前进网页
- (nullable WKNavigation *)goForward;
//刷新网页
- (nullable WKNavigation *)reload;
//忽略缓存的刷新
- (nullable WKNavigation *)reloadFromOrigin;
//停止加载
- (void)stopLoading;
//执行JavaScript代码
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
//是否允许右滑返回手势
@property (nonatomic) BOOL allowsBackForwardNavigationGestures;

WKBackForwardList类为导航管理的网页列表类

@interface WKBackForwardList : NSObject
//当前所在的网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *currentItem;
//前进的一个网页节
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *forwardItem;
//回退的一个网页节点
@property (nullable, nonatomic, readonly, strong) WKBackForwardListItem *backItem;
//获取某个index的网页节点
- (nullable WKBackForwardListItem *)itemAtIndex:(NSInteger)index;
//获取回退的节点数组
@property (nonatomic, readonly, copy) NSArray *backList;
//获取前进的节点数组
@property (nonatomic, readonly, copy) NSArray *forwardList;
@end

WKBackForwardListItem 网页节点

@interface WKBackForwardListItem : NSObject
//当前节点的URL
@property (readonly, copy) NSURL *URL;
//当前节点的标题
@property (nullable, readonly, copy) NSString *title;
//创建此WebView的初始URL
@property (readonly, copy) NSURL *initialURL;

WKUserScript

//注入的js代码的脚本
    WKUserScript *js = [[WKUserScript alloc] initWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
    [self.webView.configuration.userContentController addUserScript:js];

/*source为要注入的js代码
WKUserScriptInjectionTime设置注入的时机forMainFrameOnly参数设置是否只在主页面注入
typedef NS_ENUM(NSInteger, WKUserScriptInjectionTime) {
    //原js代码运行前注入
    WKUserScriptInjectionTimeAtDocumentStart,
    //原js代码运行后注入
    WKUserScriptInjectionTimeAtDocumentEnd
} NS_ENUM_AVAILABLE(10_10, 8_0);

*/

- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;

WKNavagationDelegate协议

该代理提供的方法,可以用来追踪加载过程(页面开始加载、加载完成、加载失败)、决定是否执行跳转。

//追踪加载过程
// 页面开始加载时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;

// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;

// 页面加载完成之后调用
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;

// 页面加载失败时调用
 -(void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation;

// 页面跳转的代理方法有三种,分为(收到跳转与决定是否跳转两种):
// 接收到服务器跳转请求之后调用
 -(void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;

 // 在收到响应后,决定是否跳转
 -(void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;


/*
在发送请求之前,决定是否跳转,决定是否响应网页的某个动作,例如加载,回退,前进,刷新等,在这个方法中,必须执行decisionHandler()代码块,并将是否允许这个活动执行在block中进行传入
*//*
WKNavigationAction是网页动作的抽象化,其中封装了许多行为信息,后面会介绍
WKNavigationActionPolicy为开发者回执,枚举如下:
typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
    //取消此次行为
    WKNavigationActionPolicyCancel,
    //允许此次行为
    WKNavigationActionPolicyAllow,
} NS_ENUM_AVAILABLE(10_10, 8_0);
*/
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    decisionHandler(WKNavigationActionPolicyAllow);
}

WKUIDelegate协议


//创建新的webView时调用的方法
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures{    return webView;
}
//关闭webView时调用的方法
-(void)webViewDidClose:(WKWebView *)webView{

}
//下面这些方法是交互JavaScript的方法
//JavaScript调用alert方法后回调的方法 message中为alert提示的信息 必须要在其中调用completionHandler()
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{    NSLog(@"%@",message);
    completionHandler();
}
//JavaScript调用confirm方法后回调的方法 confirm是js中的确定框,需要在block中把用户选择的情况传递进去
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{    NSLog(@"%@",message);
    completionHandler(YES);
}
//JavaScript调用prompt方法后回调的方法 prompt是js中的输入框 需要在block中把用户输入的信息传入
-(void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{    NSLog(@"%@",prompt);
    completionHandler(@"123");
}

WKNavagationAction类

@interface WKNavigationAction : NSObject
//原页面
@property (nonatomic, readonly, copy) WKFrameInfo *sourceFrame;
//目标页面
@property (nullable, nonatomic, readonly, copy) WKFrameInfo *targetFrame;
//请求URL
@property (nonatomic, readonly, copy) NSURLRequest *request;

//活动类型/*typedef NS_ENUM(NSInteger, WKNavigationType) {
    //链接激活    WKNavigationTypeLinkActivated,
    //提交操作    WKNavigationTypeFormSubmitted,
    //前进操作    WKNavigationTypeBackForward,
    //刷新操作    WKNavigationTypeReload,
    //重提交操作 例如前进 后退 刷新    WKNavigationTypeFormResubmitted,
    //其他类型
    WKNavigationTypeOther = -1,
} NS_ENUM_AVAILABLE(10_10, 8_0);
*/

@property (nonatomic, readonly) WKNavigationType navigationType;
@end

参考链接

https://www.cnblogs.com/XYQ-208910/p/5876419.html
https://www.jianshu.com/p/ab58df0bd1a1
https://www.jianshu.com/p/ab58df0bd1a1

你可能感兴趣的:(WebKit内核的研究与应用)