标签:wkwebview use 加载完成 接口调用 回退 path 客户 平台 direct
既然使用了内嵌,可以使用App原生的一些功能,可以用App分享海报落地图片给微信,调取App的定位功能,调用App支付功能,调用App连接各种硬件;
在内嵌的时候,如果功能复杂了就会出一些问题,这里列举几个(以下几条都是我实际遇到的问题,从开始到最后都解决过):
Cookie
传给H5,App注入Cookie失败问题;我所负责的项目原本只在微信公众号中使用的,开发使用的是vue-cli
,由于业务需求,2020年已实现基于Flutter
的Hybrid
和微信小程序的Hybrid
,项目迁移原生化成本比较大,不同平台对应的功能也有所区别,这里只不过总结一下;
以上需要解决的有:运行环境判定问题,cookie注入问题,webview切换实现问题,webview切换的数据同步问题,页面返回页面状态改变问题;
Cookie
及环境判定App有自己的一套登录流程,且可以进行微信授权登录,所以只需要把登录token(下方统一称为‘userToken‘)注入给H5就可以实现H5的自身功能,这里注意,此userToken在公众号使用时已被设置成HttpOnly且SameSite为Lax;
HttpOnly:由服务端设定对应的cookie,在客户端无法使用js访问且无法使用js设定这个cookie,可以有效地阻止XSS攻击; SameSite:对应的cookie发送权限,可有效的阻止CSRF攻击,有以下三种设定值 - Strict:完全禁止第三方cookie,跨站点时,任何情况下都不会发送 cookie - Lax: 设定的对应站点可以,其他不允许发送,导航到站点的Get可以 - None:关闭cookie的访问限制权限
这里使用的运行App环境判定有三种,且一套代码在三个平台运行,所以需要首先判定环境再做后续操作(把判定环境标记注入到cookie中以便接口识别来源,可以是用于业务区分和BI埋点分析):
webview
植入对应内嵌特殊navigator.userAgent
标记,我们在初始化的时候就取对应标记来分辨是否为App内嵌环境,写入cookie中;userAgent
的(可以由一些问题造成无法注入或注入失败)标记,则在url上获取对应的标记,并写入cookie中;标记运行环境后,判定为App内嵌则注入对应环境标记和userToken
给cookie,然后等待页面渲染和执行对应的接口请求数据。
说明:当一个webview创建时且未调用接口,首次注入userToken不会被阻止,这里可能误打误撞的直接成功注入,但是有些手机机型是注入比较慢的,在接口调用前这个cookie还未注入userToken,接口返回后userToken已经被设定为HttpOnly状态,这时再次注入就失败了,所以需要提前获取到对应运行环境和注入cookie
;
这里就需要App开发配合注入对应的调用方法,这个方法在js调用时会通知App作出对应的反馈,那么就需要约定一下参数,比如:
...
// AppSkipCtrl 跳转控制
function AppSkipCtrl (to, from, next) {
...
// 调用callHandler方法,并传入对应调用实现类型,这里是pushUrl为跳转到下一个页面,并新开一个webview
window.app_inappwebview.callHandler(‘pushUrl‘, {
// 所跳转的页面地址
path: ‘#‘ + to.fullPath,
// 所跳转的页面title
title: to.meta.title || ‘‘,
// 这里的参数可暂时不看,后边会讲到
dataList: transData(),
...
});
return;
...
// 根据上边的一些逻辑判断是否需要走App还是走H5,若是H5则调用下方next
next(true);
}
...
上方代码AppSkipCtrl中调用App的callHandler
方法,外层为适配当前Vue而封装的,也可以单独拿出来使用,以下就是调用方式
...
beforeRouteUpdate: AppSkipCtrl,
beforeRouteLeave: AppSkipCtrl,
...
上方两个方法都是Vue路由组件内导航守卫
本地缓存有window
上绑定的数据,localStorage
和sessionStorage
等(其他的暂时没用到,至于vuex
也是绑定到window上的),接着上方说过的dataList: transData()
,这个参数就是需要收集的本地缓存,然后把此数据先存给App,等App创建完webview后再把这些数据回传给页面,因为数据量太大,不能仅仅只是从url上传递,限制太多,dataList
可以不仅仅存一些数据,可扩展性很大,还可以存一些控制参数,之后会提到。
function transData (data) {
...
const dataList = [
...
{
type: ‘window‘,
data: {
...
‘abc‘: window.abc || ‘‘,
...
}
},
{
type: ‘localStorage‘,
data: {
...
‘bcd‘: window.localStorage.getItem(‘bcd‘) || ‘‘,
...
}
},
{
type: ‘sessionStorage‘,
data: {
...
‘def‘: window.sessionStorage.getItem(‘def‘) || ‘‘,
...
}
},
...
];
let dataStr = JSON.stringify(dataList);
dataStr = window.encodeURIComponent(dataStr);
dataStr = window.btoa(dataStr);
return dataStr;
};
export default transData;
跳转到一个新的webview后,首先会走第一步的cookie注入,然后拿上一个页面的缓存存入本地,这里需要App调用约定好的js方法,约定一下数据存入的大小不能超过太多,js获取后转换然后塞进对应的缓存中,下方为获取及转换:
function AllControll (callback) {
...
InjectionRequest(callback);
...
}
...
function InjectionRequest (callback) {
window.app_inappwebview.onInjectionData = data => {
if (!data) {
return;
}
// 先获取data内容然后转换成json
if (typeof data === ‘string‘) {
data = window.atob(data);
data = window.decodeURIComponent(data);
data = JSON.parse(data);
}
// 这里的callback提供回调
this.setInjectionData(data, callback);
};
}
...
function setInjectionData (list, callBack) {
if (list && Array.isArray(list)) {
// 把这些转换后的数据分别塞进对应本地缓存中
list.forEach(item => {
if (item.type === ‘window‘ && item.data) {
Object.keys(item.data).forEach(child => {
window[child] = item.data[child] && item.data[child];
});
} else if (item.type === ‘localStorage‘ || item.type === ‘sessionStorage‘) {
Object.keys(item.data).forEach(child => {
window[item.type].setItem(child, item.data[child] ? item.data[child] : ‘‘);
});
}
...
});
}
...
callback && callback( ... );
...
}
...
以上有一个callback
参数,下边就讲到callback
的作用了(文件为App.vue
):
<template lang="pug">
router-view(v-if=‘reloadAppSkip‘)
</template>
<script>
...
export default {
...
mounted () {
...
if (window.isApp) {
hybridSetting.AllControll(() => {
this.reload();
});
}
...
}
methods: {
...
reload () {
console.log(‘reload页面---begin‘);
this.reloadAppSkip = false;
this.$nextTick(() => {
console.log(‘reload页面前---last‘);
this.reloadAppSkip = true;
});
},
...
}
...
}
</script>
以上代码:在App.vue
中,我们可以这么使用,父组件的v-if
的false
和true
会使子组件先注销再注册,这样组件的一些生命周期就会重新走一遍,在内部的一些ajax请求也会重新请求,以达到刷新的功能,并且上方InjectionRequest
方法不仅仅起到数据传入的作用,而且在页面回退时,由App
进行调用,其上一个页面也会进行刷新,有些人可能会想到为什么不用页面刷新功能呢?window上绑定的一些数据在页面刷新的时候都会初始化,但是这不是我们所需要的,我们实现了组件的刷新;
其实在上方实现中还有其他问题,在第一部分说过,在注入cookie
时可能会慢于js执行速度,同样数据同步也有这样的问题,我们约定了一个获取数据的时机,等待webview
的ready
之后才能再去请求数据,同时可以请求获取定位信息,或者回退控制权等等,那么这时就需要一套机制,下方为IOS的WKWebView
各个时机调用的方法,App
开发在didFinishNavigation
通知js准备好了:
// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{}
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{}
// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
// 这里插入ready代码通知H5我准备好了,你可以调用了
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{}
// 接收到服务器跳转请求之后调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{}
在这之前使用观察者模式来注册一系列需要准备调用app的方法,App开发在IOS的didFinishNavigation
时机进通知js,然后js在ready
之后把注入的一系列方法列表通知给App,App再回调js对应的方法处理后续调用;
我们在App.vue中已经注入了AllControl
方法,且还有个回调,这个回调起到刷新的作用,那么这里再列一个返回控制的,举个例子:
function AllControl (callback) {
// 数据注入
InjectionRequest(callback);
// 返回控制
BackPageControl();
// 一系列需要注入的方法,或有回调或没有
...
AllTriggerEvent();
}
function InjectionRequest (callback) {
// 先注入App调用js的方法
window.app_inappwebview.onInjectionData = data => {
...
callback && callback();
...
};
// 把触发事件注入eventlist中
if (sourceKeys.eventList.indexOf(‘InjectionData‘) > -1) {
sourceKeys.eventList.push(‘InjectionData‘);
}
}
function BackPageControl () {
// 监听App的返回,App通知js,js调用对应的方法传值给App,再由App回退
window.app_inappwebview.onBackPageControl = () => {
...
// 这里同样把数据传给App,然后App返回,
...
};
// 把触发事件注入eventlist中,如果有些页面为第三方的,则就没有这些js方法,我们只注入有的
if (sourceKeys.eventList.indexOf(‘BackPageControl‘) > -1) {
sourceKeys.eventList.push(‘BackPageControl‘);
}
}
// 所有的方法将在此处通知给App,由App来进行处理对应的操作
function AllTriggerEvent () {
// 在App的didFinishNavigation方法中调用AppWebViewPlatformReady方法通知js
window.addEventListener(‘AppWebViewPlatformReady‘, event => {
sourceKeys.eventList && sourceKeys.eventList.length > 0 && sourceKeys.eventList.forEach(item => {
item && window.app_inappwebview.callHandler(item);
});
// 此标记我们可以用于判定是否为首次进入页面的且reload过了,之后只要是返回就可以限制做其他的一些判定
window.appReloadInitStatus = true;
});
}
...
以上所有的代码不仅要兼容App内嵌Hybrid
方式,也要兼容普通微信浏览器的功能,一些细节功能没有列出来,只是把跨webview
的跳转及数据传输的功能大概讲了一下;
在下一篇文章讲到内嵌小程序的webview
时遇到的问题和解决(忍不住吐槽,集成多个环境的缝合怪);
标签:wkwebview use 加载完成 接口调用 回退 path 客户 平台 direct
原文地址:https://www.cnblogs.com/sixthrhapsody/p/14835969.html