Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,44 @@ For example:

will print 'JS got a message hello' and 'JS responding with' in webview console.

### Persistent Callbacks (New Feature)

By default, callbacks are deleted after first use. However, you can now use persistent callbacks that can be reused multiple times:

#### Java Side

```java
// Use persistent callback that won't be deleted after first use
webView.callHandlerPersistent("functionInJs", data, new OnBridgeCallback() {
@Override
public void onCallBack(String data) {
// This callback can be called multiple times
Log.d(TAG, "Persistent callback called: " + data);
}
});
```

#### JavaScript Side

```javascript
// Use persistent callback
WebViewJavascriptBridge.callHandlerPersistent("javaHandler", data, function(response) {
// This callback can be reused multiple times
console.log("Persistent callback response: " + response);
});

// Register and manually manage persistent callbacks
var callbackId = "my_persistent_callback";
WebViewJavascriptBridge.registerPersistentCallback(callbackId, function(data) {
console.log("Persistent callback called: " + data);
});

// Remove persistent callback when no longer needed
WebViewJavascriptBridge.removePersistentCallback(callbackId);
```

This feature is useful when you need to maintain a long-term communication channel between Java and JavaScript, such as for real-time updates or event notifications.

### Switch to CustomWebView
* activity_main.xml
```xml
Expand Down
114 changes: 114 additions & 0 deletions example/src/main/assets/persistent_callback_demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
<title>Persistent Callback Demo</title>
</head>

<body>
<h2>Persistent Callback Demo</h2>
<p>This demo shows how to use persistent callbacks that can be reused multiple times.</p>

<div id="log" style="border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; margin: 10px 0;"></div>

<p>
<input type="button" id="testPersistent" value="Test Persistent Callback" onclick="testPersistentCallback();" />
</p>
<p>
<input type="button" id="reuseCached" value="Reuse Cached Callback" onclick="reuseCachedCallback();" />
</p>
<p>
<input type="button" id="testNormal" value="Test Normal Callback" onclick="testNormalCallback();" />
</p>
<p>
<input type="button" id="clearLog" value="Clear Log" onclick="clearLog();" />
</p>

<script>
var cachedCallback = null;
var callCount = 0;

function log(message) {
var logDiv = document.getElementById('log');
var timestamp = new Date().toLocaleTimeString();
logDiv.innerHTML += '[' + timestamp + '] ' + message + '<br>';
logDiv.scrollTop = logDiv.scrollHeight;
console.log(message);
}

function clearLog() {
document.getElementById('log').innerHTML = '';
}

function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge && WebViewJavascriptBridge.inited) {
callback(WebViewJavascriptBridge);
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function() {
callback(WebViewJavascriptBridge);
},
false
);
}
}

connectWebViewJavascriptBridge(function(bridge) {
bridge.init(function(message, responseCallback) {
log('Bridge initialized');
});

// Register handler for persistent callback testing
bridge.registerHandler("persistentCallbackTest", function(data, responseCallback) {
log('Handler called with data: ' + data);

// Cache the callback for reuse
cachedCallback = responseCallback;

// Respond immediately
if (responseCallback) {
responseCallback("Initial response from handler");
log('Sent initial response');
}
});

log('Bridge ready and handlers registered');
});

function testPersistentCallback() {
log('Testing persistent callback...');

// Use the new persistent callback method
WebViewJavascriptBridge.callHandlerPersistent('persistentCallbackTest', 'test data for persistent callback', function(response) {
callCount++;
log('Received response #' + callCount + ': ' + response);
});
}

function reuseCachedCallback() {
if (cachedCallback) {
try {
callCount++;
cachedCallback("Reused callback response #" + callCount);
log('Successfully reused cached callback #' + callCount);
} catch (error) {
log('Error reusing cached callback: ' + error.message);
}
} else {
log('No cached callback available. Call "Test Persistent Callback" first.');
}
}

function testNormalCallback() {
log('Testing normal callback...');

// Use normal callback (should be deleted after first use)
WebViewJavascriptBridge.callHandler('persistentCallbackTest', 'test data for normal callback', function(response) {
log('Received normal callback response: ' + response);
});
}
</script>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathC
}
});

webView.addJavascriptInterface(new MainJavascriptInterface(webView.getCallbacks(), webView), "WebViewJavascriptBridge");
webView.addJavascriptInterface(new MainJavascriptInterface(webView.getCallbacks(), webView.getPersistentCallbacks(), webView), "WebViewJavascriptBridge");
webView.setGson(new Gson());
webView.loadUrl("file:///android_asset/demo.html");
User user = new User();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks, WebViewJ
mWebView = webView;
}

public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks, Map<String, OnBridgeCallback> persistentCallbacks, WebViewJavascriptBridge webView) {
super(callbacks, persistentCallbacks);
mWebView = webView;
}

public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks) {
super(callbacks);
}
Expand Down
8 changes: 8 additions & 0 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.code.gson:gson:2.8.5'

// Test dependencies
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.12.4'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.4'
}


Expand Down
45 changes: 40 additions & 5 deletions library/src/main/assets/WebViewJavascriptBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
var sendMessageQueue = [];

var responseCallbacks = {};
var persistentCallbacks = {};
var uniqueId = 1;

var lastCallTime = 0;
Expand Down Expand Up @@ -63,24 +64,48 @@
delete messageHandlers[handlerName];
}

// Register a persistent callback that won't be deleted after first use
function registerPersistentCallback(callbackId, callback) {
persistentCallbacks[callbackId] = callback;
responseCallbacks[callbackId] = callback;
}

// Remove a persistent callback
function removePersistentCallback(callbackId) {
delete persistentCallbacks[callbackId];
delete responseCallbacks[callbackId];
}

// 调用线程
function callHandler(handlerName, data, responseCallback) {
function callHandler(handlerName, data, responseCallback, persistent) {
// 如果方法不需要参数,只有回调函数,简化JS中的调用
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend(handlerName, data, responseCallback);
_doSend(handlerName, data, responseCallback, persistent);
}

// Call handler with persistent callback that can be reused
function callHandlerPersistent(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend(handlerName, data, responseCallback, true);
}

//sendMessage add message, 触发native处理 sendMessage
function _doSend(handlerName, message, responseCallback) {
function _doSend(handlerName, message, responseCallback, persistent) {
var callbackId;
if(typeof responseCallback === 'string'){
callbackId = responseCallback;
} else if (responseCallback) {
callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
if (persistent) {
persistentCallbacks[callbackId] = responseCallback;
}
message.callbackId = callbackId;
}else{
callbackId = '';
Expand All @@ -98,7 +123,10 @@
return;
}
responseCallback(responseData);
delete responseCallbacks[callbackId];
// Only delete if it's not a persistent callback
if (!persistentCallbacks[callbackId]) {
delete responseCallbacks[callbackId];
}
}
}

Expand Down Expand Up @@ -141,7 +169,10 @@
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
// Only delete if it's not a persistent callback
if (!persistentCallbacks[message.responseId]) {
delete responseCallbacks[message.responseId];
}
} else {
//直接发送
if (message.callbackId) {
Expand Down Expand Up @@ -179,7 +210,11 @@
WebViewJavascriptBridge.init = init;
WebViewJavascriptBridge.doSend = send;
WebViewJavascriptBridge.registerHandler = registerHandler;
WebViewJavascriptBridge.removeHandler = removeHandler;
WebViewJavascriptBridge.callHandler = callHandler;
WebViewJavascriptBridge.callHandlerPersistent = callHandlerPersistent;
WebViewJavascriptBridge.registerPersistentCallback = registerPersistentCallback;
WebViewJavascriptBridge.removePersistentCallback = removePersistentCallback;
WebViewJavascriptBridge._handleMessageFromNative = _handleMessageFromNative;
WebViewJavascriptBridge._fetchQueue = _fetchQueue;

Expand Down
Loading