H5调用原生功能
封装一个统一的接口,供H5页面调用原生的功能,比如获取设备信息、打开相机、分享内容等。这样,H5页面只需调用这个接口,而无需关心具体的原生实现细节。
//定义一个类,用@JavascriptInterface注解来注释一个方法,这个方法就可以被H5直接调用
public class NativeAPI {
@JavascriptInterface
public 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;
@Override
protected 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 {
@JavascriptInterface
public void onComplexFunctionWithCallbackResult(String jsonString, String callbackId) {
// 在主线程中解析 JSON 字符串为复杂类型数据
runOnUiThread(new Runnable() {
@Override
public 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;
@Override
protected 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;
@Override
protected 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 {
@JavascriptInterface
public 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版本,可以进行一些兼容性处理,确保在不同平台和环境下都能正常运行。
调试日志
在桥接代码中添加调试日志,以便在开发和测试过程中更容易发现问题,对于某些复杂的交互,可以在日志中打印交互数据,方便排查问题。