WebKit内核的研究与应用

作者: _既白_ | 来源:发表于2018-01-05 15:49 被阅读138次

    概述

    本篇文章将阐述,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<NSString *> *)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端调用时 (<js function string>)(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 <WKNavigationDelegate> navigationDelegate;
    //设置UI代理
    @property (nullable, nonatomic, weak) id <WKUIDelegate> 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<WKBackForwardListItem *> *backList;
    //获取前进的节点数组
    @property (nonatomic, readonly, copy) NSArray<WKBackForwardListItem *> *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内核的研究与应用

        本文链接:https://www.haomeiwen.com/subject/ogujnxtx.html