码迷,mamicode.com
首页 > 其他好文 > 详细

记:Hybrid复杂交互数据处理

时间:2021-06-02 19:43:23      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:wkwebview   use   加载完成   接口调用   回退   path   客户   平台   direct   

引:

  • 只要是使用App开发都避免不了发布审核的问题,这个审核时长将会导致即将到来的活动或者其他临时性改动不能及时响应到客户端;
  • 另一方面,一套H5开发可以发布到不同的平台,也可以省去IOS和Android并行开发发布上线的问题;
  • 所以这时候就有了hybrid技术,使用各个平台的webview来内嵌对应H5功能来实现实时的功能,完全可以由接口控制对应的功能,只要链接不同,那么对应页面也就不一样,功能也就不同;
  • 当然小程序也避免不了审核,另起一篇再讲讲小程序处理内嵌遇到的一些问题;

既然使用了内嵌,可以使用App原生的一些功能,可以用App分享海报落地图片给微信,调取App的定位功能,调用App支付功能,调用App连接各种硬件;

在内嵌的时候,如果功能复杂了就会出一些问题,这里列举几个(以下几条都是我实际遇到的问题,从开始到最后都解决过):

  1. App登录后,Cookie传给H5,App注入Cookie失败问题;
  2. 一个功能必须要A webview 切换到B webview 处理,比如这个功能需要铺满全屏等等功能不得不起新的webview;
  3. 第2条再附加一个条件,有本地存储的数据需要在B webview页面参与使用或者展示;
  4. 第3条再附加一个条件,B webview页面有操作数据需要在B 返回A时带入A webview页面参与使用或展示;
  5. 第4条再附加一个条件,B webview页面操作后返回A webview,A页面需要刷新接口但要保持A的window上的临时数据;

我所负责的项目原本只在微信公众号中使用的,开发使用的是vue-cli,由于业务需求,2020年已实现基于FlutterHybrid和微信小程序的Hybrid,项目迁移原生化成本比较大,不同平台对应的功能也有所区别,这里只不过总结一下;

以上需要解决的有:运行环境判定问题,cookie注入问题,webview切换实现问题,webview切换的数据同步问题,页面返回页面状态改变问题;


一. 实现App给页面注入Cookie及环境判定

App有自己的一套登录流程,且可以进行微信授权登录,所以只需要把登录token(下方统一称为‘userToken‘)注入给H5就可以实现H5的自身功能,这里注意,此userToken在公众号使用时已被设置成HttpOnlySameSite为Lax;

HttpOnly和SameSite
  HttpOnly:由服务端设定对应的cookie,在客户端无法使用js访问且无法使用js设定这个cookie,可以有效地阻止XSS攻击;
  SameSite:对应的cookie发送权限,可有效的阻止CSRF攻击,有以下三种设定值
    - Strict:完全禁止第三方cookie,跨站点时,任何情况下都不会发送 cookie
    - Lax:  设定的对应站点可以,其他不允许发送,导航到站点的Get可以
    - None:关闭cookie的访问限制权限 
  

这里使用的运行App环境判定有三种,且一套代码在三个平台运行,所以需要首先判定环境再做后续操作(把判定环境标记注入到cookie中以便接口识别来源,可以是用于业务区分和BI埋点分析):

  • 让app给webview植入对应内嵌特殊navigator.userAgent标记,我们在初始化的时候就取对应标记来分辨是否为App内嵌环境,写入cookie中;
  • 在webview的url上让App开发拼接对应的标记,若取不到userAgent的(可以由一些问题造成无法注入或注入失败)标记,则在url上获取对应的标记,并写入cookie中;
  • 让App开发在webview创建时注入对应的cookie,然后js获取对应的标记;

标记运行环境后,判定为App内嵌则注入对应环境标记和userToken给cookie,然后等待页面渲染和执行对应的接口请求数据。

说明:当一个webview创建时且未调用接口,首次注入userToken不会被阻止,这里可能误打误撞的直接成功注入,但是有些手机机型是注入比较慢的,在接口调用前这个cookie还未注入userToken,接口返回后userToken已经被设定为HttpOnly状态,这时再次注入就失败了,所以需要提前获取到对应运行环境和注入cookie


二. 页面跳转创建新的App webview和参数传值

这里就需要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上绑定的数据,localStoragesessionStorage等(其他的暂时没用到,至于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-iffalsetrue会使子组件先注销再注册,这样组件的一些生命周期就会重新走一遍,在内部的一些ajax请求也会重新请求,以达到刷新的功能,并且上方InjectionRequest方法不仅仅起到数据传入的作用,而且在页面回退时,由App进行调用,其上一个页面也会进行刷新,有些人可能会想到为什么不用页面刷新功能呢?window上绑定的一些数据在页面刷新的时候都会初始化,但是这不是我们所需要的,我们实现了组件的刷新;

其实在上方实现中还有其他问题,在第一部分说过,在注入cookie时可能会慢于js执行速度,同样数据同步也有这样的问题,我们约定了一个获取数据的时机,等待webviewready之后才能再去请求数据,同时可以请求获取定位信息,或者回退控制权等等,那么这时就需要一套机制,下方为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时遇到的问题和解决(忍不住吐槽,集成多个环境的缝合怪);

记:Hybrid复杂交互数据处理

标签:wkwebview   use   加载完成   接口调用   回退   path   客户   平台   direct   

原文地址:https://www.cnblogs.com/sixthrhapsody/p/14835969.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!