With the rise of the mobile wave, various APPs emerge in an endless stream. Rapid business expansion raises the team’s requirements on development efficiency. At this time, the cost of purely using Native development technology will inevitably be a little higher. The low cost, high efficiency and cross-platform features of H5 were immediately taken advantage of to form a new development model: Hybrid App

As a Hybrid development mode, Hybrid App relies on the container provided by Native (WebView) in the bottom layer, uses various front-end technologies in the upper layer to complete the business development (now the three-party Vue, React and Angular), and is transparent in the bottom layer and diversified in the upper layer. This scenario is very conducive to front-end involvement and is very suitable for rapid iterations of the business. Hybrid became popular.

Everyone knows the general principle, but according to what I know, there are still a lot of people and companies who are not doing well in Hybrid, so I will make a summary of my experience, hoping to help the majority of developers in technology selection

Hybrid is a current situation

Perhaps the early stage is PC web development, with the development of mobile Internet, IOS, Android smartphone popularity, a lot of business and scene have been transferred from PC to mobile terminal. Front-end developers are starting to develop web pages for mobile. In this way, the packaging of early resources into Native APP will increase the size of application package. More and more businesses begin to try with H5, which inevitably requires a place to access Native functions. In this way, it may be that Native developers who know some front-end technologies encapsulate or expose Native capabilities to the JS terminal in the early stage. When there are more businesses, the appearance is obviously unrealistic. You need a dedicated Hybrid team to do this; If the quantity is large, it needs rules and norms.

Conclusion:

  1. Hybrid is developed efficiently, cross-platform and at low cost
  2. Hybrid has no version problem in terms of business, there are bugs that can be fixed in time

Hybrid needs a certain specification when it is used in a large number of applications, so this article will discuss the design knowledge of a Hybrid.

  • What is the work of Hybrid, Native and Front End respectively
  • How is the Hybrid interface designed
  • How to design the Hybrid Header
  • How to design the directory structure of Hybrid and how to implement the increment mechanism
  • Resource caching policy, white screen problem…

The division of labor between Native and front end

Before doing Hybird architecture design, we need to distinguish the boundary between Native and front end. First of all, Native provides the host environment. In order to make reasonable use of the capabilities provided by Native and realize the general Hybrid architecture, I think the following core design issues need to be considered in order to stand on the vision of the big front end.

Interaction design

The first issue to be considered in Hybrid architecture design is how to design the interaction between the front end and Native. If the design is not good, it will have a profound impact on the subsequent development and maintenance of the front end framework. And the effects are irreversible. Therefore, in the early stage, the front end needs to cooperate well with the Native and provide a common interface. Such as

  1. Native UI component, Header component, Message class component
  2. Contact list, system and device information reading interface
  3. H5 and Native mutual jump. For example, how does H5 jump to a Native page? How does H5 open a new WebView and do animation to jump to another H5 page

Account information design

Account system is important and unavoidable. Native needs to design a good and secure authentication mechanism to ensure that it is transparent enough for business developers to get through the account system

Hybrid development and debugging

The functional design and coding are not really over. Native and front end need to discuss a set of developable and debugging models, otherwise many business development work is difficult to continue.

IOS Debugging Tips

Android debugging tips:

  • Open the App Webview debugging (Webview. SetWebContentsDebuggingEnabled (true); )
  • Type chrome://inspect/#devices to access the list of WebViews that can be debugged
  • You need a over the wall environment

Hybrid Interaction Design

Hybrid interaction is nothing more than Native calling JS method on H5 page, or interface provided by H5 page calling Native through JS. The bridge of communication is the WebView. Mainstream communication methods in the industry: 1. Bridging objects (timing problem, not advocating this approach); 2. 2. Custom URL scheme

The APP defines the URL scheme itself, and registers the customized URL to the scheduling center, such as weixin:// You can open WeChat.

Take a look at this article if you are not clear about the URL scheme

JS to Native

Native provides some API in each version, and a corresponding framework team wraps it up at the front end to release the business interface. For example,

SdgHyBrid.http.get () // Take the data to the business server sdgHyBrid.http.post () // Submit the data to the business server sdgHyBrid.http.sign () // Calculate the signature SdGHyBrid.http.getUA () // Get the UserAgent
SdghyBridReady (function(arg){sdghyBrid.http. post({url: arg. BaseURL + '/feedback', params:{title: 'slow to order ', content: 'Service bad'}, success: (data) = bb0 {renderUI(data); }, fail: (err) => { console.log(err); }})})

The front-end framework defines a global variable SDGHyBrid as the bridge between Native and front-end, and the front-end can access Native through this object

Api interaction

Native API interfaces are called in a manner similar to traditional Ajax calls to the server or interfaces provided by Native network requests

So what we need to encapsulate is to simulate the creation of a Native request similar to the Ajax model.

Format contract

The first step in the interaction is to design the data format. This is divided into request data format and response data format, refer to the Ajax model:

$.ajax({ type: "GET", url: "test.json", data: {username:$("#username").val(), content:$("#content").val()}, dataType: "json", success: function(data){ renderUI(data); }});
$. Ajax (options) = > XMLHTTPRequest type (default: GET), the HTTP request method (GET | POST | DELETE |...). URL (default value: current URL), request URL address data(default value: "") if the data in the request is a String is unchanged, if it is Object, it needs to be converted to String, contains Chinese will encodeURI

So the request model in Hybrid is:

RequesThyBrid ({// H5 request completed by Native tagName: 'nativeRequest ', // request parameter param: requestObject, // result callback: function (data) { renderUI(data); }});

This method can form a URL, such as: SDGHybrid: / / NativeRequest? t=1545840397616&callback=Hybrid_1545840397616&param=%7B%22url%22%3A%22https%3A%2F%2Fwww.datacubr.com%2FApi%2FSearchInfo% 2FgetLawsInfo%22%2C%22params%22%3A%7B%22key%22%3A%22%22%2C%22page%22%3A1%2C%22encryption%22%3A1%7D%2C%22Hybrid_Request_M ethod%22%3A0%7D

Native WebView environment can monitor any internal resource request, and determine if it is SDGHyBrid, then distribute the event, and may carry parameters at the end of processing. The parameter needs to start with the URLDecode and then fetch the resulting data through the WebView to the callback (hybrid_timestamp) in the window object.

The data is returned in a format similar to a normal interface return format

{errno: 1, message: 'App version is too low, please update App version ', data: {}}

Note that the real data is in the data node. If errno is not 0, you need to prompt message.

Simple version code implementation.

/ / general Hybrid call Native window. SDGbrHybrid = window. SDGbrHybrid | | {}; var loadURL = function (url) { var iframe = document.createElement('iframe'); iframe.style.display = "none"; iframe.style.width = '1px'; iframe.style.height = '1px'; iframe.src = url; document.body.appendChild(iframe); setTimeout(function () { iframe.remove(); }, 100); }; var _getHybridUrl = function (params) { var paramStr = '', url = 'SDGHybrid://'; url += params.tagname + "? t=" + new Date().getTime(); if (params.callback) { url += "&callback=" + params.callback; delete params.callback; } if (params.param) { paramStr = typeof params.param == "object" ? JSON.stringify(params.param) : params.param; url += "&param=" + encodeURIComponent(paramStr); } return url; }; Var requestThyBrid = function (params) {var tt = (new Date().getTime()); var t = "Hybrid_" + tt; var tmpFn; if (params.callback) { tmpFn = params.callback; params.callback = t; window.SDGHybrid[t] = function (data) { tmpFn(data); delete window.SDGHybrid[t]; } } loadURL(_getHybridUrl(params)); }; Scheme/XX. XX. XX var getThyBridInfo = function () {var platform_version = {}; var na = navigator.userAgent; var info = na.match(/scheme\/\d\.\d\.\d/); if (info && info[0]) { info = info[0].split('/'); if (info && info.length == 2) { platform_version.platform = info[0]; platform_version.version = info[1]; } } return platform_version; };

Native has a WebView container for H5. The underlying framework && does not care much about the business implementation of H5, so Native calls H5 scenes less in real business.

The above network access Native code (for example, IOS)

typedef NS_ENUM(NSInteger){ Hybrid_Request_Method_Post = 0, Hybrid_Request_Method_Get = 1 } Hybrid_Request_Method; @interface RequestModel : NSObject @property (nonatomic, strong) NSString *url; @property (nonatomic, assign) Hybrid_Request_Method Hybrid_Request_Method; @property (nonatomic, strong) NSDictionary *params; @end @interface HybridRequest : NSObject + (void)requestWithNative:(RequestModel *)requestModel hybridRequestSuccess:(void (^)(id responseObject))success hybridRequestfail:(void (^)(void))fail; + (void)requestWithNative:(RequestModel *)requestModel hybridRequestSuccess:(void (^)(id responseObject))success HybridRequestfail (void (^) (void)) fail {/ / handle the situation without all requests NSAssert (requestModel | | success | | fail, @ "Something goes wrong"); NSString *url = requestModel.url; NSDictionary *params = requestModel.params; if (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get) { [AFNetPackage getJSONWithUrl:url parameters:params success:^(id responseObject) { success(responseObject); } fail:^{ fail(); }]; } else if (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) { [AFNetPackage postJSONWithUrl:url parameters:params success:^(id responseObject) { success(responseObject); } fail:^{ fail(); }]; }}

Common interactive APIs

Good interaction design is the first step, and in real business development there are some APIs that must be used by application scenarios.

jump

Jump is one of Hybrid’s must-use APIs. For the front end, there are the following:

  • Intra-page jump, nothing to do with Hybrid
  • H5 Jump to the Native interface
  • H5 new WebView jump to H5 page, general animation switch page if the use of animation, according to the business is divided into forward, backward. Forward & Backword, stipulates as follows, first is H5 jump Native a certain page
//H5 jump Native page //=>SDGHybrid://forward? t=1446297487682&param=%7B%22topage%22%3A%22home%22%2C%22type%22%3A%22h2n%22%2C%22data2%22%3A2%7D requestHybrid({ TagName: 'forward', param: {// Pages to go to topage: 'home', // Pages to go to Native type: 'Native ', // Pages to go to Native type:' Native ', // Other parameters data2: 2}});

The H5 page needs to go to a page called Native

//=>SDGHybrid://forward? t=1446297653344&param=%7B%22topage%22%253A%22Goods%252Fdetail%20%20%22%252C%22type%22%253A%22h2n%22%252C%22id%22%253A201 7D requestHybrid({tagname: 'forward', param: {// page to go to topage: 'Goods/detail', // H5 Hip Native type: 'native', // other parameter id: 20151031}});

H5 New WebView way to jump to H5

RequesThyBrid ({tagname: 'forward', param: {// To go to the page, first find the goods channel, then navigate to the detail module topage: 'goods/detail ', // load H5 page type: 'Webview ', // other parameter id: 20151031});

Back is the same as forward, and there may be an animateType parameter that determines the animation when the page is switched. In real usage, the global encapsulation method may be used to ignore the tagname details.

Header component design

Native changes are “slow” each time, so things like headers are needed.

  1. Mainstream containers are doing this, such as WeChat, mobile Baidu, Ctrip
  2. If you have a network error or a white screen, your App will go into suspended animation

PS: When H5 is opened, if there is no response at 300ms, the Loading component is required to avoid the white screen, because the H5 App itself has Header component. From the perspective of the front-end framework layer, it is necessary to ensure that the business code is consistent, and all the differences need to be made transparent in the framework layer. In brief, the Header design needs to follow:

  • The H5 Header component uses the call layer interface the same as the Header component provided by Native
  • The front-end framework layer chooses whether to use the H5 Header component or the Native Header component according to the judgment of the environment

In general, the Header component needs to do the following:

  1. The left and right sides of the Header can be configured to display text or ICONS (the Header is required to implement the mainstream icon, and the icon can also be controlled by the business), and its click callback needs to be controlled
  2. The title of the Header can be set to a single title or a main title or a subtitle type, and the lefticon and righticon can be configured (icon centered).
  3. Satisfy some special configurations, such as the tag class Header

Therefore, from the front end business side, the Header can be used as (where tagname is not allowed to be repeated) :

// The default callback method will be executed if a callback is not registered, or if a callback is not returned, the default method will be executed. If the callback is not returned, the default method will be executed. This. Header. set({left: [{// If the value field is present, the default is not to use icon tagname: 'back', value: Lefticon: 'back', callback: 'back', callback: 'back', callback: 'back', callback: The function () {}}], right: [{/ / the default icon for the tagname, here for icon tagname: 'search', the callback: Function () {}}, // custom icon {tagname: 'me', // will go to the hotel channel to store the static header icon resource directory to search for the icon, if not, use the default icon: 'hotel/me.png', callback: Function () {}}], title: 'title', // function () {}}], title: 'title', // function () {}}], title: 'title', // function () {}}], title: 'title', // function () {}}], title: 'title', // 'title', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon ',' righticon ', 'righticon'. function () { } } });

Since the left side of the Header usually has only one button, its objects can use this form:

this.header.set({ back: function () { }, title: '' }); / / syntactic sugar = > this. Header. The set ({left: [{tagname: 'back', the callback: the function () {}}], the title: "',});

In order to complete the implementation of the Native side, two new interfaces will be added here to register events with the Native side and cancel events:

var registerHybridCallback = function (ns, name, callback) { if(! window.Hybrid[ns]) window.Hybrid[ns] = {}; window.Hybrid[ns][name] = callback; }; var unRegisterHybridCallback = function (ns) { if(! window.Hybrid[ns]) return; delete window.Hybrid[ns]; };

Native Header component implementation:

define([], function () { 'use strict'; return _.inherit({ propertys: function () { this.left = []; this.right = []; this.title = {}; this.view = null; this.hybridEventFlag = 'Header_Event'; }, set: function (opts) {if (! opts) return; var left = []; var right = []; var title = {}; var tmp = {}; If (opts.back) {TMP = {tagName: 'back'}; if (typeof opts.back == 'string') tmp.value = opts.back; else if (typeof opts.back == 'function') tmp.callback = opts.back; else if (typeof opts.back == 'object') _.extend(tmp, opts.back); left.push(tmp); } else { if (opts.left) left = opts.left; } // The right button must be data consistent if (typeof opts.right == 'object' && opts.right. Length) right = opts.right if (typeof opts.title == 'string') { title.title = opts.title; } else if (_.isArray(opts.title) && opts.title.length > 1) { title.title = opts.title[0]; title.subtitle = opts.title[1]; } else if (typeof opts.title == 'object') { _.extend(title, opts.title); } this.left = left; this.right = right; this.title = title; this.view = opts.view; this.registerEvents(); _.requestHybrid({ tagname: 'updateheader', param: { left: this.left, right: this.right, title: this.title } }); } / / register event, the event in local registerEvents: function () {_. UnRegisterHybridCallback (enclosing hybridEventFlag); this._addEvent(this.left); this._addEvent(this.right); this._addEvent(this.title); }, _addEvent: function (data) { if (! _.isArray(data)) data = [data]; var i, len, tmp, fn, tagname; var t = 'header_' + (new Date().getTime()); for (i = 0, len = data.length; i < len; i++) { tmp = data[i]; tagname = tmp.tagname || ''; if (tmp.callback) { fn = $.proxy(tmp.callback, this.view); tmp.callback = t; _.registerHeaderCallback(this.hybridEventFlag, t + '_' + tagname, fn); }}, // display header show: function () {_.requestThyBrid ({tagName: 'Showheader'}); }, // Hide header hide: function () {_. RequestThyBrid ({tagName: 'hideHeader ', param: {animate: true}}); }, update: function (title) {_.requestThyBrid ({tagName: function (title) {_.requestThyBrid ();}, update: function (title) {_.requestThyBrid (); 'updateheadertitle', param: { title: 'aaaaa' } }); }, initialize: function () { this.propertys(); }}); });

Request class

While a GET request can circumvent the cross-domain problem with JSONP, a POST request is a roadblock. For security reasons, the server will set CORS to only a few domain names. Hybrid embedded static resources may be read via a local file, so CORS will not work. Another problem is to prevent crawlers from obtaining data. Since Native has made security Settings for the network (authentication, anti-capture package, etc.), the network request of H5 is completed by Native. Some people may say that the H5 network request is safe to let the Native go? I can keep crawling your DOM nodes. This is for anti-crawler method one. For more anti-crawler strategies, check out my article on Web Anti-crawler Solutions

This usage scenario is consistent with the Header component. The front-end framework layer must be transparent to the business, and the business does not have to care about whether the network request is sent by Native or browser.

HybridGet = function (url, param, callback) {

};
HybridPost = function (url, param, callback) {

};

In the real business scenario, it will be encapsulated into the data request module and adapted at the bottom level. AJAX request is used under the H5 site and sent by proxy when Native is embedded. The agreement with Native is as follows

requestHybrid({ tagname: 'NativeRequest', param: { url: arg.Api + "SearchInfo/getLawsInfo", params: requestparams, Hybrid_Request_Method: 0, encryption: 1 }, callback: function (data) { renderUI(data); }});

Commonly used nativeUI components

In general, Native will provide common UI, such as loading layer, Toast message box

var HybridUI = {}; HybridUI.showLoading(); //=> requestHybrid({ tagname: 'showLoading' }); HybridUI. ShowToast ({title: '111', hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6, hidesec: 6); //=> requestHybrid({ tagname: 'showToast', param: { title: '111', hidesec: 3, callback: function () { } } });

Native UI and front-end UI are not easy to get through, so in the real business development process, only a few key Native UI will be used.

Account system design

WebView running web page, the account login is determined by whether the key cookie (can not guarantee the validity of the key). Because Native does not pay attention to the business implementation, each load may be the result of successful login jump back, so each load needs to pay attention to the change of key cookie, in order to achieve the consistency of login data.

  • Use Native agent as the request interface. If there is no login, the Native layer will evoke the login page
  • Direct connection uses the Ajax request interface, evoking the underlying login page if no login occurs (H5)
/* The logon box will be closed whether it succeeds or fails. Error: If success is not set, or success does not return true after execution, the callback URL for success is not set. Url */ hybridui.login = function (opts) {// hybridui.login = function (opts) {//... }; //=> requestHybrid({ tagname: 'login', param: { success: function () { }, error: function () { }, url: '... '}}); }}}}}}}}}}}}}}}}}}}}}}}}}}}} };

When designing the Hybrid layer, the interface should be willing to obtain the user account information stored at the Native end through the interface for the code in the Hybrid environment; For the traditional web environment, online account information can be obtained through the interface, and then the non-sensitive information is stored in localStorage. Then every time the page is loaded, the data is read from localStorage to the memory (such as Vue.js in the framework of VueX, Redux in Reaction.js)

Hybrid Resource Management

Hybrid resources need to be updated incrementally and split easily, so a Hybrid resource structure looks something like the following

Suppose there are two lines of business: shopping mall and shopping cart

WebApp │ -mall │ -cart │ index.html // WebApp │ -mall │ -cart │ index.html // WebApp │ -mall │ -cart │ index.html // │ ├─ ├─ static // │ ├─ CSS │ ├─hybrid // Save business custom class Native Header ICONS │ ├─ images ├─ Exercises ─ Exercises ─ Exercises ─ Exercises ─ Exercises

Incremental updating

After each business development is completed, it needs to be deployed and put online on the packaging and distribution platform, and then a version number will be generated.

Channel Version md5
Mall 1.0.1 12233000ww
Cart 1.1.2 28211122wt2

When the Native App is started, it will request an interface from the server, and the interface will return a JSON string, the content of which is the version number and MD5 information of each H5 business line contained in the App.

After you get the JSON, you can compare it with the version information saved locally by the APP. If you find any changes, you can request the corresponding interface, and the interface will return the file corresponding to MD5. After the Native is obtained, decompress and replace it.

After all the replacement is completed, the resource version number information requested by this interface will be saved and replaced to Native local.

Because each resource has a version number, if there is a problem with a version on the line, you can roll back to the stable version based on the stable version.

Some piecemeal solutions

  1. Static straight out

The concept of “straight out” is not unfamiliar to front-end students. In order to optimize the first-screen experience, most mainstream pages are rendered by NodeJS after the first-screen data is pulled from the server side, and then an HTML file containing the first-screen data is generated. In this way, the problem of content transformation can be solved when the first screen is displayed. Of course, this kind of page “straight out” method also brings a problem, the server needs to pull the first screen data, which means the server processing time increases. However, since the HTML is now published to the CDN and the WebView is retrieved directly from the CDN, this time is not affected by the user. There is an automated build system VNUES in HandQ. When the product manager modifies the data release, the build task can be started with one key. The VNUES system will automatically synchronize the latest code and data, and then generate new HTML with the first screen and publish it to the CDN.

We can do a similar thing, automatically synchronize the latest code and data, and then generate new HTML with the first screen and publish it to the CDN

  1. Offline pushing

After the page is published to the CDN, the WebView needs to make a network request to pull it. When the user is on a weak network or poor network speed environment, this load time can be very long. So we pull the resources of the page to the local area in advance through offline pre-push. When the user loads the resources, it is equivalent to loading from the local area. Even if there is no network, the first page can also be displayed. This is also known as an offline package. Hand Q generated offline packets using 7 z, off-line package server will be the new package with the business at the same time the corresponding historical offline package BsDiff binary difference, generate incremental package, further reduce the bandwidth of the download the offline package cost, download the consumed flow from a complete offline package (253 KB) reduced to a delta packages (3 KB).

https://mp.weixin.qq.com/s?__…

  1. Intercept load

In fact, in the case of highly customized WAP pages, we have strict control over the types of pages that may appear in the WebView. We can avoid the jump of external pages in the WAP page through the control of content, or we can disable the jump type we don’t want through the corresponding proxy method of WebView, or we can use the dual protection to ensure that only the customized content will appear in the current WebView container. Since the type of WAP page is limited, it is natural to think that most pages of the same type are generated by the front end using the template, and the HTML, CSS, and JS resources used in the page are likely to be the same, or a limited number of copies, so it becomes feasible to package them locally with the client directly. When loading the corresponding URL, directly load the local resource. For network requests in a WebView, you can also let the client take over. For example, in your Hybrid framework, you can register an interface for the front end to make network requests. All network requests in the WAP page are sent through this interface. In this way, the client can do a lot of things. For example, NSURLPROTOCOL cannot intercept the network request initiated by WKWebView, and Hybrid method is adopted to send it to the client, so the corresponding intercept can be achieved. Based on the above scenario, the complete presentation process for our WAP page looks like this: The client loads a URL in the WebView, judges that it complies with the rules, and loads the local template HTML. The internal implementation of the page is through the network request interface provided by the client, initiates the network request to obtain the specific page content, and obtains the filled data to complete the display.

NSURLProtocol allows you to redefine the behavior of Apple’s URL Loading System, which has a number of classes for handling URL requests, such as NSURL, NSURLRequest, etc. NSURLConnection and NSURLSession. When the URL Loading System uses NSURLRequest to fetch resources, it creates an instance of a subclass of NSURLPROTOCOL. You should not instantiate NSURLPROTOCOL directly. NSURLPROTOCOL looks like a protocol. But it’s actually a class, and it has to use subclasses of that class, and it has to be registered.

  1. WKWebView network request interception method one (Native side) : The native WKWebView executes the network request in a process independent of the APP process, and the request data does not pass through the main process, so it is impossible to intercept the request directly by using NSURLProtocol on WKWebView.

However, since the offline packet mechanism of MPAAS strongly relies on network interception, based on this, MPAAS uses the hidden API of WKWebView to register interception network requests to meet the business scenario requirements of offline packet. The reference code is as follows:

[WKBrowsingContextController registerSchemeForCustomProtocol:@"https"]

However, for performance reasons, the body of the WKWebView network request will be removed when passing data to the main process, resulting in the loss of the body parameter of the request after intercepting.

In the offline package scenario, since the page’s resources do not require the body data, the offline package can be used normally without being affected. However, other POST requests within the H5 page will lose the data parameter.

To solve the problem of missing POST parameters, MPAAS works by injecting code into JS and hooking the XMLHttpRequest object in JS context.

After the method content is assembled in the JS layer, and then the content is sent to the main process through the MessageHandler mechanism of WKWebView, the corresponding HttpBody is saved, and then the JS side is notified to continue the request. After the network request reaches the main process, the JS side is notified to continue the request. A POST request is processed by adding the corresponding HttpBody to the POST request. The overall process can be referred to as follows:



Through the above mechanism, it not only satisfies the resource interception appeal of the offline packet, but also solves the problem of missing the body of the POST request. However, there are still some problems in some scenarios that need to be adapted.

Method two (JS side) : proxy the information requested by the network to the local client through the hook method of Ajax request. So if you get the POST request information in the WKWebView, the rest of it is not a problem. The implementation of an Ajax hook can be seen in this Repo.