14
头图

With the rise of the mobile wave, various apps emerge in an endless stream, 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. H5's low-cost, high-efficiency, and cross-platform features were immediately used to form a new development model: Hybrid App

As a hybrid development model, 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 the three pillars of Vue, React, Angular), the bottom layer is transparent, and the upper layer is diversified. This scenario is very conducive to front-end intervention and is very suitable for rapid business iteration. So Hybrid became popular.

Everyone knows the principle, but according to what I know, there are still many people and companies that have not done well in Hybrid, so I will summarize my experience and hope that it can help the majority of developers. Selection is helpful

A status quo of Hybrid

Perhaps the early days were PC-side web page development. With the development of the mobile Internet and the popularization of iOS and Android smart phones, many services and scenarios have shifted from the PC-side to the mobile-side. There are beginning front-end developers to develop web pages for the mobile terminal. In this way, packaging of early resources into Native App will increase the size of the application package. More and more businesses are beginning to use H5 to try. This will inevitably require a place to access Native functions. In this way, it may be that Native developers who understand some front-end technology themselves encapsulate or expose Native capabilities to the JS side, and wait for business comparisons. Many times the appearance is obviously unrealistic, and a dedicated Hybrid team is needed to do this; when the quantity is large, rules and regulations are needed.

to sum up:

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

Hybrid needs certain specifications when it is widely used, so this article will discuss a Hybrid design knowledge.

  • What are the respective jobs of Hybrid, Native, and front-end
  • How to design Hybrid interactive interface
  • How to design Hybrid Header
  • How to design the directory structure of Hybrid and how to implement the incremental mechanism
  • 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 a host environment. To make reasonable use of the capabilities provided by Native, to achieve a universal Hybrid architecture, and to stand on the front-end vision, I think the following core design issues need to be considered.

Interactive Design

The first consideration in Hybrid architecture design is how to design the front-end interaction with Native. If this design is not good, it will have a profound impact on subsequent development and maintenance of the front-end framework. And this effect is irreversible and hard to return. Therefore, the front end needs to cooperate with Native to provide a common interface in the early stage. such as

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

Account information design

The account system is important and unavoidable. Native needs to design a good and secure identity verification mechanism to ensure that this is sufficiently transparent for business developers to open up the account system

Hybrid development and debugging

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

iOS debugging skills

Android debugging skills:

  • Turn on Webview debugging in the App (WebView.setWebContentsDebuggingEnabled(true);)
  • In the chrome browser, enter chrome://inspect/#devices to access the list of webviews that can be debugged
  • The environment that needs to overturn the wall

结构

Hybrid interaction design

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

通信设计

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

About Url scheme is not clear if you can see this article

JS to Native

Native will provide some APIs in each version, and a corresponding framework team will encapsulate them on the front end to release business interfaces. For 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 front-end interaction, and the front-end can obtain the ability to access Native through this object

Api interaction

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

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

通信示例

Format convention

The first step of the interaction is to design the data format. Here 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. If it is SDGHybrid, the event will be distributed. The parameters may be carried at the end of the processing. The parameters need to be urldecoded and then the result data is obtained through Webview to obtain the callback (Hybrid_timestamp) in the window object

The format of the data return is similar to the normal interface return format

{
  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. The framework && bottom layer doesn't care about H5's business implementation, so Native calls H5 scenes less 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();
        }];
    }
}

Commonly used interactive APIs

Good interaction design is the first step. In real business development, some APIs will definitely be used in 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, has nothing to do with Hybrid
  • H5 Jump to Native interface
  • H5 newly opened Webview to jump to H5 page, general animation switch page
    If you use animation, it is divided into forward and backward according to the business. forward & backword, the rules are as follows, firstly, H5 jumps to a certain page of Native
//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 wants 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 Newly opened Webview to jump to H5

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

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

Header component design

Native changes are "slower" every time, so similar headers are needed.

  1. This is done by mainstream containers, such as WeChat, Mobile Baidu, and Ctrip.
  2. No Header Once there is a network error or a white screen, the App will fall into a state of suspended animation

PS: Native turns on H5, if there is no response in 300ms, loading component is needed to avoid white screen
Because H5 App itself has a Header component, as far as the front-end framework layer is concerned, it is necessary to ensure that the business code is consistent. All differences need to be transparent at the framework layer. Simply put, the header design 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 H5's Header component or Native's 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 click callbacks need to be controlled
  2. The title of the Header can be set as a single title or a main title, subtitle type, and lefticon and righticon (icon centered) can be configured
  3. Meet some special configurations, such as label header

Therefore, from the front-end business side, the use of Header is (where 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 canceling 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 the get request can use jsonp to bypass the cross-domain problem, the post request is a stumbling block. For security issues, the server will set cors only for a few domain names. Hybrid embedded static resources may be read through local files, so cors will not work. Another problem is to prevent crawlers from obtaining data. Because Native has made security settings for the network (authentication, anti-capture, etc.), H5 network requests are completed by Native. Maybe some people say that H5's network request is safe to let Native go? I can continue to crawl your Dom node. This is the first method against anti-reptiles. If you want to know more anti-crawler strategies, you can read my article Web anti-crawler solution

Web网络请求由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 issued by the Native or the 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 layer. Ajax requests are used under the H5 site, and the proxy is used when embedded in Native. 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);
  }
});

Commonly used NativeUI components

In general, Native usually provides commonly used 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, generally only a few key Native UI will be used.

Design of account system

For the webpage running in Webview, the account login or not is determined by whether the key cookie is carried (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. Therefore, 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 call up the login page
  • The direct connection method uses the ajax request interface, if not logged in, the login page will be invoked 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 environment, the online account information can be obtained through the interface, and then the non- Sensitive information is stored in LocalStorage, and then data is read from LocalStorage into memory every time the page is loaded (such as Vuex in the Vue.js framework, Redux in React.js)

Hybrid resource management

Hybrid resources need incremental update needs to be split easily, so a Hybrid resource structure is similar to the following

Hybrid资源结构

Suppose there are 2 business lines: shopping 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

Every time the business is developed, it needs to be deployed and launched on the packaging distribution platform, and then a version number will be generated.

ChannelVersionmd5
Mall1.0.112233000ww
Cart1.1.228211122wt2

When the Native App starts, 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 getting the json, compare it with the version information saved locally in the App, and if there is any change, request the corresponding interface, and the interface returns the file corresponding to md5. After Native gets it, complete decompression and replacement.

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

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

Some scattered 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 will pull the first screen data on the server side and then render it through NodeJs, and then generate an Html file containing the first screen data, so that the content can be resolved when the first screen is displayed The problem of turning chrysanthemum.
Of course, this “straight-out” method of the page will also cause a problem. The server needs to pull the first screen data, which means that the processing time on the server side increases.
However, because Html is now published on the CDN, and WebView is obtained directly from the CDN, this time-consuming has no impact on users.
There is a set of automated build system Vnues in Mobile QQ. When the product manager modifies the data release, 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, and then generate a new Html with the first screen, and publish it to the CDN

  1. Offline pre-push

After the page is published on the CDN, then WebView needs to initiate a network request to pull it. When the user is in a weak network or poor network speed environment, this loading time will be very long. So we use offline pre-push to pull the resources of the page to the local in advance. 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 familiar offline package.
Mobile Q uses 7Z to generate offline packages. At the same time, the offline package server performs a 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. From a complete 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 will strictly control the types of pages that may appear in the webview. You can control the content to avoid the jump to the external page in the wap page, or you can use the corresponding proxy method of the webview to ban the jump types that we don’t want to appear, or use it at the same time, double protection to ensure that only the current webview container There will be content that we have customized. Since the types of wap pages are limited, it is natural to think that most pages of the same type are generated by front-end templates. The resources of html, css, and js used by the page are likely to be the same or a limited number of copies. It becomes feasible to package the client locally. When loading the corresponding url, directly load the local resources.
For network requests in webview, in fact, it can also be taken over by the client. For example, in the Hybrid framework you use, register an interface for initiating network requests for the front end. All network requests in the wap page are sent through this interface. In this way, the client can do a lot. For example, NSURLProtocol cannot intercept the network request initiated by WKWebview. It can be intercepted by sending it to the client in the Hybrid way.
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 meets 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 a specific page, obtain the filled data to complete the display.

NSURLProtocol allows you to redefine the behavior of Apple's URL Loading System. There are many classes in the URL Loading System for processing URL requests, such as NSURL, NSURLRequest, NSURLConnection and NSURLSession. When URL Loading System uses NSURLRequest to get resources, it will create an instance of NSURLProtocol subclass. You should not directly instantiate a NSURLProtocol. NSURLProtocol looks like a protocol, but in fact it is a class and must be used. A subclass of the class and needs to be registered.

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

However, because 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 request body when passing data to the main process, resulting in the loss of the request body parameter after interception.

In the offline package scenario, since the page resource does not require body data, the offline package can be used normally without being affected. But other post requests in the H5 page will lose the data parameter.

In order to solve the problem of post parameter loss, mPaas solved the problem by injecting code into js and hooking the XMLHTTPRequest object in the js context.

By assembling the content of the method in the JS layer, then passing the content to the main process through the messageHandler mechanism of WKWebView, storing the corresponding HTTPBody, and then notifying the JS side to continue the request. After the network request reaches 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-时序图
Through the above mechanism, it not only satisfies the resource interception demands of offline packages, but also solves the problem of post request body loss. However, there are still some problems in some scenarios, which require developers to adapt.

Method two (JS side):
Through the hook method of AJAX request, the information requested by the network is proxied to the client locally. 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 .


杭城小刘
1.1k 声望5.1k 粉丝

95 - iOS - 养了4只布偶猫