Мини-программа (6) Идеальное решение для встраиваемых систем H5

внешний интерфейс Апплет WeChat
Мини-программа (6) Идеальное решение для встраиваемых систем H5

Это третий день моего участия в Gengwen Challenge.Подробности о мероприятии, пожалуйста, проверьте:Обновить вызов

1. Разница между мини-программами и H5

1.1 Операционная среда

С точки зрения операционной среды хост-среда H5 — это браузер. Пока есть браузер, его можно использовать, включая компонент веб-просмотра в приложении и компонент веб-просмотра, предоставляемый апплетом. Апплет бывает разный, работает на определенной мобильной программной платформе (Wechat/Alipay/ByteDance/Baidu/QQ и т.д.).

В качестве примера возьмем апплет WeChat, это встроенный парсер, основанный на рефакторинге ядра браузера. Это не полноценный браузер. В официальном документе подчеркивается, что объект окна и объект документа, обычно используемые в браузерах, не могут быть использованы в скрипте. это не API, связанный с DOM и BOM.Этот убивает JQ и некоторые пакеты NPM, которые зависят от BOM и DOM.

1.2 Рабочий механизм

Работа H5 — это работа веб-страницы, я не буду вдаваться в подробности, апплет по-прежнему является примером апплета WeChat.

1.2.1 Запуск

Если пользователь уже открыл определенный апплет и снова открывает апплет в течение определенного периода времени, нет необходимости перезапускать в это время, просто переключите апплет в фоновом состоянии на передний план, и весь процесс так называемый горячий старт. Если пользователь открывает апплет в первый раз или снова открывает апплет после активного уничтожения WeChat, апплет необходимо перезагрузить и запустить, что冷启动.

1.2.2 уничтожение

Когда апплет переходит в фоновый режим на определенный период времени, или использование системных ресурсов слишком велико, или вы вручную уничтожаете его, это настоящее уничтожение.

1.3 Системные разрешения

Где больше всего критикуют H5? Недостаточные системные разрешения, такие как состояние связи в сети, возможность кэширования данных, адресная книга или вызывающее оборудование, такое как функция Bluetooth и другие функции, которые есть у некоторых приложений, H5 не имеет этих системных разрешений, потому что он в значительной степени зависит от возможностей браузера, по-прежнему WeChat Возьмем, к примеру, апплет, эти разрешения на уровне системы клиента WeChat могут быть легко связаны с апплетом WeChat, и официальные лица утверждают, что имеют плавную работу родного приложения.

1.4 Уровень кодирования разработки

Как мы все знаем из разработки H5, стандартные HTML, CSS, JavaScript неотделимы от апплета «Три мушкетера» (Wechat / Alipay / ByteDance / Baidu / QQ и т. д.). WXML — это все пользовательские теги WeChat. Файлы WXSS, JSON и JS записываются немного ограниченными способами. В официальной документации есть четкое введение в использование, хотя с ним легко начать работу, но все же есть различия.

1.5 Механизм обновления

Если вы хотите обновить H5, вы можете обновить его, как хотите.После обновления вы можете игнорировать кеш CDN/браузера.В принципе, вы можете увидеть эффект после завершения обновления. Апплет другой, давайте возьмем WeChat в качестве примера, эй, обновление апплета WeChat должно пройти проверку. Более того, после того, как разработчик выпустит новую версию, она не может сразу затронуть всех существующих пользователей сети, и информация о новой версии будет доведена до пользователей в течение 24 часов после выпуска. Каждый раз при холодном запуске апплет проверяет наличие обновленной версии.Если найдена новая версия, он асинхронно загружает пакет с кодом новой версии и одновременно запускает его с локальным пакетом клиента, поэтому новой версии апплета нужно дождаться следующего холода Он будет применен только после запуска.Конечно, WeChat также имеет wx.getUpdateManager для проверки обновлений.

1.6 Механизм рендеринга

H5 — веб-рендеринг, браузерный рендеринг. Хост-среда апплета WeChat — WeChat.Хост-среда обеспечивает двухстрочную модель для выполнения различных файлов апплета: файла wxml, файла wxss и файла js.

2. Мини-анализ среды программы

Уровень рендеринга и логический уровень апплета управляются двумя потоками:

image.png

  • Интерфейс слоя рендеринга визуализируется с помощью WebView.В одном апплете несколько интерфейсов, поэтому на уровне рендеринга есть несколько WebView.
  • Уровень логики использует потоки JCore для запуска скриптов JavaScript.

Связь между этими двумя потоками передается через родную сторону апплета, и сетевой запрос, отправленный логическим уровнем, также перенаправляется через родную сторону. Первоначальная цель этого дизайна — контроль и безопасность.Апплет WeChat не позволяет разработчикам использовать некоторые открытые интерфейсы, предоставляемые браузерами, такие как переход по страницам, манипулирование DOM и динамическое выполнение сценариев. Логический уровень отделен от уровня представления, и между уровнем представления и логическим уровнем существует только обмен данными, что может помешать разработчикам управлять интерфейсом по своему желанию и лучше обеспечить безопасность пользовательских данных.

Среда выполнения сценария с тремя терминалами и среда, используемая для рендеринга неродных компонентов, различаются:

Рабочая среда логический уровень визуализировать слой
Android V8 Кастомное ядро ​​Chromium
IOS JavaScriptCore WKWebView
Инструменты разработчика мини-программ NWJS Chrome WebView

Слой рендеринга логического слоя среды выполненияAndroidV8ChromiumКастомное ядро ​​IOSJavaScriptCoreWKWebViewИнструменты разработчика мини-программNWJSChrome WebViewПредставление апплета визуализируется в WebView, поэтому способ построения представления, естественно, требует использования языка HTML. Однако существует множество языковых тегов HTML, что увеличивает стоимость понимания, и, используя язык HTML напрямую, разработчики могут использовать<a>Этикетка реализует переход на другие веб-страницы в Интернете, а также может быть анимированной.JAVAScript, вышеупомянутая двухпоточная модель для решения задачи контроля и безопасности — это просто украшение.

Поэтому апплет проектирует набор компонентов фреймворка — Exparser. На основе этого фреймворка встроен набор компонентов, покрывающих основные функции апплета, чтобы разработчики могли быстро построить любой интерфейс. Он также предоставляет возможность настраивать компоненты, и разработчики могут самостоятельно расширять дополнительные компоненты для повторного использования кода. Стоит отметить, что некоторые из более сложных компонентов встроенных компонентов изначально отображаются на стороне клиента, чтобы обеспечить лучшую производительность.

3. Анализ среды браузера H5

Как мы все знаем, кэширование браузера — очень полезная функция, которая может повысить производительность, уменьшить задержку, а также уменьшить пропускную способность и нагрузку на сеть. Механизм кэширования браузеров можно описать двумя следующими предложениями:

  • Каждый раз, когда браузер инициирует запрос, он сначала ищет результат запроса и идентификатор кеша в кеше браузера.
  • Каждый раз, когда браузер получает возвращенный результат запроса, он сохраняет результат и идентификатор кеша в кеше браузера.

Сделав еще один шаг, мы можем получить общее представление о том, как работает принудительное и согласованное кэширование. Если обязательный кеш (Expires и Cache-Control) действителен, кеш используется напрямую. Если он недействителен, выполняется кеш согласования (Last-Modified/If-Modified-Since и Etag/If-None-Match). , Сервер решает, использовать ли согласованный кеш или нет. Кэш, если согласованный кеш недействителен, то кеш, представляющий запрос, недействителен, возвращает 200, возвращает ресурс и идентификатор кеша и сохраняет его в кеше браузера; если действительно, возвращайте 304 и продолжайте использовать кеш. Этот текст предназначен для того, чтобы читатели могли расширить свои знания. Если вы хотите узнать больше, вы можете найти более подробную информацию по некоторым из приведенных выше ключевых слов (надежный кеш, согласованный кеш, срок действия, контроль кеша и т. д.). Компонент веб-просмотра WeChat представляет собой браузер, встроенный в небольшую программу, он не полностью соответствует приведенным выше правилам в отношении кеша, то есть его кеш не может быть вовремя очищен. Вы, должно быть, пробовали следующие операции:

  • Выйдите из апплета вручную и войдите снова;
  • Выйдите и снова откройте WeChat в фоновом режиме и снова войдите в апплет;
  • Изменить конфигурацию Nginx Cache-Control;
  • Используйте debugx5.qq.com, чтобы вручную очистить кеш браузера Android WeChat;
  • iOS использует собственную функцию очистки кеша WeChat.

Несвоевременное обновление кеша приведет к выпуску последней страницы, в то время как предыдущая страница все еще находится в апплете, что принесет много проблем, таких как несогласованность данных на передней и задней частях, новые функции не могут работать вовремя. , а проблема модификации не решена и т.д. Подождите. Здесь нужно пояснить: веб-просмотр может обновляться через определенный промежуток времени (неопределенное время, день или несколько часов, нет очевидной регулярности), и этот чат призван решить проблему своевременного обновления.

3.1 Реализация страницы h5 onShow и межстраничной связи в апплете

Первое, что приходит на ум, это реализация метода onShow, кто-то предложил использовать visibilitychange для реализации метода onShow. Однако после расследования этот метод работает должным образом в ios, но в телефонах Android он не может быть запущен должным образом. Итак, есть следующая схема, которая требует обработки как h5, так и webview апплета.

Основная идея: использовать функцию хеширования веб-просмотра.

  • Апплет передает параметры через хэш, и страница не будет обновляться (это то же самое, что и браузер)
  • h5 может захватывать последние параметры через хешрейт для пользовательской логической обработки.
  • Наконец выполните window.history.go(-1)

Почему вам нужно выполнить window.history.go(-1)?Поскольку изменение хэша приведет к тому, что длина стека истории веб-просмотра будет равна +1, пользователю нужно будет вернуться еще раз. Но этот шаг явно лишний. В то же время, после window.history.go(-1) параметры, добавленные веб-просмотром в хеш, будут удалены, и он также может быть гарантированно таким же, как и предыдущий URL-адрес.

3.2 На что следует обратить внимание

Ради беспрепятственного доступа мы не можем придумать один размер для всех, и мы должны обеспечить доступ к существующим страницам без каких-либо изменений. Новые возможности должны отличаться дополнительными параметрами, такими как: обнаружение части запроса в URL, с__isonshowpro=1Затем передайте параметры с помощью хеш-метода. Измените исходную логику так, чтобы при __isonshowpro=1 логика обработки хэша имела наивысшее определение параметра, а перед ним добавлялись два символа подчеркивания, чтобы разделить обычные параметры в URL-адресе. Посмотрим как реализован sdk на стороне h5

import util from './util';

class WASDK {
  /**
   * Create a instance.
   * @ignore
   */
  constructor(){
    // hashchang事件处理
    if('onhashchange' in window && window.addEventListener && !WASDK.hashInfo.isInit){
      // 更新标志位
      WASDK.hashInfo.isInit = true;
      // 绑定hashchange
      window.addEventListener('hashchange', ()=>{
        // 如果小程序webview修改的hash,才进行处理
        if (util.getHash(window.location.href, '__wachangehash') === '1') {
          // 这块有个坑:
          // ios小程序webview在修改完url的hash之后,页面hashchange和更新都可以正常触发
          // 但是:h5调用部分小程序能力会失败(如:ios在设置完hash后,调用wx.uploadImg会失败,需要重新设置wx.config)
          // 因为ios小程序的逻辑是,url只要发生变化,wx.config中的appId就找不到了
          // 所以需要重新进行wx.config配置
          // 这一步是获取之前设置wx.config的参数(需要从服务端拿,因为之前已经获取过了,这里从缓存直接取)
          const jsticket = window.native && window.native.adapter && window.native.adapter.jsticket || null;
          const ua = navigator.userAgent;
          // 非安卓系统要重新设置wx.config
          if (jsticket && !(ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1)) {
            window.wx.config({
              debug: false,
              appId: jsticket.appId,
              timestamp: jsticket.timestamp,
              nonceStr: jsticket.noncestr,
              signature: jsticket.signature,
              jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ',
                'onMenuShareQZone', 'onMenuShareWeibo', 'scanQRCode', 'chooseImage', 'uploadImage', 'previewImage', 'getLocation', 'openLocation']
            })
          }
          // 触发缓存数组的回调
          WASDK.hashInfo.callbackArr.forEach(callback=>{
            callback();
          })
          // 执行返回操作(这一步是重点!!)
          // 因为webview设置完hash参数后,会使webview历史栈+1
          // 而实际并不需要这次多余的历史记录,所以需要执行返回操作把它去掉
          // 即便是返回操作,也仅仅是hash层面的变更,所以不会触发页面刷新
          // 用setTimeout表示在下一次事件循环进行返回操作。如果后面有对dom操作可以在当前次事件循环完成
          setTimeout(()=>{
            window.history.go(-1);
          }, 0);
        }
      }, false)
    }
  }

  /**
   * hash相关信息
   */
  static hashInfo = {
    // 是否已经初始化
    isInit: false,
    // hash回调数组
    callbackArr: []
  }

  /**
   * 页面再次展示时钩子方法
   * @param {Function} callback - 必填, callback回调方法, 回传参数为hash部分问号后面的参数解析对象
   */
  @execLog
  onShow(callback){
    if (typeof callback === 'function') {
      // 对回调方法进行onshow逻辑包装,并推入缓存数组
      WASDK.hashInfo.callbackArr.push(function(){
        // 检查是否是指定参数发生变化
        if(util.getHash(window.location.href, '__isonshow') === '1'){
          // 触发onShow回调
          callback();
        }
      })
    } else {
      util.console.error(`参数错误,调用onShow请传入正确callback回调`);
    }
  }

  /**
   * 业务处理完成并发送消息
   * @param {Object}           obj - 必填项,消息对象
   * @param {String}           obj.key - 必填项,消息名称
   * @param {String}           obj.content - 可选项,消息内容,默认空串,如果是内容对象,请转换成字符串
   * @param {String|Number}    condition - 可选项,默认仅进行postMessage
   *                           String - 可以传指定url的路径,当小程序webview打开指定的url或者onshow时,会触发该消息
   *                           也可传小程序path,这个为以后预留
   *                           Number - 返回到指定的测试,类似history.go(-1),如: -1,-2
   */
  @execLog
  serviceDone(obj, condition){
    if(obj && obj.key){
      // 消息体
      const message = {
        // 消息名称
        key: obj.key,
        // 消息体
        content: obj.content || '',
        // 触发条件
        trigger: {
          // 类型 'immediately'在下一次onshow中立刻触发, 'url',在找到指定h5链接时触发,'path'在打开指定小程序路径时触发
          type: 'immediately',
          // 条件内容,immediately是为空,url是为h5链接地址,path是为小程序路径
          content: ''
        }
      };
      // 解析触发条件
      condition = condition || 0;
      // 如果是路径
      if(typeof condition === 'string' && (condition.indexOf('http') > -1 || condition.indexOf('pages/') > -1)){
        // 设置消息触发条件
        message.trigger = {
          type: condition.indexOf('http') > -1 ? 'url' : 'path',
          content: condition
        }
      }
      // 发送消息
      wx.miniProgram.postMessage({
        data: {
          messageData: message
        }
      });
      // 如果不是url或者path触发,则对conditon是否需要返回进行判断
      if(message.trigger.type === 'immediately'){
        // 查看是否需要返回指定的层级,兼容传入'-1'字符串这种类型的场景
        try{
          condition = parseInt(condition, 10);
        }catch(e){}
        // 保证返回级数的正确性
        if(condition && typeof condition === 'number' && !isNaN(condition)){
          this.handler.navigateBack({delta: Math.abs(condition)});
        }
      }
    }else{
      util.console.error(`参数错误,调用serviceDone方法,传入的对象中不包含key值`);
    }
  }

  ...
}

window.native = new Native();
export default native;

Это выглядит довольно много, и это можно суммировать в двух пунктах:

Реализация метода onShow

Привязать событие hashchange (здесь для предотвращения повторной привязки событий), кэшировать входящее пользовательское событие onShow в массиве, при срабатывании hashchange определить, срабатывает ли оно по уникальным флагам __isonshow и __wachangehash

Реализация метода serviceDone

Обработайте переданные данные и обработайте условия запуска данных: немедленно означает последний триггер onShow или укажите URL для отправки данных через wx.miniProgram.postMessage

Браузер обращается к ресурсам через URL-адрес, если адрес встроенного H5 не меняется, то ресурсы доступа к веб-просмотру будут браться из кеша, а в кеше нет последних данных, что делает последние ресурсы на серверная сторона невозможна Доступ к браузеру, что объясняет, почему изменение конфигурации Nginx Cache-Control также не вступает в силу. Поэтому, чтобы полностью решить проблему своевременного обновления, необходимо разрешить веб-просмотру доступ к новому адресу. Мы предполагаем, что URL-адрес, к которому обращается апплет:https://www.yourdomain.com/101/#/indexСреди них 101 — это номер версии сборки, который каждый раз увеличивается, чтобы каждый раз быть другим.

3.4 Как оценить среду, в которой находится текущая страница апплета

Эта часть должна установить sdk на странице H5.Например, имя bridge.js.Следующие общие методы, которые я делал в небольших программах в течение нескольких лет:

// bridge.js
let ua = window.navigator.userAgent.toLowerCase();
const globalObj = {
    testDataArr: [],
    doJSReadyFuncExecuted: false,
    errorInfo: '',
    miniappSDK: null,
    miniappType: '',
    actionQueue: [],
    MINIAPP_TYPE: {
        WECHATMINIAPP:  'WECHATMINIAPP',// miniprogram
        WECHATAPP:      'WECHATAPP',    // miniprogram + offiaccount
        OLDQUICKAPP:    'OLDQUICKAPP',  // old
        NEWQUICKAPP:    'NEWQUICKAPP',  // new
        ALIPAYAPP:      'ALIPAYAPP',
        BAIDUAPP:       'BAIDUAPP',
        TOUTIAOAPP:     'TOUTIAOAPP',
        QQAPP:          'QQAPP'         // No longer maintained
    },
    JSSDK_URL_OBJ: {
        WECHATMINIAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
        WECHATAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
        OLDQUICKAPP: 'https://xxxxxxxx/amsweb/quickApp/mixBridge.js',
        NEWQUICKAPP: 'https://quickapp/jssdk.webview.min.js',
        ALIPAYAPP: 'https://appx/web-view.min.js',
        BAIDUAPP: 'https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.21.js',
        TOUTIAOAPP: 'https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js',
        QQAPP: 'https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js'
    },
    bversion: '1.0.0'
}

if(typeof window['__bfi'] == 'undefined') {
    window['__bfi'] = [];
};

window['__bfi'].push([
    '_tracklog', 
    '174537', 
    `ua=${ua}&pageId=\${page_id}`
]);

function isAndroid () {
    return ua.includes('android');
}

function isWechatMiniapp () {
    // @source https://developers.weixin.qq.com/community/develop/doc/00022e37c78b802f186750b5751000
    // in wechat && (in android || in ios)
    return isWechat() && (ua.includes('miniprogram') || window.__wxjs_environment === 'miniprogram');
}

function isWechat () {
    // in wechat-web-browser
    // https://blog.csdn.net/daipianpian/article/details/86543080
    // @source blog ( https://www.jianshu.com/p/6a10f833b099 )
    return /micromessenger/i.test(ua) || /windows phone/i.test(ua);
}

function isOldQuickapp () {
    return (/(hap|OPPO\/Hybrid)\/\d/i.test(ua)) && !isNewQuickapp();
}

function isNewQuickapp () {
    // @source 2020.04.10, Vivo( Li Chunjiao ) has confirmed that this method is feasible
    return ua.includes('mode-quickapp');
}

function isAlipay () {
    // @source 2020.06.15, Alipay has confirmed that this method is feasible
    let isAli = (/APXWebView/i.test(ua)) || (/MiniProgram/i.test(ua) && !ua.includes('micromessenger'));
    // @source 2020.11.17, https://www.yuque.com/books/share/6d822c34-9121-47d8-a805-4c57b0b2d2f0/hiv1tn
    let isUCKuake = ua.includes('aliapp') && (ua.includes('uc') || ua.includes('quark'));
    // @source 2021.03.26
    let isGaode = ua.includes('aliapp') && ua.includes('amapclient');
    return isAli || isUCKuake || isGaode;
}

function isBaidu () {
    // @source 2020.11.05, baidu's doc ( https://smartprogram.baidu.com/docs/develop/component/open_web-view/ )
    return /swan\//.test(ua) || /^webswan-/.test(window.name);
}

function isToutiao () {
    // @source 2020.11.05, toutiao's doc ( https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/component/open-capacity/web-view/ )
    return ua.includes("toutiaomicroapp");
}

function isQQ () {
    // @source 2021.04.21, add ua.includes('miniprogram'), qq's doc ( https://q.qq.com/wiki/develop/miniprogram/component/open-ability/web-view.html )
    return ua.includes('qq') && ua.includes('miniprogram');
}

// return miniapp type of the environment
function isMiniProgram () {
    let appType = false;
    let typeNameObj = globalObj.MINIAPP_TYPE

    try {
        if (isWechatMiniapp()) {
            appType = typeNameObj.WECHATMINIAPP;
        } else if (isOldQuickapp()) {
            appType = typeNameObj.OLDQUICKAPP;
        } else if (isNewQuickapp()) {
            appType = typeNameObj.NEWQUICKAPP;
        } else if (isAlipay()) {
            appType = typeNameObj.ALIPAYAPP;
        } else if (isBaidu()) {
            appType = typeNameObj.BAIDUAPP;
        } else if (isToutiao()) {
            appType = typeNameObj.TOUTIAOAPP;
        } else if (isQQ()) {
            appType = typeNameObj.QQAPP;
        }

        console.log('判断所处环境,isMiniProgram 返回值: ', appType);
        window['__bfi'].push([
            '_tracklog', 
            '174537', 
            `api_name=isMiniProgram&miniappType=${appType}&pageId=\${page_id}`
        ]);

        return appType;
    } catch (e) {
        window['__bfi'].push([
            '_tracklog', 
            '174537', 
            `api_name=isMiniProgram&err_msg=${e.message}&err_stack=${e.stack}`
        ]);
        return false; // 'catch error'
    }
}

export {
    isAndroid,       // 判断H5页面是否处于安卓系统
    isWechatMiniapp, // 判断H5页面是否处于微信小程序环境
    isWechat,        // 判断H5页面是否处于微信环境
    isOldQuickapp,   // 判断H5页面是否处于【老版快应用】小程序环境
    isNewQuickapp,   // 判断H5页面是否处于【新版快应用】小程序环境
    isAlipay,        // 判断H5页面是否处于支付宝小程序环境
    isBaidu,         // 判断H5页面是否处于百度小程序环境
    isToutiao,       // 判断H5页面是否处于头条小程序环境
    isQQ,            // 判断H5页面是否处于QQ小程序环境
    isMiniProgram    // 返回H5页面所处环境的应用名
}

Меры предосторожности при использовании

Перед использованием лучше всего проверить документацию соответствующего апплета, потому что каждый апплет имеет разные уровни поддержки API. Ссылка на файл js не может быть помещена в bridge.js, работает заголовок текущей страницы. Поскольку bridge.js вводится в JSSDK путем добавления тега сценария к тегу head, если в теге head будет добавлен bridge.js, будет сообщено об ошибке.

Если вы открываете h5 и отображается подсказка, например «доступ к странице ограничен», вы можете попробовать выполнить следующие операции: (в этом случае она обычно появляется при открытии URL-адреса h5 тестовой среды). легальность имени домена веб-просмотра» в «Проверке IDE» и «Игнорировать проверку действительности домена запроса».

【Быстрое приложение】На данный момент Vivo, Oppo и Huawei поддержали новую версию быстрого приложения, VivoOPPO запущено, а Xiaomi нет. Для новой версии быстрого приложения, если страница H5 должна вызывать API, предоставленный в новой версии быстрого приложения JS-SDK, доменное имя ссылки H5 должно быть настроено в доверенном URL-адресе заранее ( конфигурация должна быть записана в виде регулярного выражения).

【Похожие заголовки】RedirectTo, navigationTo и другие API-интерфейсы перехода по страницам апплета Toutiao поддерживают только абсолютные пути, начинающиеся с url /

【Связанные с Alipay】Текущая версия 1.0.73 bridge.js оценивает, находится ли он в апплете Alipay, он будет определять, что h5 находится в апплете Alipay, а h5 находится во встроенном браузере Alipay, как в апплете Alipay. Поэтому перед настройкой my.XXXX необходимо настроить функцию инструмента среды оценки, чтобы убедиться, что она действительно находится в апплете Alipay, а не во встроенном браузере Alipay.

3.5 Мини-программа для получения номера последней версии

В апплете мы используем функцию хука onShow приложения для завершения получения последнего URL-адреса и в то же время гарантируем, что другие страницы могут быть загружены только после получения номера версии, поэтому здесь используется вызов синхронного интерфейса. . Пожалуйста, обратитесь к коду ниже:

//这里加入同步请求到服务器获取最新路径
onShow: function (options) {
    this.getFEVersion()
},
getFEVersion: function () {
    //下面是利用Promise进行同步调用的写法
    return new Promise(function (resolve, reject) {
      wx.request({
        //下面是本机调试的一个地址,上线时请改成自己服务端的地址
        url: 'http://192.168.0.168:8090/getFEVersion',
        data: {},
        method: 'POST',
        header: {
          'content-type': 'application/json',
        },
        success: function (res) {
          if (res.data.success) {
            const app = getApp();
            //res.data.version 是从服务端返回的最新fe的版本号,即上面的数字101
            app.globalData.feUrl = 'https://www.yourdomain.com/' + res.data.version + '/#/index'
          }
          resolve();
        },
        fail: function (error) {
          console.log(error);
          reject();
        }

      })
    });
  },
webview动态处理
/**
 * @file 根据入参的小程序类型,动态加载相应的 JavaScript文件
 * 指定<script>元素的src属性,指定事件处理程序(onload事件 onerror事件)
 */

const globalObj = {
    testDataArr: [],
    doJSReadyFuncExecuted: false,
    errorInfo: '',
    miniappSDK: null,
    miniappType: '',
    actionQueue: [],
    MINIAPP_TYPE: {
        WECHATMINIAPP:  'WECHATMINIAPP',// miniprogram
        WECHATAPP:      'WECHATAPP',    // miniprogram + offiaccount
        OLDQUICKAPP:    'OLDQUICKAPP',  // old
        NEWQUICKAPP:    'NEWQUICKAPP',  // new
        ALIPAYAPP:      'ALIPAYAPP',
        BAIDUAPP:       'BAIDUAPP',
        TOUTIAOAPP:     'TOUTIAOAPP',
        QQAPP:          'QQAPP'         // No longer maintained
    },
    JSSDK_URL_OBJ: {
        WECHATMINIAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
        WECHATAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
        OLDQUICKAPP:'https://xxxxxxxxx/amsweb/quickApp/mixBridge.js',
        NEWQUICKAPP: 'https://quickapp/jssdk.webview.min.js',
        ALIPAYAPP: 'https://appx/web-view.min.js',
        BAIDUAPP: 'https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.21.js',
        TOUTIAOAPP: 'https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js',
        QQAPP: 'https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js'
    },
    bversion: '1.0.0'
}

let n = 0;
function loadListener (type) {
    // 先执行一次,再进入setTimeout
    // 多加几个埋点,记录不同类型的信息
    console.log(`====== 重试次数:${n} ======`);
    if(n === 0) {
        processAddRes(type);
    } else {
        setTimeout(function () {
            processAddRes(type);
        }, 200)
    }
}

function processAddRes(type) {
    let curMiniappType = globalObj.miniappType;
    let curLoadJsUrl = globalObj.JSSDK_URL_OBJ[curMiniappType];

    if(!addJSSDKToGlobalObj()){
        n++;

        loadListener();

        if(n % 10 === 0) {
            const msg = `重试达到【${n}】次`
            console.log(msg);
            console.log(globalObj.errorInfo || '======');
        }
        return;
    }

    let actionQueue = globalObj.actionQueue;
    if (actionQueue && actionQueue.length) {
        let aItem = null;
        while (aItem = actionQueue.shift()) {
            try {
                globalObj.miniappSDK[aItem.apiName].apply(globalObj.miniappSDK, aItem.args)
            } catch (e) {
                //
            }
        }
    }
}

// 将JSSDK提供的方法保存到global
function addJSSDKToGlobalObj () {
    let curMiniappType = globalObj.miniappType;

    try{
        let _miniappSDK = null;
        switch(curMiniappType) {
            case 'WECHATMINIAPP':
            case 'WECHATAPP':
            case 'OLDQUICKAPP':
                _miniappSDK = typeof wx !== 'undefined' && wx.miniProgram;
                break;
            case 'NEWQUICKAPP':
                _miniappSDK = qa;
                break;
            case 'ALIPAYAPP':
                _miniappSDK = my;
                break;
            case 'BAIDUAPP':
                _miniappSDK = typeof swan !== 'undefined' && swan.webView;
                break;
            case 'TOUTIAOAPP':
                _miniappSDK = typeof tt !== 'undefined' && tt.miniProgram;
                break;
            case 'QQAPP':
                _miniappSDK = typeof qq !== 'undefined' && qq.miniProgram;
                break;
        }

        if(_miniappSDK) {
            globalObj.miniappSDK = _miniappSDK
        }

        if (!globalObj.miniappSDK || !globalObj.miniappSDK.navigateTo) {
            console.log(globalObj)
            let g_errmsg = (!globalObj.miniappSDK ? 'miniappSDK_is_undefined' : 'API_is_undefined');
            let g_errstack = 'none'
            globalObj.errorInfo = 'g_errmsg=' + g_errmsg + '&g_errstack=' + g_errstack;

            return false;
        }
    } catch (e) {
        // 记录下是什么原因return的false: 在return false 的地方,将原因挂到全局变量上,loadListener触发埋点时,记录下来
        globalObj.errorInfo = 'g_errmsg=' + e.message + '_have_catch_error' + '&g_errstack=' + e.stack;

        return false;
    }
    globalObj.errorInfo = 'g_errmsg=outof_try-catch_return_true';

    return true;
}

function parseQuery(url) {
    let query = {};
    let idx = url.indexOf("?");
    let str = url.substr(idx + 1);
    if (str == "" || idx == -1) {
        return {};
    }
    let pairs = str.split('&');
    for (let i = 0; i < pairs.length; i++) {
        let pair = pairs[i].split('=');
        // 当根据 = 号分割后有多条数据时,从数组第1位起之后的要全部保留。
        // 比如 src=/issue/create?type=1752,要处理成为:src: '/issue/create?type=1752',而不是 src: '/issue/create?type'
        if (pair.length > 2) {
            pair[1] = pair.slice(1).join('=');
        }
        query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
    }
    return query;
};

function loadScript() {
    // 埋点信息,增加加载的jssdk-url,后续可能可以从url中获取到微信的版本号
    // 设备品牌、设备型号、微信版本号、操作系统及版本、客户端平台、客户端基础库版本 Object wx.getSystemInfoSync()
    let curMiniappType = globalObj.miniappType;
    let curLoadJsUrl = globalObj.JSSDK_URL_OBJ[curMiniappType];

    let jSBridgeReady = function(type) {
        console.log('jSBridgeReady, event type: ', type);

        // 保证后续逻辑只会执行一次
        if (globalObj.doJSReadyFuncExecuted) {
            return;
        }
        globalObj.doJSReadyFuncExecuted = true;
        console.log('script is onload, doJSReadyFuncExecuted')

        loadListener(type);
    }


    if (curMiniappType === "WECHATMINIAPP" || curMiniappType === "WECHATAPP" || curMiniappType === "OLDQUICKAPP") {
        // 监听WeixinJSBridgeReady 和 onload 前,发个埋点,看下当前是否已经有wx 和 wx.miniProgram(因为目前nfes 只引入了微信jssdk)
        document.addEventListener('WeixinJSBridgeReady', function() {
            console.log('WeixinJSBridgeReady ======');
            jSBridgeReady('WeixinJSBridgeReady')
        }, false)
    }

    if (curMiniappType === "NEWQUICKAPP") {
        document.addEventListener('QaJSBridgeReady', function() {
            console.log('QaJSBridgeReady ======');
            jSBridgeReady('QaJSBridgeReady')
        }, false)
    }

    let script = document.createElement("script");
    script.src = curLoadJsUrl;
    script.async = false; // 注释掉,因为添加async的话,执行顺序无法保证

    let scriptArr = document.getElementsByTagName('script');
    console.log(scriptArr);

    for(let i = 0; i < scriptArr.length; i++) {
        let item = scriptArr[i];
        if(item.src.includes('/ares2/market/mixappBridge/') && item.src.includes('/default/bridge')) {
            // 取参数,动态设置async
            let queryObj = parseQuery(item.src); // 兜底的值为 {}
            console.log('queryObj: ', queryObj);
            if(typeof queryObj.bridgeAsync !== 'undefined') {
                script.async = queryObj.bridgeAsync === '1' ? true : false;
            }
        }
    }
    console.log('最终,script.async: ', script.async);

    script.onload = function(e) {
        console.log('script is onload ======')
        jSBridgeReady('onload')
    }

    script.onerror = function(e) {
        console.log('script is onerror')
    }

    window.onerror = function(message, source, lineNo, columnNo, error) {
        // to do track
    }
    document.getElementsByTagName('head')[0].appendChild(script)
}

export {
    loadScript
}

Краткое изложение бизнес-деталей веб-просмотра небольшой программы в действии

5.1 Различайте окружение

WeChat предоставляет переменную среды.После загрузки h5 первая страница может быть получена вовремя, но последующие страницы могут быть получены только после загрузки WeChat SDK, поэтому рекомендуется судить в случае wx.ready или weixinjsbridgeready, разница в том, что первому нужно загрузить jweixin.js, но здесь есть яма, яма в том, что разработчики h5 могут не знать, что ваш процесс обнаружения занимает время, это асинхронный процесс, им может понадобиться вызвать какой-то api, в это время могут возникать ошибки, поэтому необходимо предусмотреть очередь и механизм ожидания для вызовов api. Подробности смотрите в приведенном выше коде.

5.2 Оплата

Вторая распространенная проблема — это оплата, т.к. оплата WeChat не поддерживается напрямую в webview апплета, поэтому в основном когда нужно заплатить, нужно зайти в апплет и после оплаты вернуться обратно. После того, как вышеописанное сделано, достаточно вызвать блок h5 одним предложением. В случае продукта с большим количеством встроенных страниц H5 лучше всего разделить его на две платежные страницы в зависимости от бизнеса.Во-первых, у некоторых предприятий есть собственная полная система транзакций в H5, и действие заказа может быть завершено в H5, и им нужны только небольшие программы. Оплата, поэтому у нас есть оптимизированная страница оплаты, и мы можем напрямую начать оплату WeChat, когда зайдем. мы можем напрямую войти в кассу нашего апплета Рисунок Выше приведена основная логика в SDK Мы используем параметр payOnly, чтобы решить, на какую страницу войти.

Давайте посмотрим, как реализован упрощенный платеж в апплете.После загрузки напрямую вызывается API для запуска платежа WeChat.После успешного платежа, в соответствии с параметрами, возвращаемыми h5, если это страница апплета, затем перейдите непосредственно к нему, в противном случае обновите страницу веб-просмотра, а затем вернитесь назад.

5.3 Вернуться в левый верхний угол

Итак, как решить этот вид потери, мы добавили функцию возврата в верхнем левом углу. Первое, что нужно ввести, это пустая страница переноса, а затем введите страницу h5, чтобы кнопка «Назад» появилась в верхнем левом углу.Когда пользователь нажимает кнопку возврата в верхнем левом углу, страница будет перезагружена на домашняя страница апплета, которая кажется простой Небольшие действия на самом деле оказывают большое влияние на бизнес.Давайте посмотрим на две цифры.После нашей статистики мы обнаружили, что частота кликов кнопки возврата в верхнем левом углу достигает 70%, потому что эта посадочная страница вообще расшаривается пользователями., раньше, когда использовался чистый h5, вернуться можно было только через левый верхний угол, так что пользователи привыкли к этому в апплете.Второе число, после перезагрузки на главную страница, скорость доступа к последующей странице составляет более 10%.Эти два числа хороши для улучшения бизнеса.На самом деле довольно много. Принцип реализации очень прост, и обрабатывается он запуском onShow во второй раз.

В: Возможно, у меня проблема с синхронизацией входа и выхода.

A:Перейдите на личную страницу, чтобы завершить вход. В это время только что открытое веб-представление синхронизирует состояние входа на обоих концах. Нажмите «Назад», чтобы вернуться к предыдущему веб-представлению. В это время вложенная домашняя страница этого веб-представления не запускает событие onshow реакции-imvc. Эта страница устарела, и то же самое верно для выхода из системы, поэтому на домашней странице она перейдет к входу в систему h5 вместо входа в апплет, что приведет к рассинхронизации статуса входа. Решение: вам нужно вернуться на домашнюю страницу и обновить страницу h5.

Недоразумение: сразу после личного входа перезапуск на домашнюю страницу не приведет к прямому вызову выхода из веб-просмотра для замены токена, невозможно выйти Решение. Когда сегмент суждения вернется с личной страницы, установите URL-адрес веб-просмотра, добавьте параметр и обновите его.