The functions of mobile applications are becoming more and more abundant, and some functions need to be realized in H5
This requires H5 to interact with Native. We use JSBridge to communicate between them
JSBridge defines the communication mode between Native and H5
Native calls H5 through the bridge object of the contract
H5 call Native through the bridge object of the contract
1, Interaction mode
1. Pseudo protocol url scheme (applicable to all devices)
The pseudo protocol is similar to url. H5 triggers the pseudo protocol, and the system judges according to priority:
- Whether it is a system application. If yes, open the system application
- Whether an app registers the current pseudo protocol. If yes, open the app
- native triggers the url event, captures the pseudo protocol, parses the pseudo protocol, and calls the method
Specific process:
-
H5 call Native
Trigger pseudo protocol, for example:
window.location.href= 'xxx://event=a&data=b&successName=c&failName=d' -
Native call H5
H5 registers methods in the window for native calls (the same as native calls H5 in API interaction mode)
2. Interaction through API
Specific process:
-
H5 call Native
1.1. H5 calls Android: native is registered through addjavascript interface and then called by H5
1.2. H5 calls iOS: native is registered through JavaScript core (above iOS 7) and then called by H5 -
Native call H5
2.1. Android calls H5: H5 registers methods on the window. native calls H5 through loadUrl. Versions 4.4 and above can be called through evaluateJavascript
2.2. iOS calls H5: H5 registers the method on the window, and native calls H5 through stringByEvaluatingJavascriptFromString
3. Distinction
The difference is how H5 calls Native and top note Native responds to H5.
3.1 how H5 calls Native:
Pseudo protocol:
window.location.href = 'xxx://event=a&data=b&successName=c&failName=d'
API:
window.JSActionBridge.handlerAction( event, data, successName, failName ) window.webkit.messageHandlers.JSActionBridge.postMessage({ method: 'handlerAction', data: { actionEvent: event, paramsJsonValue: data, successCallback: successName, errorCallback: failName } })
3.2 Native response H5 is not listed
3.3 benefits of API interaction:
- It's easier to write in H5 without creating a URL
- H5 is more convenient to pass parameters. The method of intercepting URL is used. The parameters need to be spliced behind the URL. If they contain special characters, they need to be escaped, otherwise there will be errors in parsing parameters, such as & =?
- for example
window.location.href = 'xxx://event=a&data=http://www.baidu.com/xxx.html?p=2&successName=c&failName=d'
Here, the value obtained by parsing the parameter is not the desired value, which needs to be corrected http://www.baidu.com/xxx.html?p=2 Escape
2, Scheme
At present, the company uses API to interact internally (Android needs more than 4.2 and iOS needs more than 7)
1. Agreed name
Primary pass JSActionBridge register JSBridge object H5 adopt JSActionBridge obtain JSBridge object
2. Native JSBridge object registration
Android:
webView.addJavascriptInterface(new JSActionBridge(), "JSActionBridge")
iOS:
// Register via JavaScript core self.context = [[JSContext alloc] int]; self.context[@"JSActionBridge"] = self.bridgeobj;
3. Call
3.1 definition of native method
Android: /* * @params {String} actionEvent: Native event name and method name to be called * @params {String|Object|Array} paramsJsonValue: The parameters passed to the native should preferably be in json format * @params {String} successCallBack: The name of the callback function to be executed after calling the native method successfully. This function is defined on the window * @params {String} errorCallback: The name of the callback function to be executed after calling the native method fails, which is defined on the window */ void handlerAction(String actionEvent, String paramsJsonValue, String successCallBack, String errorCallback)
3.2 H5 call Native
JSActionBridge.handlerAction(actionEvent, paramsJsonValue, successCallBack, errorCallback)
3.3 Native call H5
Android calls H5
String callbackUrl = "javascript:" + callback + "(\"" + jsonData + "\")"; webView.loadUrl(callbackUrl);
iOS call H5
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat: @"callBack(%@)", jsonData]];
3, Data Convention
Native method definition
void handlerAction(String actionEvent, String paramsJsonValue, String successCallBack, String errorCallback)
1. Both successful and failed callback functions return json data
Data format:
{ "code":0, "desc":"success", "data": nativeReturnData }
2. When the contract code === 0, the call is successful, otherwise it fails
3. actionEvent classification
- Jump to native PAGE
Home page, login registration page, etc - Get native data
Obtain user information, equipment information, etc - Call native functions
Jump to new page, wake up password input box, view network status, set title, etc - other
4, JSBridge encapsulation
1. Agreement:
- Android transfer string
- iOS delivery json
2. successCallBack/errorCallback callback data format:
{ "code":0, "desc":"success", "data": nativeData }
3. JSBridge encapsulation
import { isIOS, isAndroid, isApp } from './utils.js' const JSBridge = { isApp, // APP offline environment isOfflineApp: isApp && location.protocol === 'file:', // H5 call App /** * Call App method * @param event: Event name * @param data: json data * @param successCallBack: Successful callback * @param failCallBack: Failed callback * @returns {Promise<object>} */ callApp (event, data = {}) { return new Promise((resolve, reject) => { // Call not in App, prompt not in App if (!this.isApp) { resolve('not in app') return } // Construct unique key const callbackKey = Date.now() + '' + Math.floor(Math.random() * 100000) const successName = `s${callbackKey}` const failName = `f${callbackKey}` // Use the constructed "success \ failure key" to register a function on the window so that the App can call this.registerFn(successName, function(data) { if (data.code === -1) { // reject if failed reject(data) } else { // resolve if successful resolve(data.data) } }) this.registerFn(failName, function(err) { reject(err) }) // ******Start calling the method provided by App****** // Android if (isAndroid) { // Android needs to string the data data = JSON.stringify(data) window.JSActionBridge.handlerAction( event, data, successName, failName ) } // iOS if (isIOS) { window.webkit.messageHandlers.JSActionBridge.postMessage({ method: 'handlerAction', data: { actionEvent: event, paramsJsonValue: data, successCallback: successName, errorCallback: failName } }) } }) }, /** * Register H5 methods for app calls * @param fnName * @param fn */ registerFn (fnName, fn) { if (typeof fnName !== 'string') { throw TypeError('Illegal fnName: Not an string') } if (typeof fn !== 'function') { throw TypeError('ol. fn: Not an function') } window[fnName] = function (data) { if (isIOS) { fn(data) } if (isAndroid) { // Android environment needs to be converted data = data || '{}' fn(JSON.parse(data)) } } }, /** * Unregister H5 methods for app calls * @param fnName */ unregisterFn (fnName) { if (typeof fnName !== 'string') { throw TypeError('Illegal fnName: Not an string') } delete window[fnName] }, /** * Jump to app module * @param url * @param isWaitingResult * @returns {*|Promise<Object>} */ gotoNativeModule (url, isWaitingResult = false) { if (this.isApp) { this.callApp('goto_native_module', { url, isWaitingResult }) } }, /** * Newly opened in APP * @param url * @param titleBarVisible * @param title */ gotoNewWebview (url, titleBarVisible = true, title = '') { if (this.isApp) { this.gotoNativeModule( `xxx://webview?url=${encodeURIComponent( url )}&titleBarVisible=${titleBarVisible}&title=${title}` ) } else { window.location.href = url } }, /** * Monitor page activity status * @param activated * @param deactivated */ watchPageActivity (activated, deactivated) { const successCallBackName = 'command_watch_activity_status_success_callback' const failCallBackName = 'command_watch_activity_status_fail_callback' const successCallBack = window[successCallBackName] const failCallBack = window[successCallBackName] this.registerFn(successCallBackName, function (data) { if ( Object.prototype.toString.apply(successCallBack) === '[object Function]' ) { successCallBack(data) } try { if (typeof data === 'string') { data = JSON.parse(data) } if (data.data.status === 'visible') { activated(data) } else { deactivated(data) } } catch (e) { console.log(successCallBackName, e) } }) this.registerFn(failCallBackName, function (data) { if ( Object.prototype.toString.apply(failCallBack) === '[object Function]' ) { failCallBack(data) } }) this.callAppNoPromise( 'command_watch_activity_status', {}, successCallBackName, failCallBackName ) }, /** * Call app method to register global events * @param event: Event name * @param data: json data * @param successCallBack: Active setting successful callback * @param failCallBack: Active setting failure callback * @returns {Promise<object>} */ callAppNoPromise (event, data = {}, successCallBackName, failCallBackName) { if (!this.isApp) { return } // Android if (isAndroid) { data = JSON.stringify(data) window.JSActionBridge.handlerAction( event, data, successCallBackName, failCallBackName ) } // ios if (isIOS) { window.webkit.messageHandlers.JSActionBridge.postMessage({ method: 'handlerAction', data: { actionEvent: event, paramsJsonValue: data, successCallback: successCallBackName, errorCallback: failCallBackName } }) } }, /** * Obtain app user information to prevent multiple access * @param needUpdate Need to update again * @returns {*|Promise<Object>} */ _appUser: { _loaded: false, // Whether data has been loaded from the app. This field is not obtained from the app userName: '', phoneNum: '', userId: '', userToken: '', // User token. If there is no token, it means that the user is not logged in }, getAppUser (needUpdate) { return new Promise(resolve => { if (!this.isApp) { resolve(this._appUser) } if (needUpdate || !this._appUser._loaded) { this.callApp('get_user_info').then(res => { this._appUser = res this._appUser._loaded = true resolve(this._appUser) }) } else { resolve(this._appUser) } }) } } export default JSBridge