网页开发适配指南

好像是macOS10.10之后,以及iOS8之后,新出现的WKWebview组件就迅速的替代了Webview及UIWebView。后者的确存在一些无法解决的bug,诸如架构导致的速度缓慢和内存泄漏。
但无法避免的问题总是有的,比如有些客户端软件,仍然要求兼容老版本的系统,这时候,很不想使用,但也不得不仍然把Webview塞到自己的代码中。
互联网是个喜新厌旧的圈子,网上搜索,几乎只有两类。一是WKWebview的文档,二是iOS类的文档。想要的macOS下面Webview的资料缈如黄鹤。
经过部分只言片语的资料指导和大量的实验,终于完成了工作。所以决定来烧烧冷灶,写出来记录一下。

微信iOS客户端将于2017年3月1日前逐步升级为WKWebview内核,需要网页开发者提前做好网站的兼容检查和适配。

1.添加Webview

最简单添加webview的方法就是直接在Interface
Builder中把Webview拖入到窗口并且用鼠标拖动到指定位置和指定大小,随后在程序中加上对应的变量:

    @IBOutlet weak var webView: WebView!

如果必须动态程序实现,可以使用window.contentView?.addSubview(webView)把webview控件插入到界面中。

背景

WKWebView 是苹果在iOS
8中引入的新组件,目的是提供一个现代的支持最新Webkit功能的网页浏览控件,摆脱过去
UIWebView的老、旧、笨,特别是内存占用量巨大的问题。它使用与Safari中一样的Nitro JavaScript引擎,大大提高了页面js执行速度。

2.载入网页

  1. 可以直接导向到某个网页,也可以先在本地启动一个静态页面文件,后续一些工作可以在本地静态网页中用js处理。这种方法是比较多用的,因为程序启动速度会感觉快的很多。

        let path = Bundle.main.path(forResource: "somepage", ofType: "html")
        let url = NSURL.fileURL(withPath: path!)
        let request = URLRequest(url: url);
        self.webView.mainFrame.load(request);
  1. 把somepage.html添加到项目,并在项目设置中Build Phases->Copy
    Bundle
    Resources中添加上文件somepage.html,这样最后生成app文件的时候,somepage.html文件才会被打包到其中。
  2. 如果建立的项目使用沙箱(sandbox)模式,现在的应用,如果想上App
    Store,一般是强制要求使用沙箱的,需要在系统设置的Capabilities中允许incoming
    network/output
    networking。否则本地网页没问题,之后的任何网站都无法访问。
  3. 新版本的macOS及iOS都强制必须使用https网页访问,如果需要支持老的http网页,还需要在Info.plist中增加一行:App
    Transport Security Settings,类型为字典项,其中增加一项:Allow
    Arbitrary Loads,值为YES。
    完成以上4项,网页已经可以访问了。

切换方法

iOS微信6.5.3版本开始支持开发者手动切换WKWebview和UIWebview,使开发者可提前对WKWebview进行适配。

 

手动切换入口:

在微信会话列表页点击右上角“加号按钮”,选择菜单中的”添加朋友”,在添加朋友界面的搜索框中输入字符串:“:switchweb”,再点击键盘右下角搜索按钮。切换成功后会提示当前使用的内核是UIWebview或是WKWebview。

 

校验切换方法:

通过命令成功切换到WKWebview后,可通过以下方法验证当前网页使用的是否是WKWebview内核。 

微信内任意入口进入任意网页,在网页加载成功后向下拉动页面(或点击网页右上角菜单按钮),使之显示出地址栏,当地址栏以
“此网页由” 开头即为当前使用WKWebview,若以“网页由”则是使用的UIWebview。

 

页面如何判断当前使用的webview内核:

在页面中可通过微信注入的window.__wxjs_is_wkwebview变量判断当前使用的webview内核。
iOS微信6.5.3及其之后的版本 window.__wxjs_is_wkwebview
为true时是使用WKWebview,为 false或者 “undefine”时是 UIWebview 。

前端适配关注的要点

适配的首要原则:若不能区分是WKWebview的新特性新行为还是微信内部逻辑导致原有页面出现问题时,可使用测试页面分别在Safari和微信中的WKWebview内核分别测试,用以快速定位问题产生的原因。

3.从swift调用js

假定在网页中有如下内容:

<script>
function callFromSwift(msg){
    document.getElementById('msgbox').innerHTML=msg;
    return("msg return from js");
}
</script>
<div id='msgbox'></div>

其中定义了一个函数callFromSwift,当被调用的时候,在下面预定义的div中显示传入的字符串,并且返回一个字符串“msg
return from js”。
在swift中调用网页中的callFromSwift函数并获取其返回值可以这样做:

        let s=webView.windowScriptObject.evaluateWebScript("callFromSwift('Hello, JavaScript')")
        NSLog(s as! String) //s是js函数的返回结果,可以是多种类型,本例要求是string

适配指南

切换为WKWebview后,微信中的Webview行为和Safari中保持高度一致,唯一的区别是微信Webview中会注入微信JSBridge相关的脚本。所以适配的重点需要关注以下几个方面: 

一:页面功能是否正常 

二:页面屏幕适配是否正常
三:页面行为是否正常(例如用户在浏览页面时点击返回按钮返回上一个页面时的页面逻辑是否正常) 

澳门新萄京 ,四:页面使用的语法是否兼容。 

五:JSSAPI是否正常完美的工作。 

六:重点关注Cookie和LocalStorage等相关的逻辑是否正常。 

七:若服务器有设置返回
Cache-Control缓存有效时间,则需要检查相关逻辑是否正常。

 

正常情况下,你的页面是不需要做特别的适配,但若你的页面有涉及到以下几个受影响的逻辑,则需要根据适配建议进行适配和确认。

 

JSAPI相关适配

一:将不再支持cache 

变化:在WKWebview中将暂不支持cache jsapi。 

适配建议:所有使用此api的开发者可去掉页面相关逻辑。

 

二:页面通过LocalID预览图片 

变化:不再支持通过使用chooseImage api返回的localld以如:”img
src=wxLocalResource://50114659201332”的方式预览图片。 

适配建议:

  1. 在iOS微信6.5.3版本及之后的版本中,使用新增的jsapi:getLocalImgData
    拿到LocalID对应的图片base64编码后再在前端页面中显示。

2.
如果引入了页面有引入JSSDK,则直接将JSSDK升级为1.2.0最新版本即可帮助页面自动适配。(目前JSSDk线上版本是
1.0.0 和 1.1.0,更新版本为1.2.0
,  )

 

三:有使用JSSDK,并且使用了wx.config进行权限授权需关注jsapi调用的失败问题 

变化:WKWebview的内部实现变更使我们对微信内的页面jsapi权限管理做了一定逻辑上的调整,有极小可能会发生以前授权正常的jsapi获取权限不正常,从而导致调用jsapi失败。 

适配建议:

1. iOS微信6.5.1,WKWebview在此版本中已知有以下问题:页面使用HTML5的History
API pushState; popstate;    
 replaceState等控制页面导航(典型的如单应用页面),同时使用JSSDK的wx.config为jsapi授权,此时大几率会出现jsapi因为无权限而调用失败的问题。
在6.5.1中页面若可能的情况下,可使用Anchor
hash技术替换History技术来解决此问题。

2. iOS微信6.5.2及其之后版本,将不会存在以上问题,但不能100%确认有使用到
history或hash技术更改页面导航地址的页面完全没有此类问题,依然需要开发者注意关注此类问题。

 

Cookie和LocalStorage设置相关

一:退出微信账号后,将会清空所有Cookie和LocalStorage。

 

二:页面功能依赖Cookie,或有涉及到Cookie的相关逻辑 

变化:WKWebview内部实现变更,会影响目前页面Cookie相关的逻辑,例如跨域存取Cookie和页面的资源或图片存储服务器依赖校验Cookie来返回数据等情况。

问题说明:在访问一个页面A时,如果页面A引用了另一个页面B的资源(页面A和B为不同的域名),这时页面B就认为是第三方页面。若在页面B中设置Cookie,就会命中WKWebview下阻止第三方跨域设置Cookie的安全策略,导致问题出现。

适配建议:

在WKWebview中是默认阻止跨域的第三方设置Cookie。所有通过Cookie传递的信息,可通过业务后台存储需要传递的信息,然后给页面一个存储信息相对应的access_token加密码,然后通过Url中加入自己业务的access_token进行页面间信息传递。

如果页面的资源或图片存储的服务器依赖校验Cookie来返回数据的情况,在切换到WKWebview后,在微信内长按保存,或者点击预览大图时,将不会完整的带上所设置的Cookie,会导致图片保存失败或预览失败。除了此种情况,开发者不用担心其他情况下Cookie丢失的问题,所有请求都会带上完整的Cookie。

 

页面视频小窗播放

变化:iOS微信6.5.3及其之后的版本中,Webview默认支持小窗播放。 

开发者需要特别注意小窗播放需要前端同时适配iOS10和iOS10以下的低版本 

适配建议:需要完全按照以下代码设置video标签才可同时兼容不同的iOS版本

<video webkit-playsinline playsinline> </video>

 

WKWebview页面行为与Safari完全一致,会导致页面依赖UIWebview页面行为的逻辑失效或异常:(可根据业务自身逻辑,实现测试页面后分别在Safari和微信WKWebview中验证)

一:Safari或微信WKWebview中
页面A跳转到页面B再返回页面A后不会重新执行Script和Ajax(也不会触发页面reload)。 

二:Safari或微信WKWebview中,在页面弹出输入键盘后,会触发jQuery的resize事件,而在UIWebView下不会。 

三:Safari或微信WKWebview中, window unload
事件在只有刷新才能触发,退出页面或者跳转到其他页面都无法触发。 

四:Safari或微信WKWebview中,极少数情况下某些特殊实现的页面点击事件会失效。

如果有涉及或者遇到以上问题,以兼容Safari行为为准。

4.从js调用swift

前面的3部分都比较容易,跟WKWebview也大同小异。从JS到swift的调用要复杂的多了。
首先在初始化的时候,要加上一句:

        webView!.frameLoadDelegate=self;

对应的,要在类声明的位置加上一个继承:WebFrameLoadDelegate,随后加入代码:

    //为js对象声明一个接口
    func webView(_ webView: WebView!, didClearWindowObject windowObject: WebScriptObject!, for frame: WebFrame!) {
        self.webView.windowScriptObject.setValue(self, forKey: "swiftHost")
    }
    //这个是基本框架,声明了本类中有两个函数会开放给js对象,并供其调用
    //这里示例了两个,一个是callFromJS1,另一个是quit
    //注意swift中的函数名跟js中的函数名可以不一样,
    //#selector中指明的是swift中声明的函数名,因为selector是object-c中的机制,
    //所以后面在声明真正函数的时候,前面必须加@objc的标志
    //在后面return "xxx"的部分,返回的字符串js中会使用的名字,
    //本例中,swift中函数名跟js中函数名使用了相同的名字,我认为这是好习惯
    override class func webScriptName(for aSelector: Selector) -> String?
    {
        //NSLog("%@",aSelector.description)
        if aSelector == #selector(callFromJS1)
        {
            return "callFromJS1"
        }
        else
        if aSelector == #selector(quit)
        {
            return "quit"
        }
        else
        {
            return nil
        }
    }
    //这个函数顾名思义,应当是不允许在js中调用的,对所有的来值都返回false表示全部允许调用
    override class func isSelectorExcluded(fromWebScript aSelector: Selector) -> Bool
    {
        //NSLog("%@",aSelector.description)
        return false
    }
    //具体的函数
    @objc
    func callFromJS1(message:String)
    {
        NSLog(message)
    }
    @objc
    func quit()
    {
        NSLog("call for quit")
        NSApp.terminate(self);
    }

前三个函数是基本的框架,其中第二个麻烦一些,随后实际上工作的函数没有什么特别。
接着来看看js的部分:

    <a href='javascript:testCallSwift();'>testCallSwift</a><p>
    <a href='javascript:needQuit();'>Quit</a><p>
    <script>
        function testCallSwift(){
            //注意调用方式,window是js的对象
            //swiftHost是swift的接口
            //其后则是声明的swift函数
            window.swiftHost.callFromJS1("hello swift");
        }
        function needQuit(){
            window.swiftHost.quit();
        }
    </script>

其他问题

一:页面自定义重载标准方法或者函数时,需要确保不会与微信注入Webview中的JSBridge相关方法冲突,否则会导致页面在微信中的行为异常。

二:强烈建议不要在无法确保页面缓存策略和逻辑与服务器逻辑完全保持一致的情况下冒然设置html页面文件(除了html类型的页面,页面引用的其他资源或脚本按照自身业务合理设置即可)相关的Cache-Control属性。 

典型案例: 

如果第一次访问页面A.html
服务器302跳转到A1.html?uid=111设置Cache-Control:
max-age=60,此A1.html的uid参数是服务器设置的111(此时A1.html已经被客户端缓存)。
第二次访问页面A.html
,服务器同样302跳转到A1.html?uid=222,但是此时的A1.html页面的uid参数是222,
客户端带参数完整链接询问服务器缓存是否可用,
服务器返回缓存可用304,但是客户端缓存的A1.html完整链接带的uid参数是111,所以本地找不到数据,此时加载页面就会失败。

发表评论

电子邮件地址不会被公开。 必填项已用*标注