Практика автономного пакетного решения гибридного приложения (с открытым исходным кодом)

внешний интерфейс

Статья впервые опубликована в моем блогеGitHub.com/MCU король/Но...

задний план

В гибридном режиме разработки H5 + Native наиболее критикуемой проблемой, вероятно, является проблема белого экрана в процессе загрузки страниц H5. На следующем рисунке описан весь процесс от инициализации WebView до окончательного рендеринга страницы H5.

image

Текущие основные методы оптимизации в основном включают:

  1. Для инициализации WebView: этот процесс занимает примерно 70–700 мс. Когда клиент только запущен, глобальный WebView может быть инициализирован заранее, чтобы его можно было использовать и скрыть. Когда пользователь получает доступ к WebView, непосредственно используйте WebView для загрузки соответствующей веб-страницы и ее отображения.

  2. Для отправки запросов интерфейса на серверную часть: когда клиент инициализирует WebView, натив напрямую начинает запрашивать данные из сети, когда инициализация страницы завершена, натив получает данные, запрошенные его прокси.

  3. Динамическое объединение html для загруженного js (одностраничное приложение): можно использовать многостраничную упаковку, рендеринг на стороне сервера и предварительный рендеринг во время создания.

  4. Для размера загружаемых ресурсов страницы: можно использовать ленивую загрузку и другие методы для разделения частей, требующих больших ресурсов, а затем асинхронно запрашивать разделенные ресурсы после завершения общего рендеринга страницы, чтобы повысить общую скорость загрузки страницы.

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

Технические решения

Во-первых, общая идея объясняется ниже:

Мы можем сначала упаковать и предварительно загрузить статические ресурсы, необходимые для страницы, в установочный пакет клиента. Когда пользователь устанавливает, распаковывает ресурсы в локальное хранилище. Когда WebView загружает страницу H5, он перехватывает все отправленные http-запросы. существует ли запрошенный ресурс локально, и вернуть ресурс напрямую, если он существует.

Ниже приводится общая диаграмма технической решения, в которой я использую Jenkins по умолчанию для CI / CD, конечно, могут также использоваться другие методы.

image

внешний интерфейс

Соответствующий код:

Плагин для упаковки офлайн-пакетов:GitHub.com/MCU король/выкл…

Интерфейсный проект для плагина приложения:GitHub.com/MCU король/шаблоны…

Во-первых, необходимо одновременно генерировать автономные пакеты во время процесса упаковки внешнего интерфейса.Моя идея состоит в том, что плагин веб-пакета проходит и читает объект компиляции (представляющий построение одной версии и генерацию ресурсов), когда emit hook (перед генерацией ресурсов и выводом их в каталог) Берем упакованные и сгенерированные webpack ресурсы, а затем записываем информацию о каждом ресурсе (диапазон обхода может быть ограничен типом файла) в json карты ресурсов , Подробности следующие:

Пример json сопоставления ресурсов

{
  "packageId": "mwbp",
  "version": 1,
  "items": [
    {
      "packageId": "mwbp",
      "version": 1,
      "remoteUrl": "http://122.51.132.117/js/app.67073d65.js",
      "path": "js/app.67073d65.js",
      "mimeType": "application/javascript"
    },
    ...
  ]
}

Где remoteUrl — это адрес ресурса на сервере статических ресурсов, а путь — это локальный относительный путь на клиенте (путем перехвата запроса сервера, соответствующего ресурсу, локального обращения к соответствующему ресурсу в соответствии с относительным путем и его возврата) .

Наконец, файл json сопоставления ресурсов и статические ресурсы, которые необходимо локализовать, упаковываются в zip-пакет для использования в последующих процессах.

Автономная платформа управления пакетами

Соответствующий код:

Интерфейс и серверная часть платформы управления пакетами в автономном режиме:GitHub.com/MCU король/выкл…

инструмент сравнения файлов:GitHub.com/exo way/ Время от времени…

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

Платформа офлайн-пакетов призвана решить эту проблему. Ниже я используюmobile-web-best-practiceЭтот интерфейсный проект является примером, объясняющим весь процесс:

mobile-web-best-practiceНазвание автономного пакета, соответствующего проекту, — main.Первая версия может быть предварительно установлена ​​в пакете установки клиента, как описано выше, а автономный пакет может быть загружен на платформу управления автономными пакетами.В дополнение к сохранению автономного пакета файл пакета и связанная с ним информация. В дополнение к информации также создается файл json с именем packageIndex, то есть файл, в котором записана коллекция всей соответствующей информации об автономном пакете, и этот файл в основном предоставляется клиенту для загрузки. Общее содержание следующее:

{
  "data": [
    {
      "module_name": "main",
      "version": 2,
      "status": 1,
      "origin_file_path": "/download/main/07eb239072934103ca64a9692fb20f83",
      "origin_file_md5": "ec624b2395a479020d02262eee36efe4",
      "patch_file_path": "/download/main/b4b8e0616e75c0cc6f34efde20fb6f36",
      "patch_file_md5": "6863cdacc8ed9550e8011d2b6fffdaba"
    }
  ],
  "errorCode": 0
}

Среди них данные представляют собой набор информации обо всех связанных автономных пакетах, включая версию, статус и URL-адрес файла, а также значение md5 автономного пакета.

когдаmobile-web-best-practiceПосле обновления пройдетoffline-package-webpack-pluginПлагин упакован в новый автономный пакет. В это время мы можем загрузить автономный пакет на платформу управления, и версия основного автономного пакета в packageIndex будет обновлена ​​до 2.

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

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

Для этой проблемы мы можем использовать инструмент сравнения файлов —bsdiff-nodejs, инструмент узла вызывает алгоритм bsdiff, реализованный на языке c (на основе сравнения двоичных файлов для расчета пакета diff/patch). При загрузке автономного пакета с версией 2 на платформу управления платформа будет сравниваться с ранее сохраненным автономным пакетом с версией 1, чтобы вычислить разницу между пакетами от 1 до 2. Клиенту нужно только загрузить дифференциальный пакет, а затем использовать инструмент, основанный на алгоритме bsdiff, для исправления локального автономного пакета версии 1 для создания автономного пакета версии 2.

На этом общие принципы автономной платформы управления пакетами завершены, но есть еще области, требующие улучшения, такие как:

  1. Добавить функцию журнала

  2. Добавлена ​​статистическая функция скорости поступления офлайн-посылок

...

клиент

Похожие материалы:

Проект Android, интегрирующий автономную библиотеку пакетов:GitHub.com/MCU король/шаблоны…

Автономная библиотека пакетов клиента в настоящее время разработана только для платформы Android, и библиотека доступна вwebpackagekitВторичная разработка, основанная на (лично разработанной библиотеке офлайн-пакетов Android), в основном реализует многоверсионный файловый менеджер ресурсов, который может поддерживать несколько интерфейсных офлайн-пакетов, предварительно заданных в клиенте. Исходный код перехваченного запроса выглядит следующим образом:

public class OfflineWebViewClient extends WebViewClient {
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        final String url = request.getUrl().toString();
        WebResourceResponse resourceResponse = getWebResourceResponse(url);
        if (resourceResponse == null) {
            return super.shouldInterceptRequest(view, request);
        }
        return resourceResponse;
    }

    /**
     * 从本地命中并返回资源
     * @param url 资源地址
     */
    private WebResourceResponse getWebResourceResponse(String url) {
        try {
            WebResourceResponse resourceResponse = PackageManager.getInstance().getResource(url);
            return resourceResponse;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Перехватите HTTP-запрос, переопределив метод TointercePtreequest класса WebViewClient и выяснить, есть ли соответствующие статические ресурсы в интернет-панели, и возвращаются напрямую, если есть.

Ответы на некоторые вопросы

1. Можно ли автоматически обновлять автономные пакеты?

Когда интерфейсные ресурсы автоматически упаковываются компьютером CI, а затем развертываются на сервере статических ресурсов, как загрузить их на платформу автономных пакетов? Я когда-то считал, что когда интерфейсные ресурсы упакованы, они автоматически загружаются на платформу офлайн-пакетов через интерфейс. Однако было обнаружено, что осуществимость невысока, потому что наши интерфейсные ресурсы необходимо обновлять, вручную изменяя версию докера в процессе эксплуатации и обслуживания после этапа тестирования. Если он загружается автоматически, автономная платформа пакетов уже загрузила непроверенные интерфейсные ресурсы, но сервер статических ресурсов не был обновлен. Таким образом, вам все равно нужно загружать офлайн-пакеты вручную. Конечно, читатели могут выбрать подходящий метод загрузки в соответствии с реальной ситуацией.

2. В случае нескольких приложений, как определить, какому приложению принадлежит автономный пакет?

При заполнении информации о загруженном офлайн-пакете добавляется поле appName. При запросе JSON-файла списка автономных пакетов добавьте в запрос поле appName, и платформа автономных пакетов вернет только список автономных пакетов, принадлежащий приложению.

3. Обязательно начните во временном приложении Скачать автономный пакет это?

Конечно, вы можете сделать больше, например, когда вы можете выбрать подключение к Wi-Fi или переключиться с фона на стойку регистрации и более 10 минут. Этот элемент настройки можно настроить на платформе офлайн-пакетов, которые можно сделать глобальными или персонализированными настройками для различных офлайн-пакетов.

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

Не беспокойтесь об этом.Если HTTP-запрос в приведенном выше коде не обращается ни к каким внешним ресурсам, запрос будет отправлен на удаленный сервер. Таким образом, даже если локальные ресурсы автономного пакета не обновляются вовремя, можно гарантировать, что статические ресурсы страницы всегда актуальны. То есть есть решение «снизу вверх», если есть большая проблема, вернуться к исходному режиму загрузки запрашивающего сервера.

5. Если версия автономного пакета клиента равна 1, а последняя версия автономного пакета, соответствующая платформе автономного пакета, равна 4, то есть когда разница версий больше 1, необходимо ли также загружать дифференциальную package, а затем выполнить слияние патчей локально?

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

6. Если офлайн-пакет включает в себя офлайн-js, css и другие ресурсы, а также офлайн-html, будут ли проблемы?

Здесь автор приводит пример для простоты объяснения: предполагается, что клиент запрашивает онлайн- и офлайн-версию пакета в момент запуска приложения и раз в два часа. Когда приложение только что запросило версию онлайн- и офлайн-пакета, ресурсы онлайн-интерфейсной страницы обновляются, а онлайн- и офлайн-пакеты также будут обновлены. В это время, когда пользователь обращается к странице, клиент не знает, что онлайн-ресурс был обновлен, поэтому он все равно перехватит запрос html-ресурса и найдет его из локального офлайн-пакета. Так как в имени файла html нет хэша, даже если содержимое обновления страницы изменится, имя файла останется прежним, поэтому вы все равно сможете найти соответствующий html файл из локального офлайн-пакета и вернуть его, хотя этот html файл уже более старый файл по сравнению с онлайновым. js/css и другие ресурсы, на которые ссылается старый html, также будут старыми ресурсами, что приведет к тому, что страница, которую видит пользователь, всегда будет старой. Последняя страница может быть обновлена ​​только после того, как клиент повторно запросит онлайн- и автономную версию пакета после перезапуска приложения или после почти двухчасового ожидания.

Основная проблема в том, что клиент не знает сроков обновления онлайн-ресурсов и может только периодически опрашивать. Если сервер активно информирует клиента, например, с помощью push-уведомлений, когда обновляется онлайн-офлайн-пакет, он уведомляет клиента о необходимости запросить последнюю версию автономного пакета, что может максимально обеспечить своевременное обновление. (Конечно, загрузка автономного пакета займет некоторое время)

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

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

7. Для wkWebview на стороне iOS нет API для прямого перехвата запросов веб-страниц Как реализовать пакетное автономное решение?

Автор спросил об автономном пакетном решении на стороне Xiayun Music для iOS, которое осуществляется через частный API.registerSchemeForCustomProtocolСхема http(s) регистрируется, после чего можно получать все запросы http(s).Для получения дополнительной информации см. следующую статью:

Южный Coder.top/2019/04/11 / ...

В статье упоминается, что поскольку WKWebView выполняет сетевые запросы в процессе NSURLProtocol Network Process независимо от основного процесса, при нормальных обстоятельствах процесс NSURLProtocol не может перехватывать запросы, инициированные веб-страницами в webview. (Примечание: запрос, отправленный UIWebView, NSURLProtocol, может быть перехвачен)

Если схема http(s) зарегистрирована через registerSchemeForCustomProtocol, то все http(s) запросы, инициированные WKWebView, будут передаваться от сетевого процесса Network Process к основному процессу NSURLProtocol через IPC для обработки, и все сетевые запросы могут быть перехвачены.

Но связь между процессами использует MessageQueue, сетевой процесс Network Process кодирует запрос в сообщение, а затем отправляет его основному процессу NSURLProtocol через IPC (межпроцессное взаимодействие). По соображениям производительности поля HTTPBody и HTTPBodyStream отбрасываются при кодировании.

В статье упоминается следующее решение:

image.png

Но есть еще проблема, то есть размер самого http-заголовка будет ограничен, что приведет к сбою таких сценариев, как загрузка картинок. Автор здесь упоминает способ, которым можно пройти:

При инициализации wkWebview внедрите и выполните фрагмент js.Основная логика этого js заключается в переопределении методов открытия и отправки в прототипе XMLHttpRequest, смонтированном на глобальном уровне.

В методе open строковый идентификатор генерируется на основе метки времени, монтируется в объект экземпляра XMLHttpRequest и добавляется ко второму параметру Url, а затем выполняется исходный метод open.

Что касается метода send, то он в основном получает тело http-запроса и атрибут идентификатора, монтируемый в объект экземпляра в методе open, объединяет его в объект и вызывает собственный метод для сохранения в хранилище клиента.

При перехвате XHR-запроса в основном процессе NSURLProtocol идентификатор сначала получается из запрошенного Url, а затем по идентификатору находится ранее сохраненное тело из хранилища клиента. Это решает проблему потери тела.

Конечно, если в проекте используется метод выборки, изначально предоставленный браузером, не забудьте также перезаписать метод выборки.

конец

До сих пор был объяснен общий принцип всего решения.Более подробно читатели могут обратиться по ссылке проекта, указанной в статье.Коды всех терминалов были размещены на моем github.

Это также считается исполнением моего давнего желания: реализовать автономное пакетное решение и полностью открыть его исходный код. Наконец, я надеюсь, что это поможет вам~