preface

Forget about the background, everyone gets it! Do not understand baidu please! Since you have read this article, you still have your own appeal to dynamic, so I hope the content of the article can help you.


Technology selection

Technology selection is always the first problem encountered after the project is determined. There are many types of selection on the market that can solve the project problems. Is it fashionable to drive development or lively to drive development? In fact, we should be most concerned about the selection process is not the technology, but the project. Because technology should serve the project, not the project should serve the technology, once the weight is sorted out, it is clear. The next step is to start from the project. From the project, three factors should be considered:

  1. Project factors
  2. Team factors
  3. Technical factors

Project factors

The situation that needs to be considered at different stages of a project is completely different.

For example, when the project is just starting or in the phase of basic function laying, the concerns should be rapid trial and error, rapid iteration of requirements, urgent requirements change, frequent operational activities, and other non-functional requirements. During the expansion period of the project, that is, the middle stage, it may undergo a reconstruction to upgrade and rectify all temporary solutions in the early stage, so as to increase the stability of the system and be able to cope with the internal product categories and functional requirements of the project as well as the external market and product expansion. Most project architecture in the project of the plateau have finalize the design, but not so “perfect”, there will be a lot of problems left over by history to let a person have a headache, so this time need to have a more technical field of vision and a man of great ability to lead the team to the project technology depth and height reaching a higher level, of course, This process is very test the ability of the developer.

There is a saying in the chess game that the master has no magic hand. A good architecture is silent, and any change in requirements and functions can be easily completed by developers, with enough flexibility and anti-vulnerability. Of course, I will not discuss too much here.

Team factors

The selection of the team is also very important, because the project is not done by one person, but a group of people. When you pick a technology, there are always people on the team who are not familiar with that technology. So you have to consider the cost of learning for your team members, and when you hire new people, you add the technology requirements to their skill list, and if you choose a technology that most people don’t even know, it’s embarrassing, and the project will go down the drain.

Technical factors

After considering the first two factors together, it’s time to consider the technical factor. What is the technical degree of the selected technical solution or solution? Is it stable?

  1. Are documentation and examples complete?
  2. Is it possible to get the first answer from the technical maintenance personnel after encountering problems?
  3. How to fix bugs?

How stable is the technical solution, how much manpower is needed to support the so-called stability is also something that needs to be considered. The other is extensibility, as opposed to the extension of requirements and functionality.

Finally, a more satisfactory scheme can be obtained by multi-dimensional comparison of three aspects among the alternative technical schemes.


Dynamic selection

Before the review, we discussed the selection with senior engineers from the three terminals (FE, IOS and Android). After comprehensive consideration, we chose Weex and Hybrid. Specific details include but are not limited to technology selection, applicable scenarios, functional boundaries, pointcuts, interaction protocols, etc., which will not be described here.

After selecting the scheme, we made a comprehensive comparison from the above three factors based on the team’s current project stage, as shown in the figure below.

As for why WE didn’t choose Weex, the reason is that the people in our FE team don’t know much about Weex (most of them are new), and the cost of in-depth study is too high (don’t tell me that you don’t need in-depth study and mastery after making sure that the project is completely based on a technical solution, then you are not suitable for this article). We are not sure how to solve the problem of obstruction, after all, we are not ali department. With a Hybrid, these issues are taken out of the equation.

As we all know, javascript is single threaded, and even with the introduction of a non-blocking mechanism at the bottom of the JS engine, it does not change the problem of running pages with more logic (webWorker is not discussed). So the advanced Hybrid solution uses multiple threads to split the logic and view of the front end.

Refer to asynchronous-vs-non-blocking for the difference between asynchronous and non-blocking

Evaluate performance using the RAIL model

RAIL is a user-centric performance model. Each web application has four different aspects of its life cycle that affect performance in different ways:

TL; DR

  • User-centered; The ultimate goal is not to make your site run fast on any particular device, but to make your users happy.
  • Immediate response to users; Confirm user input within 100 milliseconds.
  • When setting up animation or scrolling, generate frames in 10 milliseconds or less.
  • Maximizes idle time on the main thread.
  • Keep attracting users; Render the interaction within 1000 milliseconds.

Finally, based on the RAIL model mentioned above, we come to the conclusion that Hybrid’s experience is not much worse than Weex’s, and it is controllable. For example, the experience effect of small programs.

Industry fit

After selecting Hybrid, we compared the fit of the industry. Because our project is similar to that of e-commerce, which is to buy things. So it fits.


Applicable scenario

Next, the application scenarios of Hybrid solution in project functions are introduced. At present, our project is in the initial stage, so it has few functions, mainly including the following four categories:

In the figure are: home page, secondary page, detail page, single product detail page. Based on our scenario, how Hybrid can be applied to any other scenario of the project besides the personal center, order list, and cash register.

The breakthrough point

Since at the beginning of the project, so far, all three of us have implemented the business logic separately, so in the process of implementing the dynamic solution, there is no one-size-fits-all approach. Time, manpower, and projects didn’t allow it. So we chose a point of entry, and gradually fulfilled our requirements, like changing the territory of a speeding car.

After the project was determined, we gave priority to the detail page of each product as our technical entry point. Specific reasons are as follows:

  1. Lots of content, no complex interactions
  2. Changes frequently, and each item has different details
  3. Simple interaction, suitable for gradual customization of Hybrid protocols and logic

The subsequent migration sequence is as follows: single product detail page -> Detail page -> secondary page -> home page.


The overall architecture

So much talk above, not much technical dry goods, now to introduce the overall architecture design. In the process of requirement realization, we often need to realize the requirement of unified set of pages on both WAP and Native terminals. In order to solve the problem of repeated workload brought by such requirement, we take the compatibility of host environment into consideration in the design.

It is mainly divided into three layers:

  1. The view layer
  2. The container
  3. native / OS

The view layer is mainly responsible for the display of views, including H5 pages and templates, business framework implementation and bridge embedded in the view layer. If the view is in the webView of APP, Native Activities controls will also be included. How to design Native Activities will be discussed later.

The container is the execution environment for the view layer, either a mobile browser or an App webView. In addition to the browser, webView provides a Bridge Provider to output the capabilities encapsulated by the end to the view layer, usually through API injection and Schema implementation. It encapsulates Native level business apis and hardware device apis.

At the bottom is the OS layer of Native, which mainly provides all necessary basic capabilities. Since I don’t know much about Native, I won’t discuss it here.

Because the view layer is isolated from the container layer, the view layer does not need to care about the container implementation, but the bridge between them does. So how bridge is compatible with different containers (WAP browsers, Native Apps) is a question worth considering.

This is a simple layered architecture. Each of these layers has a specific role and function. The layers in an architecture are a high level of abstraction of concrete work that exists to fulfill a particular business request. Another outstanding feature is the separation of concerns, with each layer only dealing with its own logic. Layered isolation, on the other hand, makes layers independent of each other. Each layer in the architecture must comply with the minimum knowledge principle, and this high degree of independence makes it very compatible with WAP browsers and Native apps.


View Layer Design

What is the nature of UI? Is to get the data state from the server and display it through some operation. We can express their relationship in a mathematical expression: UI = f(state). State is the data obtained through bridge or asynchronous interface, and UI is the interface seen by the user. For Hybrid mode, what you really care about is how to realize f function.

We can easily think of f as a web Container, a Web Container. As for the Container? See the picture below:

As mentioned earlier, Hybrid can separate the View from the Service in the View layer to improve the experience. In Native App, a page is generally divided into these two parts and placed in two different WebViews, one for View part and the other for Service part * (in other words, each page needs two WebViews) *.

They package the data to be sent or just received through their respective Bridges, and then exchange data through tunnels encapsulated in the Bridge to complete subsequent operations. In a WAP browser, however, you don’t need to worry about that at all. One compatibility issue, however, is that the view layer interacts with the container through a Bridge, which means that a BRIDGE needs to exist in a WAP browser (which is also a container), but that bridge provides the capabilities of the browser.

There is something called a tunnel in the Bridge, which is responsible for passing data and events between the Service and the View. In different host environments, the composition of a tunnel is different. In Native App, a tunnel is an IPC implementation. In the browser, a tunnel is an implementation of a publish-subscribe event mechanism.

The problem of local storage of data is encountered in services. Data storage and acquisition will be uniformly sent to Native through the bridge, and then Native will process the operation content according to different operation contents. After completion, the processed data will be sent to Bridge, and then the Bridge will notify Service.

The data transfer

A Service contains any logic in the view layer other than view rendering. It processes the retrieved state through the Framework’s apis to generate View Metadata. View Metadata is a simple description of the View to be rendered, from which we can predict what the View will look like. The framework then sends the View meta-information to the View through a tunnel in the Bridge.

Note: The tunnel sends data asynchronously. Such as the setData() method in the applet.

A View contains only page rendering in the View layer. There are two main rendering objects: HTML and Native Activities controls to display. When a Service sends a view metadata containing a Native level control, the Bridge sends that part of the view metadata to Native. When Native receives it, it displays the Native control on the WebView in the view layer based on the metadata * (note: the Native component is covered on the WebView) *. When the data sent by the Service is HTML view metadata, DOM Diff is performed according to the view metadata first, and then the page is rendered according to the generated Patch object.

The transmission of the event

Once the View is rendered, it waits for the user to act. The View treats user-operated events differently: HTML events and Native control events. First, let’s talk about Native events. Native events cannot be controlled by WebView. Therefore, more attention should be paid to the encapsulation of business Native controls and unified sorting of events that may be encountered by controls. The Native control serializes the event source and event parameters through the Native framework, which then sends the serialized event data to the Service via the Bridge.

If it is an HTML event, the bridge on the View side will fetch the event source and event parameters from JS, and then serialize them uniformly. The serialized event data is then sent to the Service through a tunnel.

Request for data

The data requested in a Web Container falls into two main categories:

  • Static resource request
  • Business data request (asynchronous interface)

Static resource requests are initiated directly from the webView, which will not be described here.

For asynchronous interface requests other than static resource requests, we proxy them through Native and let Native send the request for us instead of using the XMLHttpRequest object.


Bridge design

The Bridge layer is located between the view layer and Native and is responsible for connecting both sides. A good bridge design can get twice the result with half the effort in the development process.

Our focus on Bridge:

  1. Located in theJs execution environmentThe host environmentBetween, responsible for linking both sides
  2. Compatible host environment * (WAP, APP) * differences
  3. Ability to accommodate bridging (injection apis, Schema protocols) * provided by different lines of business
  4. Configure the bridge capability based on service lines
  5. inCompilation phaseAddress host environment compatibility

The JS execution environment may be a WAP browser, or a WebView in Native. The host environment can also be a browser and a Native App. The connected service providers provide different bridging capabilities. A unified solution must be connected to at least three platforms with different functional requirements. These are the issues that need to be addressed in Bridge.

As mentioned in the previous section, “For asynchronous interface requests other than static resource requests, we will conduct proxy through Native and ask Native to send the request for us”. The main reasons for doing this are:

  1. Interface authentication is faulty. Procedure
  2. Control the granularity of data updates

Let’s start with the first authentication problem. The general practice is that after App users log in, the authentication id of the user is stored in the cookies of the webView, and then when the business code in the webView sends AJAX requests, the cookies will be carried to the server to complete user authentication. In this case, if the server fails to verify the user’s token, the APP cannot jump to the login window immediately. In addition, an asynchronous interface request for logging out is sent in the WebView, and the APP also needs to log out synchronously. Obviously, the best way is to let the APP send asynchronous interface requests for us. In this way, we can also take advantage of the persistent cache capability of the APP to store interface data.

Overall architecture process

Host environment variability

In terms of the differences between browser and Native APP, we summarize the following five points:

  1. View control
  2. Data is stored
  3. Asynchronize interface requests
  4. Page routing
  5. Page History Management

We will encapsulate a unified API on different functions to reduce compatibility issues for FE developers.

Using only asynchronous interface requests as an example, we encapsulate a unified Request method. Developers don’t need to care which platform their code will run on. With the tree Shaking feature of tools like WebPack and Rollup, we can do differentiated compilation well.

// tools.js
import Axios from 'Axios';
import bridgeRequest from '@/bridge/request.js';

export default {
	request: process.env.TARGET === 'app' ? bridgeRequest : Axios
}

// main.js
import {request} from 'tools';

request.get('http://www.test.com/test', {a: 1}).then(data= > {
	console.log('this is test data -> ', data);
});
Copy the code

At compile time we only need to specify target to do differential compilation:

# Build to app version
$ npm run build --TARGET=app
Build to waP browser version
$ npm run build --TARGET=browser
Copy the code

Bridge capacity injection

We customize a Bridge standard interface to regulate various operations, such as Native’s call-up pop-up layer controls. According to the API or Schema protocol injected into the WebView by the business side, a configuration Json file is filled in and then injected into the Bridge. This file declares the protocol or method and parameter names to be accessed by the alert operation. This way, when the Bridge calls the Alert method, it does the specified operation based on json.

The business side only needs to inject its own Schema protocol according to the standard interface defined by the Bridge.

// system.schema.json
export default {
	alert: {
		schema: 'xxxx'.params: {}},request: {
		schema: 'xxxx'.params: {}}}// interactive,js
import schema from '@/schemas/system.schema.json';
Inject the business side's own Alert Schema
interactive.injectSchema(schema);
export default {
	alert(options) {
		return interactive.api.alert(options)
	}
}

// main.js
import {bridge} from '@/bridge/index.js';
import {alert} from '@/bridge/interactive.js';

// The view layer is ready
bridge.on('ready', () => {
	alert('This is an alert! ').then(data= > {
		console.log(data.state ? 'sure' : 'cancel');
	}).catch(e= > {
		console.log('Failed to raise alert');
	});
})
Copy the code

Native layer design

Since I am not a Native developer myself, here is a diagram of the Native architecture for you to see for yourself.

Note: this picture was drawn by me as FE, and was teased by the Android guy for its unclear structure. You’ll have to make do with it!

At this point, we have covered the three main layers of the architecture: view layer, Bridge layer, and Native layer. The functional design is introduced below, mainly including three aspects: native component interaction, routing system (unified hop protocol), cache and update of resource package.


Native component interaction

A native component is not the same as a webView component implemented in javascript. They are Native controls rendered directly on top of the WebView by Native and cannot be affected by javascript, only by Native. For javascirpt code in WebView: beyond three bounds, not in five lines.

Why not use all js components in WebView? That is, the front-end component of WebView has too little influence, just like the imperial court at the end of tang Dynasty. For example, when the Alert prompt is displayed, no other interactive operations can be performed, but only the confirm and cancel buttons of Alert can be clicked. There is also the back of the left button on the Header and the operation of clicking the Icon on the right to return to the home page of the APP. Examples like this can go on. So in this case, it is necessary to ask Native controls out of the horse control field.

Here’s a look at the Native level controls available:

  • Header
  • Footer TabBar
  • Alert Tip Confirm
  • Dialog
  • SelectBar

Load timing of “partial” native components

Native components that always need to be displayed first in the view (headers, tabbars, and so on) must be treated differently. Do not render native controls after the WebView is loaded, because this will cause data errors and page flickering in the Service due to recalculation of the WebView to render native controls, which will affect the user experience.

The best approach is to place view metadata for such native components in a separate JSON file (described below) that controls versioning, rather than in a ZIP package containing the bundle’s contents. This allows Native to pre-render the Native controls based on the view meta-information in the JSON file, then load the WebView and execute the javascript code.


Routing system

Before designing the entire routing system, we had a prerequisite that each View page should be a separate WebView (actually two, one for View logic and one for Service logic), rather than loading and rendering multiple pages in the same WebView. Because only in this way can you perfectly simulate the various operations of the native application page jump. Pay attention to this, because if you don’t pay attention you won’t understand what’s going on!

We encountered the following scenarios:

Jump scenario:

  1. Native to Native
  2. Native to WebView
  3. WebView to Native
  4. WebView to WebView

Loading scenario:

  1. Same page loading (redirection)
  2. Cross-page loading

Existing problems:

  • Maximum number of Concurrent WebViews
  • Parameter passing between views
  • View history stack management

Finally, we agreed that the number of WebViews that can exist at the same time is 9, which is the same as wechat applet. A new page cannot be opened when the page stack reaches 9. The queryString format is used for parameter passing between pages. The management of the history stack is unified by Native.

History stack management

The reason we maintain a history stack is so that views in Native can move forward and backward just like browser history. The only difference is that the browser’s history stores URL strings, whereas our history stack stores view objects. Every time Native APP is opened, it will be re-recorded from the beginning, only the history during APP operation will be recorded, and the history stack will be cleared after the APP is closed.

Step by step to access

Normal action path access places views at each level in the history stack. A maximum of 9 levels can be saved, beyond which new pages cannot be loaded.

Repeat the page

When the latest item page opens a new secondary page, the history manager opens a new secondary page at the top of the stack, even if the secondary page has already been opened. Note that the two secondary pages are completely independent. There is no view promotion.

redirect

When the latest single page is redirected to a secondary page, the current page is redirected to a secondary page and rendered, similar to the repeat page above. Note that the two secondary pages are completely independent. There is no view promotion.

back

The current history management stack is consumed when you click the back button to the left of the Header (one-level back) or use the Hybrid Router API (multilevel back) to back up.


Caching and updating of resource packs

Once all the steps are in place, it’s time to move to this step, the caching and updating of bundle resources. Here we introduce the mechanism of subcontracting load, and the subcontracting level is page – based, not feature – based. It’s basically the subcontracting loading mechanism of a small program.

In order to realize this mechanism, we abandoned the cache of WebView and developed and established this cache mechanism together with Native students. And only bundle resources are cached (zip packages one by one). We specify that there is only one entry ZIP package for each business, and all subzip packages must be updated and loaded depending on subconf. json in the entry ZIP package.

Loading will be displayed only when Native updates the entry package every time, in addition, the SKELETON diagram HTML carried in the entry package will be displayed.

Every time the APP is opened, it first goes to the server to get conf.json. The content in conf.json is as follows:

{
	"version": "v1.0.1", // This content is for example only"skeletonURL": "https://pan.baidu.com/nt-static/hybrid/app.skeleton_v1.0.1.d3a938346f1ab825.html"{version}.{md5}.zip"zip": "https://issue.pcs.baidu.com/packages/bybrid/v1.0.1.d3a938346f1ab825.zip", // MD5 of the zip package. Based on this MD5, determine whether the zip package needs to be updated"md5": "d3a938346f1ab825"// Signature verification"signature": "342876ba19d34aba92f7536e42992a45"// Native controls that need to be pre-rendered"header": {
		"title": "this is a title"
	},
	"tabBar": {{"text": "log"."icon": ""
		},
		{
			"text": "home"."icon": ""}}}Copy the code

The directory structure of the entry zip package is as follows:

$tree. / v1.0.1. D3a938346f1ab825. / v1.0.1 d3a938346f1ab825 ├ ─ ─ app. The bundle. The CSS / / style file ├ ─ ─ app. The bundle. The js / / js logical file ├ ─ ─ App. Index. HTML/HTML/entrance ├ ─ ─ app. Skeleton_v1. 0.1 d3a938346f1ab825. HTML / / skeleton diagram, Consistent and conf., in the json skeletonURL └ ─ ─ subConf. Json / / child package loading and check configurationCopy the code

Content in subconf. json:

{// Same as conf.json"signature": "342876ba19d34aba92f7536e42992a45"// Subpackage entry"subRoutes": [{// The route to the entry"routes": ["/go/to/path/1"."/go/to/path/2"], // Skeleton map URL"skeletonURL": {
				"/go/to/path/1": "https://pan.baidu.com/nt-static/hybrid/app.skeleton_v1.0.1.bfa31a2ae5f55a7f.html"
			},
			"zip": "https://issue.pcs.baidu.com/packages/bybrid/v1.0.1.bfa31a2ae5f55a7f.zip"
			"md5": "bfa31a2ae5f55a7f"
		},
		{
			"routes": ["/go/to/path/3"]."skeletonURL": {}
			"zip": "https://issue.pcs.baidu.com/packages/bybrid/v1.0.1.7af5f492a74499e7.zip"."md5": "7af5f492a74499e7"}}]Copy the code

The last

This paper mainly introduces the three layers of Hybrid’s overall architecture and three points of functional design, basically covering all the key points of the end dynamic direction. Hope this article can help you.