preface
This paper introduces the implementation of JSBridge, the core of Quick Hybrid framework
Because in the latest version, iOS7 and other low versions have not been considered, so in the selection of scheme did not use the URL scheme, but directly based on WKWebView implementation
Interaction principle
For the specific interaction principle between H5 and Native, please refer to the interaction principle between H5 and Native mentioned above
The interaction schematic diagram is as follows:
The expected end result
If the analysis step by step, and finally see the effect, may be very boring, and even a little simplification for the appearance of complexity. (I feel it is easiest to read the code directly, but I have to add a lot of description every time I write the article.)
So let’s take a look at what the final product should look like.
// An example of calling alert in the UI
callHandler({
// Module name. The API in this article divides modules
module: 'ui'./ / the method name
name: 'alert'.// Request parameters to be passed to native
data: {
message: 'hello',},callback: function(res) {
Code: 1, message: 'description ', // Data data: {},} */}});Copy the code
architecture
Implementing a JSBridge from scratch makes it easy to get blindfolded.
So we need to define the functional interaction in the big picture first, and then start building the details and coding the implementation
Functional analysis and validation
According to the core architecture, the functions to be implemented are planned:
-
Design of H5 Bridge Object (JSBridge)
-
Short – term callback pool, automatic collection
-
Long-term callback pool, which can be used multiple times
-
A channel that calls a Native method, bridging a native-registered receiver method on an object
-
The channel that receives Native calls, bridging the H5 registered receiver method on the object
-
H5 can register methods that are actively called to native
-
-
Design of native bridge objects
-
Long-term method pools, where each long-term call is stored in the callback pool and can be used multiple times
-
Short term immediate execution, every short term call is immediate execution
-
The channel that calls the H5 method, bridging the h5-registered receive method on the object
-
The channel that receives the H5 call, Bridges the native registered receive method on the object, and the underlying layer automatically parses it and then executes the corresponding API
-
The callback object, the underlying channel that calls H5, is called back to H5 through the callback object after each execution
-
Active calls to H5, unlike callback objects that can only respond passively, can actively call methods registered in H5
-
-
The design of the API
-
The API in H5 is called by the front end, and the bottom layer calls the Native method through the channel, and then sends the preprocessed parameters to the Native
-
Native API, real function implementation
-
Next up is the implementation of JSBridge
Validation of global communication objects
Most importantly, several global bridge objects in communication between H5 and Native are determined first:
-
JSBridge, H5 end of the bridge object, the object is bound to receive the native call method _handleMessageFromNative, as well as internal callback function management
-
Its. MessageHandlers. WKWebViewJavascriptBridge postMessage, iOS the bridge object, this method takes H5 calls
-
Prompt, the Android bridge object, overwrites the onJsPrompt in WebChromeClient for convenience
// H5 internal logic processing
window.JSBridge = {... }// Receive native calls, including callback and active calls
JSBridge._handleMessageFromNative = function() {... }Copy the code
// H5 actively calls native
if (os.ios) {
/ / ios adoption
window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...) ; }else {
window.top.prompt(...) ; }Copy the code
Implementation of JSBridge objects
H5 relies on this object to communicate with Native. Only the core logic is introduced here
JSBridge = {
// A collection of locally registered methods, native can only actively call locally registered methods
messageHandlers: {},
// A collection of short-term callback functions that are automatically removed after a native call to the corresponding method
responseCallbacks: {},
// A collection of long-lived callbacks that can be called multiple times
responseCallbacksLongTerm: {},
_handleMessageFromNative: function(messageJSON) {
// Internal processing:/ ** if it is a callback function: If it is a short-term correction responseCallbacks query callback id, and perform, destroyed automatically after execution If it is a short-term correction responseCallbacksLongTerm query callback id, and perform the * / / * * if you are Native active call: Go to messageHandlers, the locally registered method pool, and do * /},callHandler: function(.) {
// The underlying layer calls the native receiver methods of Android or iOS, respectively
// If it is a short callback, the callback is added to the responseCallbacks
/ / if it is a long-term callback, the callback will be added to the responseCallbacksLongTerm
// omit some logic. if (os.ios) {/ / ios adoption
window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...) ; }else {
window.top.prompt(...);
}
},
registerHandler: function(handlerName, handler) {
// H5 registers methods locally that can be called natively},... };Copy the code
Android bridge object implementation
The core of Android is JSBridge, and the rest is built around it. Here is the pseudocode, listing the main logic
public class JSBridge {
// Cache all API modules (added during registration)
static exposedAPIModlues = new HashMap<>();
static register(String apiModelName, Class<? extends IBridgeImpl> clazz) {
// All framework API modules are automatically found during registration and added to the cache exposedAPIModlues. Each module can have several apis
// Each module needs to implement the IBridgeImpl interface. }static callAPI(...) {
// The parameters (passed in H5) are parsed first, which API is called, and what is passed
/ / the port: H5 passed callback id, is the key in the responseCallbacks or responseCallbacksLongTerm
ModuleName: the moduleName of the API called to retrieve modules registered in exposedAPIModlues
// name: the method name of the API to be called. Look for the API in the found module
// Others: include parameters passed, etc
// A callback object is then generated based on the callback port number of H5.
Callback callback = new Callback(port);
// Then look for API methods based on parsed parameters
// java.lang.reflect.Method;
Method method = searchMethodBy(moduleName, name);
// No method was found to call back for error messages
// Otherwise, execute the method for which the parsed argument is passed
// Call back to H5 for information within method
method.invoke(..., callback);
}
}
Copy the code
Callback class pseudocode is as follows:
public class Callback {
apply(...) {
// Parses the assembled parameters and assembs them into javascript code that contains the port value (Callback ID) for the Callback.. String js = javascript:JSBridge._handleMessageFromNative(for json arguments); callJS(js); } callHandler(...) {// Call H5 initiatively, encapsulating handleName instead of callback ID. callJS(js); } callJS(js) {// The underlying implementation is loadUrl. webviewContext.loadUrl(js); }}Copy the code
The IBridgeImpl interface is empty and is an abstract definition. Here is an example of an API that implements this interface
For clarity, use ui.alert as an example
public class xxxApi implements IBridgeImpl {
// Define a registered module alias for easy lookup, such as UI
static RegisterName = "ui";
// An API in the module, such as alert
public static voidalert(... , Callback callback) {// The next step is to implement the logic in this API.// Finally, H5 is notified by triggering callback
callback.apply(...);
}
}
Copy the code
Finally, you can see that in the WebView, the onJsPrompt of the WebChromeClient has been reactivated to receive the H5 call
And JSBridge register is called when the WebView loads
public class XXXWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(... , JsPromptResult result) {// Internally trigger jsbridge.calljavaresult.confirm(JSBridge.callJava(...) );return true; }}Copy the code
The above several are Andorid JSBridge core implementation, other such as long-term callback, short-term callback, detail implementation optimization is not the core logic, on the list, details can refer to the final source code
IOS bridge object implementation
There is still the OC, main reference marcuswestin/WebViewJavascriptBridge implementation
Core is still a WKWebViewJavascriptBridge, everything else is through it to delivery agent
@ implementation WKWebViewJavascriptBridge {/ / internal based on a WebViewJavascriptBridgeBase base class (interactive methods defined in the base class) WebViewJavascriptBridgeBase *_base; } /** * API */ - (void)callHandler:(NSString *)handlerName data:(id)data { Send data to H5} - (void)registerModuleFrameAPI { Module USES the alias agents [self registerHandlersWithClassName: @ "UIApi moduleName:" @ "UI"]. / / which is registerHandlersWithClassName presented.demonstration registered the module to the role of global, filibuster-proof majority} - (void) excuteMessage message: (nsstrings *) {/ / internal implementations of the API, // module.name,port(callbackID), etc. } #pragma mark - WKScriptMessageHandler is a protocol that follows, - (void)userContentController:(WKUserContentController *)userContentController DidReceiveScriptMessage :(WKScriptMessage *)message {// listen for API calls, Underlying invokes excuteMessage if ([message. The name isEqualToString: @ "WKWebViewJavascriptBridge"]) {[self excuteMessage: message. The body]. }}Copy the code
Then look at the realization of its base class WebViewJavascriptBridgeBase
@implementation WebViewJavascriptBridgeBase
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
// The underlying layer assembles the received data into JS code for execution. NSString* javascriptCommand = [NSString stringWithFormat:@"JSBridge._handleMessageFromNative('%@');", messageJSON];
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
}
- (void)excuteMsg:(NSString *)messageQueueString moduleName:(NSString *)moduleName {
// Find the registered handler based on the module name and API name.// Then create a callback objectWVJBResponseCallback responseCallback = (notify H5 of callback data via sendData);// Then execute this handler
handler(message[@"data"], responseCallback);
}
Copy the code
Next is the definition of the API
RegisterBaseClass, the base class that all modules must implement, defines how to register
Handlers and Handlers {// subclasses override the handlers and handlers of the API} #pragma Mark-handler access - (void)registerHandlerName:(NSString *)handleName handler:(WVJBHandler)handler {// register an API for a module} - (WVJBHandler)handler:(NSString *)handlerName {// get the API by name}Copy the code
To define an API module, inherit RegisterBaseClass and override registerHandlers (use uI.alert for clarity).
@implementation UIApi
- (void)registerHandlers {
[self registerHandlerName:@"alert" handler:^(id data, WVJBResponseCallback responseCallback) {
// Again, notify H5 via responseCallback after data has been received and processed. responseCallback(...) ; }}Copy the code
When in the webview to load is called WKWebViewJavascriptBridge registerModuleFrameAPI, for UI module name and alias UIApi, can see at the time of registration, there are one-to-one correspondence relationship between them
And then when the WebView is created, it’s going to listen, userContentController
WKWebViewConfiguration * webConfig = [[WKWebViewConfiguration alloc] init]; WKUserContentController * userContentVC = [[WKUserContentController alloc] init]; webConfig.userContentController = userContentVC; WKWebView * wk = [[WKWebView alloc] initWithFrame: CGRectZero configuration: webConfig]; self.wv = wk; ./ / agent
self.bridge = [WKWebViewJavascriptBridge bridgeForWebView: self.wv];
[self.bridge setWebViewDelegate: self];
// Add a bridge for js to call oc. The name here corresponds to the name in WKScriptMessage, which we think of as the method name in most cases.
[self.wv.configuration.userContentController addScriptMessageHandler: self.bridge name: @"WKWebViewJavascriptBridge"];
Copy the code
Similarly, other non-core things in iOS, such as long-term callbacks, have been temporarily hidden
The design of the API
According to the above implementation, a complete JSBridge interaction process can be constructed, and the interaction between H5 and Native has been realized
The next step is to design the API to actually call the outside world
To be precise, the API design has moved beyond the JSBridge interaction to the hybrid framework application level, so there will be a separate section on the API in Quick Hybrid
How is the API implemented? See IBridgeImpl for Android and RegisterBaseClass for iOS and override the registerHandlers
As for what API to plan for, it depends on the actual requirements, but in general, things like UI.alert and so on are required
For more details, see the following sections
conclusion
One last picture just to reinforce it
At this point, the entire JSBridge interaction is complete
In summary, considered a variety of forms, found that if the full text description, very boring, it is difficult to insist on reading it, if it is all sorts of principle are described in the drawing +, found that will simplify as complexity, abruptly increase the difficulty for level, so eventually adopt the pseudo code (false and true) display form (excluding some invalid information, Extract the key, and it doesn’t conflict with the final code)
Although said, this whole set of procedures are not particularly difficult place, involving knowledge points are not particularly deep. But it includes the front end, Android, iOS three areas. Therefore, if you want to make the whole set of work better, it is better to have a division of labor, compared to a person’s limited energy, really specialized in multiple fields of people is relatively few, and the subsequent various optimization content is also a lot of (API, optimization, and so on…)
Back to root
- How to implement a Hybrid framework
The source code
Github implements the framework
quickhybrid/quickhybrid
The appendix
The resources
- marcuswestin/WebViewJavascriptBridge