Optimize code and UI modules (Android)
Next we need to optimize the code
Divide the code into multiple files
Create the following structure file:
JSBridgeUI code move
Move the jsbridgeui-related code to jsbridgeui.kt.
package com.example.tobebigfe
import android.webkit.WebView
import android.widget.Toast
import com.example.tobebigfe.jsbridge.WebActivity
import org.json.JSONObject
class JSBridgeUI(val activity: WebActivity, val webView: WebView) : BridgeModule {
override fun callFunc(func: String, arg: JSONObject) {
when (func) {
"toast" -> toast(arg)
}
}
private fun toast(arg: JSONObject) {
val message = arg.getString("message")
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
}
Copy the code
Bridge related code movement
And then:
- BridgeModule
- BridgeObject
The associated code is moved to webviewbridge.kt
Code minor changes to:
interface BridgeModule {
fun callFunc(func: String, arg: JSONObject)
}
// Add parameters
class BridgeObject(val activity: WebActivity, val webView: WebView) {
private val bridgeModuleMap = mutableMapOf<String, BridgeModule>()
init {
bridgeModuleMap["UI"] = JSBridgeUI(activity, webView)
}
@JavascriptInterface
fun callNative(callbackId: String, method: String, arg: String) {
Log.e("WebView"."callNative ok. args is $arg")
val jsonArg = JSONObject(arg)
val split = method.split(".")
val moduleName = split[0]
val funcName = split[1]
valmodule = bridgeModuleMap[moduleName] module? .callFunc(funcName, jsonArg) } }Copy the code
WebActivity code
abstract class WebActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WebView.setWebContentsDebuggingEnabled(true)
webView.settings.javaScriptEnabled = true
webView.settings.cacheMode = LOAD_NO_CACHE
webView.webViewClient = WebViewClient()
// Add our js object before loading the page
webView.addJavascriptInterface(BridgeObject(this, webView), "androidBridge")
// Load the web page in assets
webView.loadUrl(getLoadUrl())
}
// Propose an abstract method that lets subclasses implement the loaded URL
abstract fun getLoadUrl(a): String
}
Copy the code
Implement the UI. Alert
Jsbridge.js is appended
JSBridge.UI.alert = function(params) {
callNative('UI.alert', params)
}
Copy the code
index.html
<script type="text/javascript">
function onClickButton(button) {
switch (button) {
case "UI.toast":
JSBridge.UI.toast("This is toast!")
break
case "UI.alert":
JSBridge.UI.alert({
title: 'notice',
message: 'This is an Alert',
button: "Good"
})
break}}</script>
<button onclick="onClickButton(this)">UI.toast</button>
<button onclick="onClickButton(this)">UI.alert</button>
Copy the code
JSBridgeUI
override fun callFunc(func: String, arg: JSONObject) {
when (func) {
"toast" -> toast(arg)
"alert" -> alert(arg)
}
}
private fun alert(arg: JSONObject) {
activity.runOnUiThread {
AlertDialog.Builder(activity)
.setTitle(arg.get("title") asString? ? :"Tip")
.setMessage(arg.get("message") asString? ? :"")
.setItems(arrayOf(arg.get("button") asString? ? :"Sure")) { _, _ -> }
.create()
.show()
}
}
Copy the code
Running effect
UI. Confirm and callback
Ui. confirm is a bridge that requires callback to JS.
Native
Native relatively requires some modification of method parameters, step by step.
BridgeModule adds the callbackId parameter because the callback is implemented using:
interface BridgeModule {
fun callFunc(func: String, callbackId: String, arg: JSONObject)
}
Copy the code
Add a new class BridgeModuleBase to implement callback code:
abstract class BridgeModuleBase(val webView: WebView) : BridgeModule {
fun callback(callbackId: String, value: Int) {
execJS("window.$callbackId($value)")}fun callback(callbackId: String, value: Boolean) {
execJS("window.$callbackId($value)")}fun callback(callbackId: String, value: String?). {
if (value == null) {
execJS("window.$callbackId(null)")}else {
execJS("window.$callbackId('$value')")}}fun callback(callbackId: String, json: JSONObject) {
execJS("window.$callbackId($json)")}fun execJS(script: String) {
Log.e("WebView"."exec $script")
webView.post {
webView.evaluateJavascript(script, null)}}}Copy the code
Call module.callFunc in BridgeObject
valmodule = bridgeModuleMap[moduleName] module? .callFunc(funcName, callbackId, jsonArg)Copy the code
JSBridgeUI changes:
// 1. Inherit BridgeModuleBase to prepare for completing callback
class JSBridgeUI(val activity: WebActivity, webView: WebView) : BridgeModuleBase(webView) {
// 2. Add callbackId
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
Copy the code
JSBridgeUI implements confirm:
override fun callFunc(func: String, callbackId: String, arg: JSONObject) {
when (func) {
"toast" -> toast(arg)
"alert" -> alert(arg)
// The callbackId needs to be passed
"confirm" -> confirm(callbackId, arg)
}
}
private fun confirm(callbackId: String, arg: JSONObject) {
val buttons = mutableListOf<String>()
if (arg.has("buttons")) {
val buttonArr = arg.getJSONArray("buttons")
for (i in 0 until buttonArr.length()) {
buttons.add(buttonArr.getString(i))
}
} else {
buttons.add("Cancel")
buttons.add("Sure")}val message = arg.get("message") as String?
activity.runOnUiThread {
AlertDialog.Builder(activity)
.setTitle(message)
.setItems(buttons.toTypedArray()) { _, index ->
// Call the parent class callback to return the selected index
callback(callbackId, index)
}
.create()
.show()
}
}
Copy the code
One detail is that we don’t support js passing the title attribute on Android because setMessage invalidates setItems, so the code above uses setTitle to pass the message argument to AlertDialog
Running effect
If you select a button, toast will appear in the index of the button. That is, js will get the selected result of the original return
Optimize code and UI modules (iOS)
Next we need to optimize the code and fix some issues, and there are some ios-related programming issues in the code
Divide the code into multiple files
Under ToBeBigFE/JSBridge, create webViewBridge.swift
Will:
- BridgeModule
- BridgeHandler
Shear to WebViewBridge. The swift
Under ToBeBigFE/JSBridge, create jsBridgeui.swift
Cut the code for the JSBridgeUI class to jsbridgeui.swift
After completion, the structure is as follows:
Fix memory leaks and condition judgments
JSBridgeUI. Swift changes:
class JSBridgeUI : BridgeModule {
// 1. Use weak to remove cyclic references
weak var viewController: WebViewController?
init(viewController: WebViewController) {
self.viewController = viewController
}
func callFunc(_funcName: String, arg: [String : Any?] ) {
switch funcName {
case "toast":
toast(arg)
default: break}}func toast(_arg: [String : Any?] ) {
// 2. Use guard to prevent message from being null
guard let message = arg["message"] as? String else {
return
}
// 3. Use question marksviewController? .view.makeToast(message) } }Copy the code
BridgeHandler class changes:
class BridgeHandler : NSObject.WKScriptMessageHandler {
// 1. Use weak to remove cyclic references
weak var webView: WKWebView?
// 2. Use weak to remove cyclic references
weak var viewController: WebViewController?
var moduleDict = [String:BridgeModule] ()func initModules(a) {
/ / add!
moduleDict["UI"] = JSBridgeUI(viewController: viewController!)
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage)
{
// 3. Guard prevents illegal calls and calls after deinit
guard
let body = message.body as? [String: Any].let webView = self.webView,
let viewController = self.viewController,
let callbackId = body["callbackId"] as? String.let method = body["method"] as? String.let data = body["data"] as? String.let utf8Data = data.data(using: .utf8)
else {
return
}
print("WebView callNative ok. body is \(body)")
// 4. Do catch to prevent data parsing errors
var arg: [String:Any? ] ?do {
arg = try JSONSerialization.jsonObject(with: utf8Data, options: []) as? [String:Any? ] }catch (let error) {
print(error)
return
}
let split = method.split(separator: ".")
let moduleName = String(split[0])
let funcName = String(split[1])
// 5. Guard optimization
guard let module = moduleDict[moduleName] else {
return
}
// 6. Arg is an empty Dictionary by default
module.callFunc(funcName, arg: arg ?? [String:Any? ] ()}}Copy the code
Implement the UI. Alert
Jsbridge.js is appended
JSBridge.UI.alert = function(params) {
callNative('UI.alert', params)
}
Copy the code
index.html
<script type="text/javascript">
function onClickButton(button) {
switch (button) {
case "UI.toast":
JSBridge.UI.toast("This is toast!")
break
case "UI.alert":
JSBridge.UI.alert({
title: 'notice',
message: 'This is an Alert',
button: "Good"
})
break}}</script>
<button onclick="onClickButton(this)">UI.toast</button>
<button onclick="onClickButton(this)">UI.alert</button>
Copy the code
JSBridgeUI
.func callFunc(_funcName: String, arg: [String : Any?] ) {
switch funcName {
case "toast":
toast(arg)
case "alert":
alert(arg)
default: break}}...func alert(_arg: [String : Any?] ) {
let title = arg["title"] as? String ?? "Tip"
let message = arg["message"] as? String ?? ""
let button = arg["button"] as? String ?? "Sure"
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: button, style: .default, handler: {
action in}) alertController.addAction(okAction) viewController? .present(alertController, animated:true, completion: nil)}...Copy the code
Implement UI.confirm and callback
Ui. confirm is a bridge that requires callback to JS.
Native
Native relatively requires some modification of method parameters, step by step.
BridgeModule changes:
protocol BridgeModule : class {
// Add the callbackId parameter that callback will use
func callFunc(_funcName: String, callbackId: String, arg: [String: Any?] )
}
Copy the code
Add a BridgeModuleBase class that does some callback logic:
class BridgeModuelBase : BridgeModule {
weak var webView: WKWebView?
func callback(callbackId: String, value: Int) {
execJS("window.\(callbackId)(\(value))")}func callback(callbackId: String, value: Bool) {
execJS("window.\(callbackId)(\(value))")}func callback(callbackId: String, value: String?) {
if value == nil {
execJS("window.\(callbackId)(null)")}else {
execJS("window.\(callbackId)('\(value!)')")}}func callback(callbackId: String, json: [String:Any?] ) {
guard let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return
}
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
return
}
execJS("window.\(callbackId)(\(jsonString))")}func execJS(_ script: String) {
print("WebView execJS: \(script)") webView? .evaluateJavaScript(script) }func callFunc(_funcName: String, callbackId: String, arg: [String: Any?] ){}}Copy the code
JSBridgeUI inherits BridgeModuleBase:
class JSBridgeUI : BridgeModuelBase {
Copy the code
BridgeHandler changes:
// Assign webView before callFunc because callback needs it
module.webView = webView
// Add the callbackId parameter
module.callFunc(funcName, callbackId: callbackId, arg: arg ?? [String:Any? ] ())Copy the code
JSBridgeUI changes:
// Add the callbackId parameter
override func callFunc(_funcName: String, callbackId: String, arg: [String : Any?] ) {
switch funcName {
case "toast":
toast(arg)
case "alert":
alert(arg)
case "confirm":
// Unlike toast and Alert, confirm requires a callbackId
confirm(callbackId: callbackId, arg)
default: break}}func confirm(callbackId: String, _arg: [String : Any?] ) {
let title = arg["title"] as? String ?? "Tip"
let message = arg["message"] as? String ?? ""
let buttons = arg["buttons"] as? [String]???? ["Cancel"."Sure"]
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
buttons.forEach { button in
let action = UIAlertAction(title: button, style: .default) { action in
// callback Specifies the index of the action selected by the user
self.callback(callbackId: callbackId, value: buttons.firstIndex(of: button)!) } alertController.addAction(action) } viewController? .present(alertController, animated:true, completion: nil)}Copy the code
Jsbridge.js is appended
JSBridge.UI.confirm = function(params, callback) {
callNative('UI.confirm', params, callback)
}
Copy the code
index.html
<button onclick="onClickButton(this)">UI.confirm</button>
Copy the code
case "UI.confirm":
JSBridge.UI.alert({
title: 'Please confirm'.message: 'Do you know mingo? '.buttons: [
"Not sure"."Don't know"."Know"
]
}, (button) => {
JSBridge.UI.toast("Chose:" + button)
})
break
Copy the code
Running effect
If you select a button, toast will appear in the index of the button. That is, js will get the selected result of the original return