A Hybrid SDK Design and Implementation

With the rise of the mobile wave, various apps emerge one after another, and the rapid development of business expansion has increased the team's requirements for development efficiency. At this time, the cost of purely using native development technology will inevitably be higher. The low-cost, high-efficiency, cross-platform and other features of H5 were immediately utilized to form a new development model: Hybrid App

As a hybrid development mode, the bottom layer of Hybrid App relies on the container (Webview) provided by Native, and the upper layer uses various front-end technologies to complete business development (now three-legged Vue, React, Angular), the bottom layer is transparent, and the top layer is diversified. This scenario is very conducive to front-end intervention and is very suitable for rapid business iteration. So Hybrid is on fire.

Everyone understands the big truth, but according to what I know, there are still a lot of people and companies that have not done a good job in Hybrid, so I will summarize my experience, hoping to help developers with technology Selection helps

A current state of Hybrid

Maybe in the early days, it was all web development on the PC side. With the development of the mobile Internet and the popularization of iOS and Android smartphones, many businesses and scenarios have been transferred from the PC side to the mobile side. Front-end developers began to develop web pages for mobile. In this way, the early resources are packaged into the Native App, which will increase the size of the application package. More and more businesses are trying to use H5, which will inevitably require a place to access Native functions. In this way, in the early days, Native developers who know some front-end technology may encapsulate or expose Native capabilities to the JS side. In many cases, the appearance is obviously unrealistic, and a dedicated Hybrid team is required to do this; if the volume is large, rules and regulations are required.

Summarize:

  1. Hybrid development is efficient, cross-platform, and low-cost
  2. From a business perspective, Hybrid has no version problems, and bugs can be fixed in time

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

  • What are the respective jobs of Hybrid, Native, and front-end?
  • How to design the Hybrid interactive interface
  • How to design Hybrid's Header
  • How Hybrid designs the directory structure and how the incremental mechanism is implemented
  • Resource caching strategy, white screen problem...

Native and front-end division of labor

Before doing Hybird architecture design, we need to distinguish the boundaries between Native and front-end. First of all, Native provides the host environment. To make reasonable use of the capabilities provided by Native, to achieve a general Hybrid architecture, and to stand in the front-end vision, I think the following core design issues need to be considered.

Interactive Design

The first question to consider in the design of the Hybrid architecture is how to design the interaction between the front end and Native. If this design is not good, it will have a profound impact on the subsequent development and maintenance of the front-end framework. And this effect is irreversible and irreversible. Therefore, in the early stage, the front-end and Native need to cooperate well and provide a common interface. for example

  1. Native UI components, Header components, message components
  2. Address book, system, device information reading interface
  3. H5 and Native jump to each other. For example, how does H5 jump to a Native page, how does H5 open a new Webview and animate it to jump to another H5 page

Account information design

The 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 open up the account system.

Hybrid development and debugging

Functional design and coding are not really the end. Native and the front end need to discuss a set of models that can be developed and debugged, otherwise many business development work will be difficult to continue.

iOS debugging tips

Android debugging tips:

  • Enable Webview debugging in App (WebView.setWebContentsDebuggingEnabled(true); )
  • In the chrome browser, enter chrome://inspect/#devices to access the list of webviews that can be debugged
  • Environments that need to be overturned

structure

Hybrid Interaction Design

Hybrid interaction is nothing more than the Native calling the JS method of the H5 page, or the H5 page calling the interface provided by the Native through JS. The bridge between the two communication is Webview. The mainstream communication methods in the industry: 1. Bridging objects (timing issues, do not advocate this method); 2. Custom Url scheme

Communication Design

The App itself defines a url scheme, and registers the custom url to the dispatch center. For example, weixin:// can open WeChat.

If you are not sure about the Url scheme, you can read this article

JS to Native

Native will provide some APIs in each version, and a corresponding framework team will encapsulate it at the front end to release business interfaces. Example

SDGHybrid.http.get()  // 向业务服务器拿数据
SDGHybrid.http.post() // 向业务服务器提交数据
SDGHybrid.http.sign() // 计算签名
SDGHybrid.http.getUA()  // 获取UserAgent
SDGHybridReady(function(arg){
  SDGHybrid.http.post({
    url: arg.baseurl + '/feedback',
    params:{
      title: '点菜很慢',
      content: '服务差'
    },
    success: (data) => {
      renderUI(data);
    },
    fail: (err) => {
      console.log(err);
    }
  })
})

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

API interaction

The way to call the Native Api interface is similar to using traditional Ajax to call the server, or the interface provided by the Native network request

HybridApi.jpg

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

Communication example

format convention

The first step in 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(默认值:GET),HTTP请求方法(GET|POST|DELETE|...)
url(默认值:当前url),请求的url地址
data(默认值:'') 请求中的数据如果是字符串则不变,如果为Object,则需要转换为String,含有中文则会encodeURI

So the request model in Hybrid is:

requestHybrid({
  // H5 请求由 Native 完成
  tagname: 'NativeRequest',
  // 请求参数
  param: requestObject,
  // 结果的回调
  callback: function (data) {
    renderUI(data);
  }
});

This method will 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_Method%22%3A0%7D

Native's webview environment can monitor any internal resource requests, and judge that if it is SDGHybrid, it will distribute events, and may carry parameters at the end of the processing.

The format of the data returned is similar to that of the ordinary interface.

{
  errno: 1,
  message: 'App版本过低,请升级App版本',
  data: {}
}

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

Simple version code implementation.

//通用的 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 requestHybrid = 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));
};

//获取版本信息,约定APP的navigator.userAgent版本包含版本信息:scheme/xx.xx.xx
var getHybridInfo = 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, and the framework && bottom layer does not care much about the business implementation of H5, so there are fewer scenarios in which Native calls H5 in real business.

The above network access Native code (iOS as an example)

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{
    //处理请求不全的情况
    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. In real business development, there are some APIs that must be used by application scenarios.

jump

Jump is one of the APIs that Hybrid must use. For the front end, there are the following situations:

  • Jump within the page, not related to Hybrid
  • H5 jump to Native interface
  • H5 newly opened Webview jumps to H5 page. If animation is used in general animation switching page, it is divided into forward and backward according to the business. forward & backword, the regulations are as follows, first of all, H5 jumps to a native page
//H5跳Native页面
//=>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: {
     // 要去到的页面
     topage: 'home',
     // 跳转方式,H5跳Native
     type: 'native',
     // 其它参数
     data2: 2
   }
});

H5 page to go to a native page

//=>SDGHybrid://forward?t=1446297653344&param=%7B%22topage%22%253A%22Goods%252Fdetail%20%20%22%252C%22type%22%253A%22h2n%22%252C%22id%22%253A20151031%7D
requestHybrid({
  tagname: 'forward',
  param: {
    // 要去到的页面
    topage: 'Goods/detail',
    // 跳转方式,H5跳Native
    type: 'native',
    // 其它参数
    id: 20151031
  }
});

H5 new way to open Webview to jump to H5

requestHybrid({
  tagname: 'forward',
  param: {
    // 要去到的页面,首先找到goods频道,然后定位到detail模块
    topage: 'goods/detail  ',
    //跳转方式,H5新开Webview跳转,最后装载H5页面
    type: 'webview',
    //其它参数
    id: 20151031
  }
});

back is the same as forward, there may be an animatetype parameter to determine the animation effect when the page is switched. In actual use, it may wrap the method globally to ignore the tagname details.

Header component design

Every change in Native is "slow", so something like Header is very necessary.

  1. Mainstream containers do this, such as WeChat, mobile Baidu, and Ctrip.
  2. Without Header Once there is a network error or a white screen, the App will fall into a suspended state

PS: Native opens H5. If there is no response in 300ms, you need to load components to avoid white screen because H5 App itself has Header components. From the front-end framework layer, you need to ensure that the business code is consistent, and all differences need to be in the framework layer. To be transparent, in short, the design of the Header needs to follow:

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

Generally speaking, the Header component needs to complete the following functions:

  1. The left and right sides of the Header can be configured and displayed as text or icons (Here, the Header is required to implement mainstream icons, and the icons can also be controlled by the business), and its click callback needs to be controlled

  2. The title of Header can be set to single title or main title, subtitle type, and can configure lefticon and righticon (icon is centered)

  3. Meet some special configurations, such as label class Header

Therefore, from the front-end business side, the usage of Header is as follows (tagname is not allowed to be repeated):

 //Native以及前端框架会对特殊tagname的标识做默认回调,如果未注册callback,或者点击回调callback无返回则执行默认方法
 // back前端默认执行History.back,如果不可后退则回到指定URL,Native如果检测到不可后退则返回Naive大首页
 // home前端默认返回指定URL,Native默认返回大首页
  this.header.set({
      left: [
        {
          //如果出现value字段,则默认不使用icon
          tagname: 'back',
          value: '回退',
          //如果设置了lefticon或者righticon,则显示icon
          //native会提供常用图标icon映射,如果找不到,便会去当前业务频道专用目录获取图标
          lefticon: 'back',
          callback: function () { }
        }
     ],
     right: [
      {
        //默认icon为tagname,这里为icon
        tagname: 'search',
        callback: function () { }
      },
      //自定义图标
      {
        tagname: 'me',
        //会去hotel频道存储静态header图标资源目录搜寻该图标,没有便使用默认图标
        icon: 'hotel/me.png',
        callback: function () { }
      }
    ],
    title: 'title',
        //显示主标题,子标题的场景
    title: ['title', 'subtitle'], 
    //定制化title
    title: {
      value: 'title',
      //标题右边图标
      righticon: 'down', //也可以设置lefticon
      //标题类型,默认为空,设置的话需要特殊处理
      //type: 'tabs',
      //点击标题时的回调,默认为空
      callback: function () { }
    }
});

Because there is generally only one button on the left side of the Header, its object can use this form:

this.header.set({
  back: function () { },
    title: ''
});
//语法糖=>
this.header.set({
    left: [{
        tagname: 'back',
        callback: function(){}
    }],
  title: '',
});

In order to complete the implementation of the Native side, two new interfaces will be added here, registering events with Native, and unregistering 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;
            }

            //右边按钮必须保持数据一致性
            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
                }
            });

        },

        //注册事件,将事件存于本地
        registerEvents: function () {
            _.unRegisterHybridCallback(this.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);
                }
            }
        },

        //显示header
        show: function () {
            _.requestHybrid({
                tagname: 'showheader'
            });
        },

        //隐藏header
        hide: function () {
            _.requestHybrid({
                tagname: 'hideheader',
                param: {
                    animate: true
                }
            });
        },

        //只更新title,不重置事件,不对header其它地方造成变化,仅仅最简单的header能如此操作
        update: function (title) {
            _.requestHybrid({
                tagname: 'updateheadertitle',
                param: {
                    title: 'aaaaa'
                }
            });
        },

        initialize: function () {
            this.propertys();
        }
    });

});

request class

Although get class requests can use jsonp to bypass cross-domain issues, post requests are a stumbling block. For security reasons, the server will set cors to only target a few domain names. Hybrid's embedded static resources may be read by means of local files, 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-packet capture, etc.), the network request of H5 is completed by Native. Maybe some people say that H5's network request is safe to let Native go? I can continue crawling your Dom nodes. This is one of the means against anti-reptiles. If you want to know more anti-crawling strategies, you can read my article Web Anti-crawling Solutions

Web network requests are completed by Native

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

HybridGet = function (url, param, callback) {

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

};

In real business scenarios, it will be encapsulated into the data request module, adapted at the bottom layer, using ajax requests under the H5 site, and using the proxy to send out when the Native is embedded. The agreement with Native is

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

Common NativeUI Components

In general, Native usually provides common UI, such as loading layer loading, message box toast

var HybridUI = {};
HybridUI.showLoading();
//=>
requestHybrid({
    tagname: 'showLoading'
});

HybridUI.showToast({
    title: '111',
    //几秒后自动关闭提示框,-1需要点击才会关闭
    hidesec: 3,
    //弹出层关闭时的回调
    callback: function () { }
});
//=>
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 UIs are generally used.

Account system design

For the webpage running in Webview, whether the account is logged in or not is determined by whether it carries the key cookie (the validity of the key cannot be guaranteed). Because Native does not pay attention to business implementation, each load may be the result of a successful login and jump back, so each load needs to pay attention to the change of the key cookie to achieve the consistency of the login state data.

  • Use the Native proxy as the request interface. If there is no login, the Native layer will evoke the login page
  • The direct connection method uses the ajax request interface. If you are not logged in, the login page will be evoked at the bottom (H5)
/*
	无论成功与否皆会关闭登录框
	参数包括:
	success 登录成功的回调
 	error 登录失败的回调
	url 如果没有设置success,或者success执行后没有返回true,则默认跳往此url
*/
HybridUI.Login = function (opts) {
    //...
};
//=>
requestHybrid({
    tagname: 'login',
    param: {
       success: function () { },
       error: function () { },
       url: '...'
	}
});
//与登录接口一致,参数一致
HybridUI.logout = function () {
	//...
};

When designing the Hybrid layer, the interface should be willing to obtain the user account information stored on the Native side through the interface for the code in the Hybrid environment; for the traditional web page environment, the online account information can be obtained through the interface, and then the non- Store sensitive information in LocalStorage, and then read data from LocalStorage into memory every page load (such as Vuex in Vue.js framework, Redux in React.js)

Hybrid Resource Management

Hybrid's resources need 增量更新to be easily split, so a Hybrid resource structure looks like the following

Hybrid Resource Structure

Suppose there are 2 business lines: mall, shopping cart

WebApp
│- Mall
│- Cart
│  index.html //业务入口html资源,如果不是单页应用会有多个入口
│  │  main.js //业务所有js资源打包
│  │
│  └─static //静态样式资源
│      ├─css 
│      ├─hybrid //存储业务定制化类Native Header图标
│      └─images
├─libs
│      libs.js //框架所有js资源打包
└─static
   ├─css
   └─images

Incremental update

After each business development, it needs to be deployed 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 starts, it will request an interface from the server, and the interface will return a json string containing the version number and md5 information of each H5 business line contained in the app.

After getting the json, compare it with the version information saved locally in the app. If it finds that it has changed, it will request the corresponding interface, and the interface will return the file corresponding to md5. After Native gets it, decompress and replace it.

After all the replacements are completed, save and replace the resource version number information requested by this interface to the native local.

Because each resource has a version number, if there is a problem with a certain version online, you can roll back to the stable version according to the corresponding stable version number.

some piecemeal solutions

  1. Static straight out

The concept of "straight out" is no stranger to front-end students. In order to optimize the first screen experience, most mainstream pages will be rendered by NodeJs after pulling the first screen data on the server side, and then generate an Html file containing the first screen data, so that when the first screen is displayed, the content can be solved. The problem of turning chrysanthemums. Of course, this way of "straight out" of the page will also bring a problem. The server needs to pull the data of the first screen, which means that the processing time on the server side increases. However, because Html is now published on CDN, and WebView is obtained directly from CDN, this time-consuming does not affect users. There is an automated build system Vnues in the mobile QQ. When the product manager modifies the data and publishes it, he can start the build task with one click. The Vnues system will automatically synchronize the latest code and data, and then generate a new Html with the first screen and publish it to Go to the CDN.

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

  1. Offline pre-push

After the page is published to the CDN, WebView needs to initiate a network request to pull it. When the user is in a weak network or an environment with poor network speed, this loading time will be very long. So we pull the resources of the page to the local in advance through the method of offline pre-push, when the user loads the resources, it is equivalent to loading from the local, even if there is no network, the first screen page can be displayed. This is the offline package that everyone is familiar with. Mobile QQ uses 7Z to generate offline packages, and the offline package server performs BsDiff binary difference between the new offline package and the historical offline package corresponding to the business to generate incremental packages, which further reduces the bandwidth cost when downloading offline packages and the traffic consumed by downloading. Reduced from a full offline package (253KB) to an incremental package (3KB).

https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488218&idx=1&sn=21afe07eb642162111ee210e4a040db2&chksm=f951a799ce262e8f6c1f5bb85e84c2db49ae4ca0acb6df40d9c172fc0baaba58937cf9f0afe4&scene=27#wechat_redirect

  1. Intercept loading

In fact, in the highly customized wap page scenario, we have tight control over the types of pages that may appear in the webview. You can control the content to avoid external page jumps in the wap page. You can also use the corresponding proxy method of the webview to disable the types of jumps we don't want to appear, or use both at the same time. Our customized content will appear. Since the types of wap pages are limited, it is natural to think that most of the same type of pages are generated by the front-end template, and the html, css, and js resources used by the page are likely to be the same copy, or a limited number of copies. It also becomes feasible to package the client locally. When loading the corresponding url, directly load the local resources. For network requests in webview, it can actually be taken over by the client. For example, in the Hybrid framework you use, register an interface for the front end to initiate 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. It can be sent by the client in the Hybrid method, and the corresponding interception can be realized. Based on the above scheme, the complete display process of our wap page is as follows: the client loads a certain url in the webview, judges that it conforms to the rules, and loads the local template html. The internal implementation of the page is through the network request interface provided by the client , initiate a network request to obtain the content of the specific page, and obtain the filled data to complete the display.

NSURLProtocol allows you to redefine the behavior of Apple's URL Loading System, which has many classes for handling URL requests, such as NSURL, NSURLRequest, NSURLConnection, and NSURLSession. When the URL Loading System uses NSURLRequest to fetch a resource, it creates an instance of a subclass of NSURLProtocol. You should not instantiate an NSURLProtocol directly. NSURLProtocol looks like a protocol, but it is a class, and you must use the A subclass of a class and needs to be registered.                                       

  1. WKWebView network request interception method 1 (Native side): The native WKWebView executes network requests 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 requests directly using NSURLProtocol on WKWebView.

However, since the offline package mechanism of mPaas strongly relies on network interception, based on this, mPaaS uses the hidden api of WKWebview to register and intercept network requests to meet the business scenario requirements of offline packages. The reference code is as follows:

[WKBrowsingContextController registerSchemeForCustomProtocol:@"https"]

However, for performance reasons, WKWebView's network request will remove the body of the request when passing data to the main process, resulting in the loss of the body parameter of the request after interception.

In the offline package scenario, since the page resources do not need body data, the offline package can be used normally without being affected. But other post requests inside the H5 page lose the data parameter.

In order to solve the problem of missing post parameters, mPaas solves the problem by injecting code in js and hooking the XMLHTTPRequest object in the js context.

By assembling the content of the method in the JS layer, and then passing the content to the main process through the messageHandler mechanism of WKWebView, saving the corresponding HTTPBody, and then notifying the JS side to continue the request, after the network request is sent to the main process, the post request corresponds to The HttpBody is added to complete the processing of a post request. The overall process can be referred to as follows: ajax-sequence diagramThrough the above mechanism, it not only meets the resource interception requirements of offline packages, but also solves the problem of post request body loss. However, there are still some problems in some scenarios, which need to be adapted by developers.

Method 2 (JS side): Proxy the information requested by the network to the client locally through the hook method of the AJAX request. If you can get the post request information in WKWebView, the rest is not a problem. The implementation of AJAX hook can be seen in this Repo .

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324214642&siteId=291194637