H5调用原生功能
封装一个统一的接口,供H5页面调用原生的功能,比如获取设备信息、打开相机、分享内容等。这样,H5页面只需调用这个接口,而无需关心具体的原生实现细节。
//定义一个类,用@JavascriptInterface注解来注释一个方法,这个方法就可以被H5直接调用
public class NativeAPI {@JavascriptInterfacepublic void getDeviceInfo() {// 假设获取设备信息的原生方法String deviceInfo = "Device: Android, Version: 11";// 将结果回调给H5页面String javascriptCode = "javascript:onDeviceInfoReceived('" + deviceInfo + "')";webView.loadUrl(javascriptCode);}
}// 添加 JavaScript 接口到 WebView 中webView.addJavascriptInterface(new NativeBridge(), "NativeBridge");
H5端调用:
function getDeviceInfo() {// 调用原生获取设备信息NativeAPI.getDeviceInfo();
}
原生调用H5
直接调用API:
String javascriptCode = "javascript:xxxx')";
webView.loadUrl(javascriptCode);
webView.evaluateJavascript("XXX")
原生回调H5
为了实现在 H5 页面中传递回调函数给原生代码,我们可以通过另外一种方式:使用随机生成的标识符来标记回调函数,并在原生代码中回调这个标识符对应的函数。
以下是修改后的示例代码,演示了在 H5 页面中调用复杂类型的 JavaScript 函数并传递回调函数标识符,然后在原生代码中通过标识符找到对应的回调函数并调用:
在 H5 页面中(例如 index.html):
<!DOCTYPE html>
<html>
<head><title>Complex Function Callback Example</title>
</head>
<body><h1>Complex Function Callback Example</h1><script>// 用于保存回调函数var callbacks = {};// 定义复杂的 JavaScript 函数,返回一个包含多个属性的对象function getComplexData() {var complexData = {name: "John Doe",age: 30,address: {city: "New York",country: "USA"},interests: ["Reading", "Traveling", "Coding"]};return complexData;}// 示例:调用一个复杂的 JavaScript 函数,并传递回调函数function triggerComplexFunctionWithCallback() {var complexData = getComplexData();// 生成一个随机标识符作为回调函数的标识var callbackId = "cb_" + new Date().getTime();// 将回调函数保存到 callbacks 对象中,用于在原生代码中调用callbacks[callbackId] = function(result) {// 在回调函数中处理原生传递回来的复杂类型数据console.log("Received complex data from Native: ", result);};// 调用原生方法,并传递回调函数标识符window.NativeBridge.onComplexFunctionWithCallbackResult(complexData, callbackId);}</script>
</body>
</html>
在 Android 原生代码中:
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONException;
import org.json.JSONObject;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);webView = findViewById(R.id.webView);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);// 添加 JavaScript 接口到 WebView 中webView.addJavascriptInterface(new NativeBridge(), "NativeBridge");webView.setWebChromeClient(new WebChromeClient());webView.loadUrl("file:///android_asset/index.html");}// 定义一个 JavaScript 接口类,供 H5 页面调用private class NativeBridge {@JavascriptInterfacepublic void onComplexFunctionWithCallbackResult(String jsonString, String callbackId) {// 在主线程中解析 JSON 字符串为复杂类型数据runOnUiThread(new Runnable() {@Overridepublic void run() {try {JSONObject json = new JSONObject(jsonString);String name = json.getString("name");int age = json.getInt("age");JSONObject address = json.getJSONObject("address");String city = address.getString("city");String country = address.getString("country");// 处理复杂类型数据// ...// 构造一个复杂类型数据的 JSON 字符串作为回调给 H5 页面String result = "{ \"message\": \"Received complex data from Native\" }";// 获取 H5 页面传递过来的回调函数标识符对应的回调函数String callbackJs = "callbacks['" + callbackId + "']";// 使用字符串拼接的方式将回调结果传递给 H5 页面的回调函数webView.evaluateJavascript(callbackJs + "(" + result + ")", null);} catch (JSONException e) {e.printStackTrace();}}});}}
}
在上述示例中,我们在 H5 页面中定义了一个 JavaScript 对象 callbacks 来保存回调函数,并使用一个随机标识符来作为回调函数的键。在 triggerComplexFunctionWithCallback() 方法中,我们将回调函数保存到 callbacks 对象中,并传递回调函数的标识符给原生代码。
在原生代码中的 NativeBridge.onComplexFunctionWithCallbackResult(String jsonString, String callbackId) 方法中,我们获取到回调函数标识符,并通过字符串拼接的方式构造出相应的回调函数调用语句,从而正确地执行了 H5 页面传递过来的回调函数。
事件派发
封装一个事件派发机制,让原生和H5页面可以通过发送和监听事件来实现双向通信。这样可以在不直接调用对方方法的情况下,进行通信和数据交换。
在 Hybrid 开发中,事件派发是指从原生代码向 H5 页面发送消息或通知,让 H5 页面可以感知到某个事件的发生,从而执行相应的处理逻辑。事件派发是一种在原生和 H5 之间进行双向通信的手段之一。
以下是一种简单的实现方式,演示了如何在原生代码中派发一个事件给 H5 页面:
在 H5 页面中(例如 index.html):
<!DOCTYPE html>
<html>
<head><title>Event Dispatch Example</title>
</head>
<body><h1>Event Dispatch Example</h1><script>// 监听自定义事件window.addEventListener("customEvent", function(event) {console.log("Received custom event:", event.detail);// 在这里可以执行相应的处理逻辑// ...});</script>
</body>
</html>
在 Android 原生代码中:
import android.os.Bundle;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);webView = findViewById(R.id.webView);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);webView.setWebChromeClient(new WebChromeClient());webView.loadUrl("file:///android_asset/index.html");// 派发事件到 H5 页面dispatchCustomEvent("Hello from Native!");}// 派发自定义事件到 H5 页面private void dispatchCustomEvent(String eventData) {// 使用 evaluateJavascript 方法调用 H5 页面中的 JavaScript 函数String script = "var event = new CustomEvent('customEvent', { detail: '" + eventData + "' });" +"window.dispatchEvent(event);";webView.evaluateJavascript(script, null);}
}
我们在 H5 页面中使用 window.addEventListener 方法来监听名为 “customEvent” 的自定义事件。然后,在原生代码中的 dispatchCustomEvent 方法中,我们使用 webView.evaluateJavascript 方法执行 JavaScript 代码,从而派发一个名为 “customEvent” 的自定义事件,并传递事件数据给 H5 页面。
当原生代码执行 dispatchCustomEvent(“Hello from Native!”) 后,H5 页面中的监听器会捕获到该事件,并执行相应的处理逻辑。
需要注意的是,事件派发时,我们可以自定义事件的类型和传递的数据,并通过 JavaScript 的 CustomEvent 构造函数来创建自定义事件对象。在这个例子中,我们通过 CustomEvent(‘customEvent’, { detail: eventData }) 创建了一个自定义事件,其中 eventData 就是我们要传递给 H5 页面的事件数据。在原生代码中,我们将 eventData 作为字符串传递给 H5 页面,但你也可以将更复杂的数据结构转换为 JSON 字符串,然后传递给 H5 页面进行处理。
错误处理
设计统一的错误处理机制,让原生和H5页面能够更好地处理错误情况,并向对方传递错误信息。例如,当某个功能不支持时,通过错误回调通知H5页面。
在 Hybrid 开发中,为了解决回调函数的问题,我们可以使用一种约定的方式,让原生代码返回一个标识符给 H5 端,表示回调函数的唯一标识。然后,H5 端将这个标识符保存起来,并在需要的时候调用原生代码提供的另一个方法,传递这个标识符和需要回调的数据。原生代码根据标识符找到对应的回调函数,并执行回调处理。
以下是一个重新设计的例子,演示了如何在 Hybrid 开发中实现回调函数的传递和调用:
在 H5 页面中(例如 index.html):
<!DOCTYPE html>
<html>
<head><title>Error Handling Example</title>
</head>
<body><h1>Error Handling Example</h1><script>// 保存回调函数的对象var callbacks = {};// 定义一个处理错误的回调函数function onError(errorMessage) {console.error("Received error message from Native:", errorMessage);// 在这里可以执行相应的错误处理逻辑// ...}// 定义一个注册回调函数的方法,返回一个唯一的标识符function registerCallback(callback) {var callbackId = "cb_" + new Date().getTime();callbacks[callbackId] = callback;return callbackId;}// 示例:调用原生方法,处理可能出现的错误function callNativeMethod() {// 注册回调函数,获取回调函数的标识符var callbackId = registerCallback(function(result) {// 检查 result 是否包含 error 字段if (result.error) {// 如果 result 包含 error 字段,则触发错误处理回调onError(result.error);} else {// 否则,继续处理正常的结果console.log("Received result from Native:", result);// 在这里可以执行正常的处理逻辑// ...}});// 调用原生方法,并传递回调函数的标识符window.NativeBridge.someFunction(callbackId);}</script>
</body>
</html>
在 Android 原生代码中:
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONException;
import org.json.JSONObject;public class MainActivity extends AppCompatActivity {private WebView webView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);webView = findViewById(R.id.webView);WebSettings webSettings = webView.getSettings();webSettings.setJavaScriptEnabled(true);// 添加 JavaScript 接口到 WebView 中webView.addJavascriptInterface(new NativeBridge(), "NativeBridge");webView.setWebChromeClient(new WebChromeClient());webView.loadUrl("file:///android_asset/index.html");}// 定义一个 JavaScript 接口类,供 H5 页面调用private class NativeBridge {@JavascriptInterfacepublic void someFunction(String callbackId) {try {// 假设这里出现了一个错误,我们模拟一个包含错误信息的 JSON 对象JSONObject errorObject = new JSONObject();errorObject.put("error", "Something went wrong!");// 获取回调函数的标识符对应的回调函数String callbackJs = "callbacks['" + callbackId + "']";// 使用字符串拼接的方式将错误信息传递给 H5 页面的回调函数webView.evaluateJavascript(callbackJs + "(" + errorObject.toString() + ")", null);} catch (JSONException e) {e.printStackTrace();}}}
}
桥接封装
将原生代码和H5交互的桥接代码进行封装,使其更易用、更健壮,避免直接在业务逻辑中直接操作桥接代码,从而降低耦合性。
对于桥接代码,可以封装一个BridgeHelper类,用于处理原生和H5交互的细节,使得在业务逻辑中不直接操作桥接代码。
public class BridgeHelper {private WebView webView;public BridgeHelper(WebView webView) {this.webView = webView;}// 封装调用原生功能的方法public void callNativeFunction(String functionName) {// 构建JavaScript代码String javascriptCode = "javascript:" + functionName + "()";// 调用原生方法webView.loadUrl(javascriptCode);}
}
//其他地方调用
BridgeHelper bridgeHelper = new BridgeHelper(webView);
bridgeHelper.callNativeFunction("someNativeFunction");
安全处理
考虑安全性问题,确保对于原生和H5之间的通信,只暴露必要的接口,并对传递的数据进行验证和过滤,以防止潜在的安全风险。
在封装的API中,对于接收的参数进行验证,确保数据的有效性和安全性。
H5代码:
function sendDataToNative(data) {// 对数据进行验证,确保不为空且符合要求if (data !== null && typeof data === 'object') {// 调用原生方法并传递数据NativeAPI.sendData(data);} else {console.error("Invalid data format!");}
}
版本兼容
针对不同的原生和H5版本,可以进行一些兼容性处理,确保在不同平台和环境下都能正常运行。
调试日志
在桥接代码中添加调试日志,以便在开发和测试过程中更容易发现问题,对于某些复杂的交互,可以在日志中打印交互数据,方便排查问题。