Full Code of happydog-intj/JsBridge for AI

master 78291ba35bb2 cached
41 files
91.0 KB
22.2k tokens
168 symbols
1 requests
Download .txt
Repository: happydog-intj/JsBridge
Branch: master
Commit: 78291ba35bb2
Files: 41
Total size: 91.0 KB

Directory structure:
gitextract_ae0068gj/

├── .github/
│   └── workflows/
│       └── android.yml
├── .gitignore
├── README.md
├── build.gradle
├── example/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   ├── demo.html
│           │   └── persistent_callback_demo.html
│           ├── java/
│           │   └── com/
│           │       └── github/
│           │           └── lzyzsd/
│           │               └── jsbridge/
│           │                   └── example/
│           │                       ├── CustomWebView.java
│           │                       ├── MainActivity.java
│           │                       └── MainJavascriptInterface.java
│           └── res/
│               ├── layout/
│               │   └── activity_main.xml
│               └── values/
│                   ├── strings.xml
│                   └── styles.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── assets/
│       │   │   └── WebViewJavascriptBridge.js
│       │   └── java/
│       │       └── com/
│       │           └── github/
│       │               └── lzyzsd/
│       │                   └── jsbridge/
│       │                       ├── BridgeHandler.java
│       │                       ├── BridgeHelper.java
│       │                       ├── BridgeUtil.java
│       │                       ├── BridgeWebView.java
│       │                       ├── BridgeWebViewClient.java
│       │                       ├── DefaultHandler.java
│       │                       ├── IWebView.java
│       │                       ├── JSRequest.java
│       │                       ├── JSResponse.java
│       │                       ├── Message.java
│       │                       ├── OnBridgeCallback.java
│       │                       └── WebViewJavascriptBridge.java
│       └── test/
│           ├── assets/
│           │   └── test_persistent_callback.html
│           └── java/
│               └── com/
│                   └── github/
│                       └── lzyzsd/
│                           └── jsbridge/
│                               └── PersistentCallbackTest.java
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/android.yml
================================================
name: Android CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8
    - name: Build with Gradle
      


================================================
FILE: .gitignore
================================================
#built application files
*.apk
*.ap_


# files for the dex VM
*.dex


# Java class files
*.class


# generated files
bin/
gen/


# Local configuration file (sdk path, etc)
local.properties


# Windows thumbnail db
Thumbs.db


# OSX files
.DS_Store


# Eclipse project files
.classpath
.project


# Android Studio
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/


# Signing files
.signing/


# User-specific configurations
.idea/libraries/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
*.iml


================================================
FILE: README.md
================================================
# JsBridge

-----

Inspired and modified from [this](https://github.com/jacin1/JsBridge) and WeChat jsBridge file, with some bug fixes and feature enhancements.

This project makes a bridge between Java and JavaScript.

It provides a safe and convenient way to call Java code from JavaScript and call JavaScript code from Java.

## How JsBridge Works
![JsBridge](./JsBridgeWork.png)

## Demo
![JsBridge Demo](https://raw.githubusercontent.com/lzyzsd/JsBridge/master/JsBridge.gif)

## Usage

## JitPack.io

I strongly recommend [JitPack.io](https://jitpack.io)

```groovy
repositories {
    // ...
    maven { url "https://jitpack.io" }
}

dependencies {
    compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
```

## Use it in Java

Add `com.github.lzyzsd.jsbridge.BridgeWebView` to your layout, it is inherited from WebView.

### Register a Java handler function so that JavaScript can call

```java

    webView.registerHandler("submitFromWeb", new BridgeHandler() {
        @Override
        public void handler(String data, CallBackFunction function) {
            Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
            function.onCallBack("submitFromWeb exe, response data from Java");
        }
    });

```

JavaScript can call this Java handler method "submitFromWeb" through:

```javascript

    WebViewJavascriptBridge.callHandler(
        'submitFromWeb'
        , {'param': str1}
        , function(responseData) {
            document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
        }
    );

```

You can set a default handler in Java, so that JavaScript can send messages to Java without an assigned handlerName

```java

    webView.setDefaultHandler(new DefaultHandler());

```

```javascript

    window.WebViewJavascriptBridge.doSend(
        data
        , function(responseData) {
            document.getElementById("show").innerHTML = "responseData from java, data = " + responseData
        }
    );

```

### Register a JavaScript handler function so that Java can call

```javascript

    WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
        document.getElementById("show").innerHTML = ("data from Java: = " + data);
        var responseData = "Javascript Says Right back aka!";
        responseCallback(responseData);
    });

```

Java can call this JavaScript handler function "functionInJs" through:

```java

    webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
        @Override
        public void onCallBack(String data) {

        }
    });

```
You can also define a default handler using the init method, so that Java can send messages to JavaScript without an assigned handlerName

For example:

```javascript

    window.WebViewJavascriptBridge.init(function(message, responseCallback) {
        console.log('JS got a message', message);
        var data = {
            'Javascript Responds': 'Wee!'
        };
        console.log('JS responding with', data);
        responseCallback(data);
    });

```

```java
    webView.send("hello");
```

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
    <com.github.lzyzsd.jsbridge.example.CustomWebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
     </com.github.lzyzsd.jsbridge.example.CustomWebView>
```
* MainActivity.java
Change BridgeWebView class to CustomWebView:
```java
    CustomWebView webView = (CustomWebView) findViewById(R.id.webView);
    
```

## Notice

This library will inject a WebViewJavascriptBridge Object to the window object.
You can listen to the `WebViewJavascriptBridgeReady` event to ensure `window.WebViewJavascriptBridge` exists, as the below code shows:

```javascript

    if (window.WebViewJavascriptBridge) {
        //do your work here
    } else {
        document.addEventListener(
            'WebViewJavascriptBridgeReady'
            , function() {
                //do your work here
            },
            false
        );
    }

```

Or put all JsBridge function call into `window.WVJBCallbacks` array if `window.WebViewJavascriptBridge` is undefined, this task queue will be flushed when `WebViewJavascriptBridgeReady` event triggered.

Copy and paste setupWebViewJavascriptBridge into your JS:

```javascript
function setupWebViewJavascriptBridge(callback) {
	if (window.WebViewJavascriptBridge) {
        return callback(WebViewJavascriptBridge);
    }
	if (window.WVJBCallbacks) {
        return window.WVJBCallbacks.push(callback);
    }
	window.WVJBCallbacks = [callback];
}
```

Call `setupWebViewJavascriptBridge` and then use the bridge to register handlers or call Java handlers:

```javascript
setupWebViewJavascriptBridge(function(bridge) {
	bridge.registerHandler('JS Echo', function(data, responseCallback) {
		console.log("JS Echo called with:", data);
		responseCallback(data);
    });
	bridge.callHandler('ObjC Echo', {'key':'value'}, function(responseData) {
		console.log("JS received response:", responseData);
	});
});
```

It's the same as [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge), which makes it easier for you to define the same behavior across different platforms between Android and iOS, while writing concise code.

## License

This project is licensed under the terms of the MIT license.


================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {

        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.3'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {

        jcenter()
        maven {
            url "https://maven.google.com"
        }
        google()
    }
    tasks.withType(Javadoc) { // 这一段是为了消除gbk的错�?
        options{
            encoding "UTF-8"
            charSet 'UTF-8'
            links "http://docs.oracle.com/javase/7/docs/api"
        }
    }
}


================================================
FILE: example/.gitignore
================================================
/build


================================================
FILE: example/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
//    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.github.lzyzsd.jsbridge.example"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(':library')
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.code.gson:gson:2.8.5'
}


================================================
FILE: example/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/bruce/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: example/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.github.lzyzsd.jsbridge.example">

    <uses-permission android:name="android.permission.INTERNET" />

    <application android:allowBackup="true"
                 android:label="@string/app_name"
                 android:icon="@mipmap/ic_launcher"
                 android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


================================================
FILE: example/src/main/assets/demo.html
================================================
<html>
<head>
    <meta content="text/html; charset=utf-8" http-equiv="content-type">
    <title>
        js调用java
    </title>
</head>

<body>
<p>
    <xmp id="show">
    </xmp>
</p>
<p>
    <xmp id="init">
    </xmp>
</p>
<p>
    <input type="text" id="text1" value="用户名(username)"/>
</p>
<p>
    <input type="text" id="text2" value="password"/>
</p>
<p>
    <input type="button" id="enter" value="发消息给Native" onclick="testClick();"
    />
</p>
<p>
    <input type="button" id="enter1" value="调用Native方法" onclick="testClick1();"
    />
</p>
<p>
    <input type="button" id="enter2" value="显示html" onclick="testDiv();"/>
</p>
<p>
    <input type="file" value="打开文件"/>
</p>
</body>
<script>
        function testDiv() {
            document.getElementById("show").innerHTML = document.getElementsByTagName("html")[0].innerHTML;
        }

        function testClick() {
            var str1 = document.getElementById("text1").value;
            var str2 = document.getElementById("text2").value;

            //send message to native
            var data = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};
            WebViewJavascriptBridge.doSend(
                data
                , function(responseData) {
                    document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
                }
            );

        }

        function testClick1() {
            var str1 = document.getElementById("text1").value;
            var str2 = document.getElementById("text2").value;

            //call native method
            window.WebViewJavascriptBridge.callHandler(
                'submitFromWeb'
                , {'param': '中文测试'}
                , function(responseData) {
                    document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
                }
            );
        }

        function bridgeLog(logContent) {
            document.getElementById("show").innerHTML = logContent;
        }

        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) {
                console.log('JS got a message', message);
                var data = {
                    'Javascript Responds': '测试中文!'
                };

                if (responseCallback) {
                    console.log('JS responding with', data);
                    responseCallback(data);
                }
            });

            bridge.registerHandler("functionInJs", function(data, responseCallback) {
                document.getElementById("show").innerHTML = ("data from Java: = " + data + Date.now());
                if (responseCallback) {
                    var responseData = "Javascript Says Right back aka!";
                    responseCallback(responseData);
                }
            });
        })

</script>

</html>



================================================
FILE: example/src/main/assets/persistent_callback_demo.html
================================================
<!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>

================================================
FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java
================================================
package com.github.lzyzsd.jsbridge.example;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.github.lzyzsd.jsbridge.BridgeHandler;
import com.github.lzyzsd.jsbridge.BridgeHelper;
import com.github.lzyzsd.jsbridge.IWebView;
import com.github.lzyzsd.jsbridge.OnBridgeCallback;
import com.github.lzyzsd.jsbridge.WebViewJavascriptBridge;
import com.google.gson.Gson;

import java.util.Map;

/**
 * 采用BridgeHelper集成JsBridge功能示例.定制WebView,可只添加实际需要的JsBridge接口.
 *
 * @author ZhengAn
 * @date 2019-07-07
 */
@SuppressLint("SetJavaScriptEnabled")
public class CustomWebView extends WebView implements WebViewJavascriptBridge, IWebView {

    private BridgeHelper bridgeHelper;

    private Gson mGson;

    public CustomWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CustomWebView(Context context) {
        super(context);
        init();
    }

    private void init() {
        this.setVerticalScrollBarEnabled(false);
        this.setHorizontalScrollBarEnabled(false);
        this.getSettings().setJavaScriptEnabled(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        bridgeHelper = new BridgeHelper(this);
        this.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView webView, String s) {
                bridgeHelper.onPageFinished();
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView webView, String s) {
                return bridgeHelper.shouldOverrideUrlLoading(s);
            }
        });
    }

    /**
     * @param handler default handler,handle messages send by js without assigned handler name,
     *                if js message has handler name, it will be handled by named handlers registered by native
     */
    public void setDefaultHandler(BridgeHandler handler) {
        bridgeHelper.setDefaultHandler(handler);
    }

    /**
     * register handler,so that javascript can call it
     * 注册处理程序,以便javascript调用它
     *
     * @param handlerName handlerName
     * @param handler     BridgeHandler
     */
    public void registerHandler(String handlerName, BridgeHandler handler) {
        bridgeHelper.registerHandler(handlerName, handler);
    }

    /**
     * unregister handler
     *
     * @param handlerName
     */
    public void unregisterHandler(String handlerName) {
        bridgeHelper.unregisterHandler(handlerName);
    }

    /**
     * call javascript registered handler
     * 调用javascript处理程序注册
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    CallBackFunction
     */
    public void callHandler(String handlerName, String data, OnBridgeCallback callBack) {
        bridgeHelper.callHandler(handlerName, data, callBack);
    }

    @Override
    public void sendToWeb(String data) {
        sendToWeb(data, (OnBridgeCallback) null);
    }

    @Override
    public void sendToWeb(String data, OnBridgeCallback responseCallback) {
        bridgeHelper.sendToWeb(data, responseCallback);
    }

    @Override
    public void sendToWeb(String function, Object... values) {
        bridgeHelper.sendToWeb(function, values);
    }

    @Override
    public void responseFromWeb(String data, String callbackId) {
        bridgeHelper.responseFromWeb(data,callbackId);
    }

    public void setGson(Gson gson){
        this.mGson = gson;
    }

    public Map<String, OnBridgeCallback> getCallbacks() {
        return bridgeHelper.getCallbacks();
    }

    @Override
    public WebView getWebView() {
        return this;
    }
}


================================================
FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java
================================================
package com.github.lzyzsd.jsbridge.example;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.Button;

import com.github.lzyzsd.jsbridge.BridgeWebView;
import com.github.lzyzsd.jsbridge.OnBridgeCallback;
import com.google.gson.Gson;

public class MainActivity extends Activity implements OnClickListener {

	private final String TAG = "MainActivity";

	BridgeWebView webView;

	Button button;

	int RESULT_CODE = 0;

	ValueCallback<Uri> mUploadMessage;

	ValueCallback<Uri[]> mUploadMessageArray;

    static class Location {
        String address;
    }

    static class User {
        String name;
        Location location;
        String testStr;
    }

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

        webView = (BridgeWebView) findViewById(R.id.webView);

		button = (Button) findViewById(R.id.button);

		button.setOnClickListener(this);


		webView.setWebChromeClient(new WebChromeClient() {

			@SuppressWarnings("unused")
			public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType, String capture) {
				this.openFileChooser(uploadMsg);
			}

			@SuppressWarnings("unused")
			public void openFileChooser(ValueCallback<Uri> uploadMsg, String AcceptType) {
				this.openFileChooser(uploadMsg);
			}

			public void openFileChooser(ValueCallback<Uri> uploadMsg) {
				mUploadMessage = uploadMsg;
				pickFile();
			}

			@Override
			public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
				mUploadMessageArray = filePathCallback;
				pickFile();
				return true;
			}
		});

		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();
        Location location = new Location();
        location.address = "SDU";
        user.location = location;
        user.name = "大头鬼";

        webView.callHandler("functionInJs", new Gson().toJson(user), new OnBridgeCallback() {
            @Override
            public void onCallBack(String data) {
				Log.d(TAG, "onCallBack: " + data);
            }
        });

        webView.sendToWeb("hello");

	}

	public void pickFile() {
		Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
		chooserIntent.setType("image/*");
		startActivityForResult(chooserIntent, RESULT_CODE);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
		if (requestCode == RESULT_CODE) {
			if (null == mUploadMessage && null == mUploadMessageArray){
				return;
			}
			if(null!= mUploadMessage && null == mUploadMessageArray){
				Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
				mUploadMessage.onReceiveValue(result);
				mUploadMessage = null;
			}

			if(null == mUploadMessage && null != mUploadMessageArray){
				Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
				if (result != null) {
					mUploadMessageArray.onReceiveValue(new Uri[]{result});
				}
				mUploadMessageArray = null;
			}

		}
	}

	@Override
	public void onClick(View v) {
		if (button.equals(v)) {
            webView.callHandler("functionInJs", "data from Java", new OnBridgeCallback() {

				@Override
				public void onCallBack(String data) {
					// TODO Auto-generated method stub
					Log.i(TAG, "reponse data from js " + data);
				}

			});
		}

	}

}


================================================
FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascriptInterface.java
================================================
package com.github.lzyzsd.jsbridge.example;

import android.util.Log;
import android.webkit.JavascriptInterface;

import com.github.lzyzsd.jsbridge.BridgeWebView;
import com.github.lzyzsd.jsbridge.OnBridgeCallback;
import com.github.lzyzsd.jsbridge.WebViewJavascriptBridge;

import java.util.Map;

/**
 * Created on 2019/7/10.
 * Author: bigwang
 * Description:
 */
public class MainJavascriptInterface extends BridgeWebView.BaseJavascriptInterface {

    //WebJSbridge
    private WebViewJavascriptBridge mWebView;

    public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks, WebViewJavascriptBridge webView) {
        super(callbacks);
        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);
    }

    @Override
    public String send(String data) {
        return "it is default response";
    }


    @JavascriptInterface
    public void submitFromWeb(String data, String callbackId) {
        Log.d("MainJavascriptInterface", data + ", callbackId: " + callbackId + " " + Thread.currentThread().getName());
        mWebView.responseFromWeb("submitFromWeb response", callbackId);
    }
}


================================================
FILE: example/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- button 演示Java调用web -->
    <Button 
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:text="@string/button_name"
        android:layout_height="48dp"
        />
    
    <!-- webview 演示web调用Java -->
    <com.github.lzyzsd.jsbridge.BridgeWebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
     </com.github.lzyzsd.jsbridge.BridgeWebView>

</LinearLayout>


================================================
FILE: example/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">JsBridge</string>
    <string name="button_name">Java调用Web</string>
</resources>


================================================
FILE: example/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Wed Mar 24 16:24:00 CST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip


================================================
FILE: gradle.properties
================================================
## Project-wide Gradle settings.
#
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Thu Jul 27 13:52:48 CST 2017
#systemProp.https.proxyPort=1080
#systemProp.http.proxyHost=127.0.0.1
#systemProp.https.proxyHost=127.0.0.1
#systemProp.http.proxyPort=1080
android.enableJetifier=true
android.useAndroidX=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: library/.gitignore
================================================
/build


================================================
FILE: library/build.gradle
================================================
apply plugin: 'com.android.library'

version = "1.0.0"

android {
    compileSdkVersion 28
//    buildToolsVersion "25.0.3"

    defaultConfig {
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 1
        versionName version
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions { // 消除lint警告
        abortOnError false
        checkReleaseBuilds false
    }
}

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'
}


def siteUrl = 'https://github.com/lzyzsd/JsBridge'
def gitUrl = 'https://github.com/lzyzsd/JsBridge.git'
// apply plugin: 'com.github.dcendents.android-maven'
group = "com.github.lzyzsd.jsbridge"
task sourcesJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier = 'sources'
}
task javadoc(type: Javadoc) {
    source = android.sourceSets.main.java.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}
task javadocJar(type: Jar, dependsOn: javadoc) {
    classifier = 'javadoc'
    from javadoc.destinationDir
}
artifacts {
    archives javadocJar
    archives sourcesJar
}
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())


================================================
FILE: library/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/bruce/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.github.lzyzsd.library">

    <application android:allowBackup="true"
                 android:label="@string/app_name"
        >

    </application>

</manifest>


================================================
FILE: library/src/main/assets/WebViewJavascriptBridge.js
================================================
//notation: js file can only use this kind of comments
//since comments will cause error when use in webview.loadurl,
//comments will be remove by java use regexp
(function() {
    if (window.WebViewJavascriptBridge && window.WebViewJavascriptBridge.inited) {
        return;
    }

    var receiveMessageQueue = [];
    var messageHandlers = {};
    var sendMessageQueue = [];

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

    var lastCallTime = 0;
    var stoId = null;
    var FETCH_QUEUE_INTERVAL = 20;
    var messagingIframe;
    var CUSTOM_PROTOCOL_SCHEME = "yy";
    var QUEUE_HAS_MESSAGE = "__QUEUE_MESSAGE__";

    // 创建消息index队列iframe
    function _createQueueReadyIframe() {
        messagingIframe = document.createElement('iframe');
        messagingIframe.style.display = 'none';
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        document.documentElement.appendChild(messagingIframe);
    }
    //创建消息体队列iframe
    function _createQueueReadyIframe4biz() {
        bizMessagingIframe = document.createElement('iframe');
        bizMessagingIframe.style.display = 'none';
        document.documentElement.appendChild(bizMessagingIframe);
    }
    //set default messageHandler  初始化默认的消息线程
    function init(messageHandler) {
        if (WebViewJavascriptBridge._messageHandler) {
            throw new Error('WebViewJavascriptBridge.init called twice');
        }
        _createQueueReadyIframe();
        _createQueueReadyIframe4biz();
        WebViewJavascriptBridge._messageHandler = messageHandler;
        var receivedMessages = receiveMessageQueue;
        receiveMessageQueue = null;
        for (var i = 0; i < receivedMessages.length; i++) {
            _dispatchMessageFromNative(receivedMessages[i]);
        }
        WebViewJavascriptBridge.inited = true;
    }

    // 发送
    function send(data, responseCallback) {
        _doSend('send', data, responseCallback);
    }

    // 注册线程 往数组里面添加值
    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }

    function removeHandler(handlerName, handler) {
        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, persistent) {
        // 如果方法不需要参数,只有回调函数,简化JS中的调用
        if (arguments.length == 2 && typeof data == 'function') {
			responseCallback = data;
			data = null;
		}
        _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, 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 = '';
        }
        try {
             var fn = eval('WebViewJavascriptBridge.' + handlerName);
         } catch(e) {
             console.log(e);
         }
         if (typeof fn === 'function'){
             var responseData = fn.call(WebViewJavascriptBridge, JSON.stringify(message), callbackId);
             if(responseData){
                 responseCallback = responseCallbacks[callbackId];
                 if (!responseCallback) {
                     return;
                  }
                 responseCallback(responseData);
                 // Only delete if it's not a persistent callback
                 if (!persistentCallbacks[callbackId]) {
                     delete responseCallbacks[callbackId];
                 }
             }
         }

        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

    // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
    function _fetchQueue() {
        // 空数组直接返回
        if (sendMessageQueue.length === 0) {
          return;
        }

        // _fetchQueue 的调用间隔过短,延迟调用
        if (new Date().getTime() - lastCallTime < FETCH_QUEUE_INTERVAL) {
          if (!stoId) {
            stoId = setTimeout(_fetchQueue, FETCH_QUEUE_INTERVAL);
          }
          return;
        }

        lastCallTime = new Date().getTime();
        stoId = null;
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        //android can't read directly the return data, so we can reload iframe src to communicate with java
        bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
    }

    //提供给native使用,
    function _dispatchMessageFromNative(messageJSON) {
        setTimeout(function() {
            var message = JSON.parse(messageJSON);
            var responseCallback;
            //java call finished, now need to call js callback function
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                // Only delete if it's not a persistent callback
                if (!persistentCallbacks[message.responseId]) {
                    delete responseCallbacks[message.responseId];
                }
            } else {
                //直接发送
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend('response', responseData, callbackResponseId);
                    };
                }

                var handler = WebViewJavascriptBridge._messageHandler;
                if (message.handlerName) {
                    handler = messageHandlers[message.handlerName];
                }
                //查找指定handler
                try {
                    handler(message.data, responseCallback);
                } catch (exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
                    }
                }
            }
        });
    }

    //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
    function _handleMessageFromNative(messageJSON) {
        if (receiveMessageQueue) {
            receiveMessageQueue.push(messageJSON);
        }
        _dispatchMessageFromNative(messageJSON);

    }

    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;

    var readyEvent = document.createEvent('Events');
    var jobs = window.WVJBCallbacks || [];
    readyEvent.initEvent('WebViewJavascriptBridgeReady');
    readyEvent.bridge = WebViewJavascriptBridge;
    window.WVJBCallbacks = [];
    jobs.forEach(function (job) {
        job(WebViewJavascriptBridge)
    });
    document.dispatchEvent(readyEvent);
})();


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java
================================================
package com.github.lzyzsd.jsbridge;

public interface BridgeHandler {
    void handler(String data, OnBridgeCallback callBackFunction);
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java
================================================
package com.github.lzyzsd.jsbridge;

import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;

import com.google.gson.Gson;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JsBridge辅助类,帮助集成JsBridge功能.
 *
 * @author ZhengAn
 * @date 2019-06-30
 */
public class BridgeHelper implements WebViewJavascriptBridge {

    private static final String TAG = "BridgeHelper";

    private static final String BRIDGE_JS = "WebViewJavascriptBridge.js";
    private Map<String, OnBridgeCallback> responseCallbacks = new HashMap<>();
    private Map<String, BridgeHandler> messageHandlers = new HashMap<>();
    private BridgeHandler defaultHandler = new DefaultHandler();

    private List<Message> startupMessage = new ArrayList<>();

    private List<Message> getStartupMessage() {
        return startupMessage;
    }

    private void setStartupMessage(List<Message> startupMessage) {
        this.startupMessage = startupMessage;
    }

    private long uniqueId = 0;

    private IWebView webView;

    public BridgeHelper(IWebView webView) {
        this.webView = webView;
    }

    /**
     * @param handler default handler,handle messages send by js without assigned handler name,
     *                if js message has handler name, it will be handled by named handlers registered by native
     */
    public void setDefaultHandler(BridgeHandler handler) {
        this.defaultHandler = handler;
    }

    /**
     * 获取到CallBackFunction data执行调用并且从数据集移除
     *
     * @param url
     */
    private void handlerReturnData(String url) {
        String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
        OnBridgeCallback f = responseCallbacks.get(functionName);
        String data = BridgeUtil.getDataFromReturnUrl(url);
        if (f != null) {
            f.onCallBack(data);
            responseCallbacks.remove(functionName);
        }
    }

    /**
     * 保存message到消息队列
     *
     * @param handlerName      handlerName
     * @param data             data
     * @param responseCallback CallBackFunction
     */
    private void doSend(String handlerName, String data, OnBridgeCallback responseCallback) {
        Message m = new Message();
        if (!TextUtils.isEmpty(data)) {
            m.setData(data);
        }
        if (responseCallback != null) {
            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            responseCallbacks.put(callbackStr, responseCallback);
            m.setCallbackId(callbackStr);
        }
        if (!TextUtils.isEmpty(handlerName)) {
            m.setHandlerName(handlerName);
        }
        queueMessage(m);
    }

    /**
     * list<message> != null 添加到消息集合否则分发消息
     *
     * @param m Message
     */
    private void queueMessage(Message m) {
        if (startupMessage != null) {
            startupMessage.add(m);
        } else {
            dispatchMessage(m);
        }
    }

    /**
     * 分发message 必须在主线程才分发成功
     *
     * @param m Message
     */
    private void dispatchMessage(Message m) {
        String messageJson = m.toJson();
        //escape special characters for json string  为json字符串转义特殊字符
        //系统原生API做Json转义替换手动转义,解决js解析数据格式报错
        messageJson = JSONObject.quote(messageJson);
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            this.loadUrl(javascriptCommand);
        }
    }

    /**
     * 刷新消息队列
     */
    private void flushMessageQueue() {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new OnBridgeCallback() {

                @Override
                public void onCallBack(String data) {
                    // deserializeMessage 反序列化消息
                    List<Message> list = null;
                    try {
                        list = Message.toArrayList(data);
                    } catch (Exception e) {
                        Log.w(TAG, e);
                        return;
                    }
                    if (list == null || list.isEmpty()) {
                        return;
                    }
                    for (int i = 0; i < list.size(); i++) {
                        Message m = list.get(i);
                        String responseId = m.getResponseId();
                        // 是否是response  CallBackFunction
                        if (!TextUtils.isEmpty(responseId)) {
                            OnBridgeCallback function = responseCallbacks.get(responseId);
                            String responseData = m.getResponseData();
                            function.onCallBack(responseData);
                            responseCallbacks.remove(responseId);
                        } else {
                            OnBridgeCallback responseFunction = null;
                            // if had callbackId 如果有回调Id
                            final String callbackId = m.getCallbackId();
                            if (!TextUtils.isEmpty(callbackId)) {
                                responseFunction = new OnBridgeCallback() {
                                    @Override
                                    public void onCallBack(String data) {
                                        Message responseMsg = new Message();
                                        responseMsg.setResponseId(callbackId);
                                        responseMsg.setResponseData(data);
                                        queueMessage(responseMsg);
                                    }
                                };
                            } else {
                                responseFunction = new OnBridgeCallback() {
                                    @Override
                                    public void onCallBack(String data) {
                                        // do nothing
                                    }
                                };
                            }
                            // BridgeHandler执行
                            BridgeHandler handler;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                                handler = defaultHandler;
                            }
                            if (handler != null) {
                                handler.handler(m.getData(), responseFunction);
                            }
                        }
                    }
                }
            });
        }
    }

    private void loadUrl(String jsUrl, OnBridgeCallback returnCallback) {
        this.loadUrl(jsUrl);
        // 添加至 Map<String, CallBackFunction>
        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
    }

    private void loadUrl(String jsUrl) {
        webView.loadUrl(jsUrl);
    }

    /**
     * register handler,so that javascript can call it
     * 注册处理程序,以便javascript调用它
     *
     * @param handlerName handlerName
     * @param handler     BridgeHandler
     */
    public void registerHandler(String handlerName, BridgeHandler handler) {
        if (handler != null) {
            // 添加至 Map<String, BridgeHandler>
            messageHandlers.put(handlerName, handler);
        }
    }

    /**
     * unregister handler
     *
     * @param handlerName
     */
    public void unregisterHandler(String handlerName) {
        if (handlerName != null) {
            messageHandlers.remove(handlerName);
        }
    }

    /**
     * call javascript registered handler
     * 调用javascript处理程序注册
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    CallBackFunction
     */
    public void callHandler(String handlerName, String data, OnBridgeCallback callBack) {
        doSend(handlerName, data, callBack);
    }

    public void onPageFinished() {
        webViewLoadLocalJs();

        if (getStartupMessage() != null) {
            for (Message m : getStartupMessage()) {
                dispatchMessage(m);
            }
            setStartupMessage(null);
        }
    }

    private void webViewLoadLocalJs() {
        String jsContent = BridgeUtil.assetFile2Str(webView.getContext(), BridgeHelper.BRIDGE_JS);
        loadUrl("javascript:" + jsContent);
    }

    public boolean shouldOverrideUrlLoading(String url) {
        try {
            // decode 之前,处理 % 和 +
            String replacedUrl = url.replaceAll("%(?![0-9a-fA-F]{2})", "%25").replaceAll("\\+", "%2B");
            url = URLDecoder.decode(replacedUrl, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.w(TAG, e);
        }

        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
            handlerReturnData(url);
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            flushMessageQueue();
            return true;
        }
        return false;
    }

    @Override
    public void sendToWeb(String data) {
        sendToWeb(data, (OnBridgeCallback) null);
    }

    @Override
    public void sendToWeb(String data, OnBridgeCallback responseCallback) {
        doSend(null, data, responseCallback);
    }

    @Override
    public void sendToWeb(String function, Object... values) {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            String jsCommand = String.format(function, values);
            jsCommand = String.format(BridgeUtil.JAVASCRIPT_STR, jsCommand);
            loadUrl(jsCommand);
        }
    }

    public void sendResponse(Object data, String callbackId) {
        if (!TextUtils.isEmpty(callbackId)) {
            final Message response = new Message();
            response.responseId = callbackId;
            response.responseData = data instanceof String ? (String) data : new Gson().toJson(data);
            if (Thread.currentThread() == Looper.getMainLooper().getThread()){
                dispatchMessage(response);
            }else {
                webView.getWebView().post(new Runnable() {
                    @Override
                    public void run() {
                        dispatchMessage(response);
                    }
                });
            }
        }
    }

    @Override
    public void responseFromWeb(String data, String callbackId) {
        sendResponse(data,callbackId);
    }

    public Map<String, OnBridgeCallback> getCallbacks() {
        return responseCallbacks;
    }

}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java
================================================
package com.github.lzyzsd.jsbridge;

import android.content.Context;
import android.webkit.WebView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


public class BridgeUtil {
	final static String YY_OVERRIDE_SCHEMA = "yy://";
	final static String YY_RETURN_DATA = YY_OVERRIDE_SCHEMA + "return/";//格式为   yy://return/{function}/returncontent
	final static String YY_FETCH_QUEUE = YY_RETURN_DATA + "_fetchQueue/";
	final static String EMPTY_STR = "";
	final static String UNDERLINE_STR = "_";
	final static String SPLIT_MARK = "/";
	
	final static String CALLBACK_ID_FORMAT = "JAVA_CB_%s";
	final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative(%s);";
	final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";
//	public final static String JAVASCRIPT_STR = "javascript:";


	public static final String JAVA_SCRIPT = "WebViewJavascriptBridge.js";
	public final static String JAVASCRIPT_STR = "javascript:%s";

	/**
	 * js 文件将注入为第一个script引用
	 * @param view WebView
	 * @param url url
	 */
	public static void webViewLoadJs(WebView view, String url){
		String js = "var newscript = document.createElement(\"script\");";
		js += "newscript.src=\"" + url + "\";";
		js += "document.scripts[0].parentNode.insertBefore(newscript,document.scripts[0]);";
		view.loadUrl("javascript:" + js);
	}

	/**
	 * 这里只是加载lib包中assets中的 WebViewJavascriptBridge.js
	 * @param view webview
	 * @param path 路径
	 */
    public static void webViewLoadLocalJs(WebView view, String path){
        String jsContent = assetFile2Str(view.getContext(), path);
		view.loadUrl("javascript:" + jsContent);
    }

	/**
	 * 解析assets文件夹里面的代码,去除注释,取可执行的代码
	 * @param c context
	 * @param urlStr 路径
	 * @return 可执行代码
	 */
	public static String assetFile2Str(Context c, String urlStr){
		InputStream in = null;
		try{
			in = c.getAssets().open(urlStr);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
            String line = null;
            StringBuilder sb = new StringBuilder();
            do {
                line = bufferedReader.readLine();
                if (line != null && !line.matches("^\\s*\\/\\/.*")) { // 去除注释
                    sb.append(line);
                }
            } while (line != null);

            bufferedReader.close();
            in.close();
 
            return sb.toString();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	public static String getFunctionFromReturnUrl(String url) {
		return "";
	}

	public static String getDataFromReturnUrl(String url) {
		return "";
	}

	public static String parseFunctionName(String jsUrl) {
		return "";
	}
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java
================================================
package com.github.lzyzsd.jsbridge;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import androidx.collection.ArrayMap;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.github.lzyzsd.library.BuildConfig;
import com.google.gson.Gson;


import org.json.JSONObject;

import java.net.URLEncoder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuppressLint("SetJavaScriptEnabled")
public class BridgeWebView extends WebView implements WebViewJavascriptBridge, BridgeWebViewClient.OnLoadJSListener {

	private final int URL_MAX_CHARACTER_NUM=2097152;
    private Map<String, OnBridgeCallback> mCallbacks = new ArrayMap<>();
    private Map<String, OnBridgeCallback> mPersistentCallbacks = new ArrayMap<>();

    private List<Object> mMessages = new ArrayList<>();

    private BridgeWebViewClient mClient;

    private long mUniqueId = 0;

    private boolean mJSLoaded = false;

    private Gson mGson;

    public BridgeWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BridgeWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public BridgeWebView(Context context) {
        super(context);
        init();
    }

    private void init() {
        clearCache(true);
        getSettings().setUseWideViewPort(true);
//		webView.getSettings().setLoadWithOverviewMode(true);
        getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
        getSettings().setJavaScriptEnabled(true);
//        mContent.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && BuildConfig.DEBUG) {
            WebView.setWebContentsDebuggingEnabled(true);
        }
        mClient = new BridgeWebViewClient(this);
        super.setWebViewClient(mClient);
    }

    public void setGson(Gson gson) {
        mGson = gson;
    }

    public boolean isJSLoaded() {
        return mJSLoaded;
    }

    public Map<String, OnBridgeCallback> getCallbacks() {
        return mCallbacks;
    }

    public Map<String, OnBridgeCallback> getPersistentCallbacks() {
        return mPersistentCallbacks;
    }

    @Override
    public void setWebViewClient(WebViewClient client) {
        mClient.setWebViewClient(client);
    }

    @Override
    public void onLoadStart() {
        mJSLoaded = false;
    }

    @Override
    public void onLoadFinished() {
        mJSLoaded = true;
        if (mMessages != null) {
            for (Object message : mMessages) {
                dispatchMessage(message);
            }
            mMessages = null;
        }
    }

    @Override
    public void sendToWeb(String data) {
        sendToWeb(data, (OnBridgeCallback) null);
    }

    @Override
    public void sendToWeb(String data, OnBridgeCallback responseCallback) {
        doSend(null, data, responseCallback);
    }

    /**
     * call javascript registered handler
     * 调用javascript处理程序注册
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    OnBridgeCallback
     */
    public void callHandler(String handlerName, String data, OnBridgeCallback callBack) {
        doSend(handlerName, data, callBack);
    }

    /**
     * call javascript registered handler with persistent callback
     * 调用javascript处理程序注册,使用持久回调
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    OnBridgeCallback (will be persistent and reusable)
     */
    public void callHandlerPersistent(String handlerName, String data, OnBridgeCallback callBack) {
        doSendPersistent(handlerName, data, callBack);
    }


    @Override
    public void sendToWeb(String function, Object... values) {
        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            String jsCommand = String.format(function, values);
            jsCommand = String.format(BridgeUtil.JAVASCRIPT_STR, jsCommand);
            loadUrl(jsCommand);
        }
    }

    @Override
    public void responseFromWeb(String data, String callbackId) {
        sendResponse(data,callbackId);
    }

    /**
     * 保存message到消息队列
     *
     * @param handlerName      handlerName
     * @param data             data
     * @param responseCallback OnBridgeCallback
     */
    private void doSend(String handlerName, Object data, OnBridgeCallback responseCallback) {
        if (!(data instanceof String) && mGson == null){
            return;
        }
        JSRequest request = new JSRequest();
        if (data != null) {
            request.data = data instanceof String ? (String) data : mGson.toJson(data);
        }
        if (responseCallback != null) {
            String callbackId = String.format(BridgeUtil.CALLBACK_ID_FORMAT, (++mUniqueId) + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            mCallbacks.put(callbackId, responseCallback);
            request.callbackId = callbackId;
        }
        if (!TextUtils.isEmpty(handlerName)) {
            request.handlerName = handlerName;
        }
        queueMessage(request);
    }

    /**
     * 保存message到消息队列,使用持久回调
     *
     * @param handlerName      handlerName
     * @param data             data
     * @param responseCallback OnBridgeCallback (persistent)
     */
    private void doSendPersistent(String handlerName, Object data, OnBridgeCallback responseCallback) {
        if (!(data instanceof String) && mGson == null){
            return;
        }
        JSRequest request = new JSRequest();
        if (data != null) {
            request.data = data instanceof String ? (String) data : mGson.toJson(data);
        }
        if (responseCallback != null) {
            String callbackId = String.format(BridgeUtil.CALLBACK_ID_FORMAT, (++mUniqueId) + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            mCallbacks.put(callbackId, responseCallback);
            mPersistentCallbacks.put(callbackId, responseCallback);
            request.callbackId = callbackId;
        }
        if (!TextUtils.isEmpty(handlerName)) {
            request.handlerName = handlerName;
        }
        queueMessage(request);
    }

    /**
     * list<message> != null 添加到消息集合否则分发消息
     *
     * @param message Message
     */
    private void queueMessage(Object message) {
        if (mMessages != null) {
            mMessages.add(message);
        } else {
            dispatchMessage(message);
        }
    }

    /**
     * 分发message 必须在主线程才分发成功
     *
     * @param message Message
     */
    private void dispatchMessage(Object message) {
        if (mGson == null){
            return;
        }
        String messageJson = mGson.toJson(message);
        //escape special characters for json string  为json字符串转义特殊字符

		  // 系统原生 API 做 Json转义,没必要自己正则替换,而且替换不一定完整
        messageJson = JSONObject.quote(messageJson);
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT&&javascriptCommand.length()>=URL_MAX_CHARACTER_NUM) {
				this.evaluateJavascript(javascriptCommand,null);
			}else {
				this.loadUrl(javascriptCommand);
			}
        }
    }

    public void sendResponse(Object data, String callbackId) {
        if (!(data instanceof String) && mGson == null){
            return;
        }
        if (!TextUtils.isEmpty(callbackId)) {
            final JSResponse response = new JSResponse();
            response.responseId = callbackId;
            response.responseData = data instanceof String ? (String) data : mGson.toJson(data);
            if (Thread.currentThread() == Looper.getMainLooper().getThread()){
                dispatchMessage(response);
            }else {
                post(new Runnable() {
                    @Override
                    public void run() {
                        dispatchMessage(response);
                    }
                });
            }
        }
    }

    @Override
    public void destroy() {
        super.destroy();
        mCallbacks.clear();
        mPersistentCallbacks.clear();
    }

    public static abstract class BaseJavascriptInterface {

        private Map<String, OnBridgeCallback> mCallbacks;
        private Map<String, OnBridgeCallback> mPersistentCallbacks;

        public BaseJavascriptInterface(Map<String, OnBridgeCallback> callbacks) {
            mCallbacks = callbacks;
        }

        public BaseJavascriptInterface(Map<String, OnBridgeCallback> callbacks, Map<String, OnBridgeCallback> persistentCallbacks) {
            mCallbacks = callbacks;
            mPersistentCallbacks = persistentCallbacks;
        }

        @JavascriptInterface
        public String send(String data, String callbackId) {
            Log.d("BaseJavascriptInterface", data + ", callbackId: " + callbackId + " " + Thread.currentThread().getName());
            return send(data);
        }

        @JavascriptInterface
        public void response(String data, String responseId) {
            Log.d("BaseJavascriptInterface", data + ", responseId: " + responseId + " " + Thread.currentThread().getName());
            if (!TextUtils.isEmpty(responseId)) {
                OnBridgeCallback function = mCallbacks.get(responseId);
                if (function != null) {
                    function.onCallBack(data);
                    // Only remove if it's not a persistent callback
                    if (mPersistentCallbacks == null || !mPersistentCallbacks.containsKey(responseId)) {
                        mCallbacks.remove(responseId);
                    }
                }
            }
        }

        public abstract String send(String data);
    }

}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java
================================================
package com.github.lzyzsd.jsbridge;

import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import androidx.annotation.Nullable;

import android.util.Log;
import android.view.KeyEvent;
import android.webkit.ClientCertRequest;
import android.webkit.HttpAuthHandler;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.SafeBrowsingResponse;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

/**
 * 如果要自定义WebViewClient必须要集成此类
 * Created by bruce on 10/28/15.
 */
class BridgeWebViewClient extends WebViewClient {

    private static final String TAG = "BridgeWebViewClient";
    private OnLoadJSListener mListener;

    private WebViewClient mClient;

    public BridgeWebViewClient(OnLoadJSListener listener) {
        mListener = listener;
    }

    public void setWebViewClient(WebViewClient client) {
        mClient = client;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (mClient != null) {
            return mClient.shouldOverrideUrlLoading(view, url);
        }
        return interceptUrl(url) ? true : super.shouldOverrideUrlLoading(view, url);
    }

    private boolean interceptUrl(String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "shouldOverrideUrlLoading, url = " + url);
        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            return true;
        }
        return false;
    }

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        if (mClient != null) {
            mClient.onPageStarted(view, url, favicon);
        } else {
            super.onPageStarted(view, url, favicon);
        }

    }

    @Override
    public void onPageFinished(WebView view, String url) {
        if (mClient != null) {
            mClient.onPageFinished(view, url);
        } else {
            super.onPageFinished(view, url);
        }
        mListener.onLoadStart();
        BridgeUtil.webViewLoadLocalJs(view, BridgeUtil.JAVA_SCRIPT);
        mListener.onLoadFinished();
    }

    @Override
    public void onLoadResource(WebView view, String url) {
        if (mClient != null) {
            mClient.onLoadResource(view, url);
        } else {
            super.onLoadResource(view, url);
        }
    }

    @Override
    public void onPageCommitVisible(WebView view, String url) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) {
            mClient.onPageCommitVisible(view, url);
        } else {
            super.onPageCommitVisible(view, url);
        }
    }

    @Nullable
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        if (mClient != null) {
            return mClient.shouldInterceptRequest(view, url);
        }
        return super.shouldInterceptRequest(view, url);
    }

    @Nullable
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mClient != null) {
            return mClient.shouldInterceptRequest(view, request);
        }
        return super.shouldInterceptRequest(view, request);
    }

    @Override
    public void onTooManyRedirects(WebView view, android.os.Message cancelMsg, android.os.Message continueMsg) {
        if (mClient != null) {
            mClient.onTooManyRedirects(view, cancelMsg, continueMsg);
        } else {
            super.onTooManyRedirects(view, cancelMsg, continueMsg);
        }
    }

    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        if (mClient != null) {
            mClient.onReceivedError(view, errorCode, description, failingUrl);
        } else {
            super.onReceivedError(view, errorCode, description, failingUrl);
        }
    }

    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) {
            mClient.onReceivedError(view, request, error);
        } else {
            super.onReceivedError(view, request, error);
        }
    }

    @Override
    public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) {
            mClient.onReceivedHttpError(view, request, errorResponse);
        } else {
            super.onReceivedHttpError(view, request, errorResponse);
        }

    }

    @Override
    public void onFormResubmission(WebView view, android.os.Message dontResend, android.os.Message resend) {
        if (mClient != null) {
            mClient.onFormResubmission(view, dontResend, resend);
        } else {
            super.onFormResubmission(view, dontResend, resend);
        }

    }

    @Override
    public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
        if (mClient != null) {
            mClient.doUpdateVisitedHistory(view, url, isReload);
        } else {
            super.doUpdateVisitedHistory(view, url, isReload);
        }
    }

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        if (mClient != null) {
            mClient.onReceivedSslError(view, handler, error);
        } else {
            super.onReceivedSslError(view, handler, error);
        }
    }

    @Override
    public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mClient != null) {
            mClient.onReceivedClientCertRequest(view, request);
        } else {
            super.onReceivedClientCertRequest(view, request);
        }
    }

    @Override
    public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
        if (mClient != null) {
            mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
        } else {
            super.onReceivedHttpAuthRequest(view, handler, host, realm);
        }
    }

    @Override
    public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
        if (mClient != null) {
            return mClient.shouldOverrideKeyEvent(view, event);
        }
        return super.shouldOverrideKeyEvent(view, event);
    }

    @Override
    public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
        if (mClient != null) {
            mClient.onUnhandledKeyEvent(view, event);
        } else {
            super.onUnhandledKeyEvent(view, event);
        }
    }

    @Override
    public void onScaleChanged(WebView view, float oldScale, float newScale) {
        if (mClient != null) {
            mClient.onScaleChanged(view, oldScale, newScale);
        } else {
            super.onScaleChanged(view, oldScale, newScale);
        }
    }

    @Override
    public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) {
        if (mClient != null) {
            mClient.onReceivedLoginRequest(view, realm, account, args);
        } else {
            super.onReceivedLoginRequest(view, realm, account, args);
        }
    }

    @Override
    public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mClient != null){
            return mClient.onRenderProcessGone(view, detail);
        }
        return super.onRenderProcessGone(view, detail);
    }

    @Override
    public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && mClient != null){
            mClient.onSafeBrowsingHit(view, request, threatType, callback);
        }else {
            super.onSafeBrowsingHit(view, request, threatType, callback);
        }
    }


    public interface OnLoadJSListener {

        void onLoadStart();

        void onLoadFinished();

    }
}

================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java
================================================
package com.github.lzyzsd.jsbridge;

public class DefaultHandler implements BridgeHandler {
    @Override
    public void handler(String data, OnBridgeCallback callBackFunction) {

    }
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java
================================================
package com.github.lzyzsd.jsbridge;

import android.content.Context;
import android.webkit.WebView;

/**
 * WebView功能接口.
 *
 * @author ZhengAn
 * @date 2019-07-01
 */
public interface IWebView {

    Context getContext();

    void loadUrl(String url);

    /**
     * 获取当前Webview
     */
    WebView getWebView();
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java
================================================
package com.github.lzyzsd.jsbridge;

/**
 * Created on 2019/7/10.
 * Author: bigwang
 * Description:
 */
class JSRequest {

    public String callbackId;

    public String data;

    public String handlerName;
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java
================================================
package com.github.lzyzsd.jsbridge;

/**
 * Created on 2019/7/10.
 * Author: bigwang
 * Description:
 */
class JSResponse {

    public String responseId;

    public String responseData;
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/Message.java
================================================
package com.github.lzyzsd.jsbridge;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.util.List;

public class Message {
    public String responseId;
    public String responseData;
    public String callbackId;
    public String data;
    public String handlerName;

    public static List<Message> toArrayList(String data) {
        return new Gson().fromJson(data, new TypeToken<List<Message>>(){}.getType());
    }

    public String getResponseId() {
        return responseId;
    }

    public void setResponseId(String responseId) {
        this.responseId = responseId;
    }

    public String getResponseData() {
        return responseData;
    }

    public void setResponseData(String responseData) {
        this.responseData = responseData;
    }

    public String getCallbackId() {
        return callbackId;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setCallbackId(String callbackStr) {
        this.callbackId = callbackStr;
    }

    public void setHandlerName(String handlerName) {
        this.handlerName = handlerName;
    }

    public String getHandlerName() {
        return handlerName;
    }

    public String toJson() {
        return new Gson().toJson(this);
    }
}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java
================================================
package com.github.lzyzsd.jsbridge;

public interface OnBridgeCallback {
	
	void onCallBack(String data);

}


================================================
FILE: library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java
================================================
package com.github.lzyzsd.jsbridge;


public interface WebViewJavascriptBridge {
	
	void sendToWeb(String data);

	void sendToWeb(String data, OnBridgeCallback responseCallback);

	void sendToWeb(String function, Object... values);

	/**
	 * 处理从js返回的数据
	 * @param data 数据
	 * @param callbackId jsCallbackId
	 */
	void responseFromWeb(String data,String callbackId);

}


================================================
FILE: library/src/test/assets/test_persistent_callback.html
================================================
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Persistent Callback Test</title>
</head>
<body>
    <div id="test-results"></div>
    
    <script>
        // Test script for persistent callback functionality
        var testResults = [];
        var persistentCallbackCount = 0;
        var cachedCallback = null;

        function logResult(message) {
            testResults.push(message);
            document.getElementById('test-results').innerHTML = testResults.join('<br>');
            console.log(message);
        }

        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) {
                logResult('Bridge initialized');
            });

            // Test persistent callback functionality
            bridge.registerHandler("testPersistentCallback", function(data, responseCallback) {
                logResult('Handler called with data: ' + data);
                
                // Cache the callback for reuse
                cachedCallback = responseCallback;
                
                // Respond immediately
                if (responseCallback) {
                    responseCallback("First response");
                    logResult('Sent first response');
                }
            });

            // Function to test reusing the cached callback
            window.testReuseCachedCallback = function() {
                if (cachedCallback) {
                    try {
                        cachedCallback("Reused callback response " + (++persistentCallbackCount));
                        logResult('Successfully reused cached callback #' + persistentCallbackCount);
                        return true;
                    } catch (error) {
                        logResult('Error reusing cached callback: ' + error.message);
                        return false;
                    }
                } else {
                    logResult('No cached callback available');
                    return false;
                }
            };

            // Test normal callback behavior
            bridge.registerHandler("testNormalCallback", function(data, responseCallback) {
                logResult('Normal handler called with data: ' + data);
                if (responseCallback) {
                    responseCallback("Normal response");
                    logResult('Sent normal response');
                }
            });

            logResult('Test handlers registered');
        });

        // Function to run all tests
        window.runPersistentCallbackTests = function() {
            logResult('Starting persistent callback tests...');
            
            // Test 1: Call handler and cache callback
            WebViewJavascriptBridge.callHandler('testPersistentCallback', 'test data', function(response) {
                logResult('Received response: ' + response);
            });
            
            // Test 2: Try to reuse cached callback multiple times
            setTimeout(function() {
                for (var i = 0; i < 3; i++) {
                    setTimeout(function() {
                        testReuseCachedCallback();
                    }, i * 100);
                }
            }, 500);
        };
    </script>
</body>
</html>

================================================
FILE: library/src/test/java/com/github/lzyzsd/jsbridge/PersistentCallbackTest.java
================================================
package com.github.lzyzsd.jsbridge;

import android.content.Context;
import android.webkit.WebView;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.google.gson.Gson;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * Test class for persistent callback functionality
 * Tests the fix for issue #280 - persistent callbacks should be reusable
 */
@RunWith(AndroidJUnit4.class)
public class PersistentCallbackTest {

    private BridgeWebView bridgeWebView;
    private Context context;

    @Mock
    private OnBridgeCallback mockCallback;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        context = ApplicationProvider.getApplicationContext();
        bridgeWebView = new BridgeWebView(context);
        bridgeWebView.setGson(new Gson());
    }

    @Test
    public void testPersistentCallbackReuse() {
        // This test verifies that persistent callbacks can be reused multiple times
        // without being deleted after first use
        
        final int[] callCount = {0};
        
        // Create a callback that should be persistent
        OnBridgeCallback persistentCallback = new OnBridgeCallback() {
            @Override
            public void onCallBack(String data) {
                callCount[0]++;
            }
        };

        // Use the persistent callback method
        bridgeWebView.callHandlerPersistent("testHandler", "testData", persistentCallback);
        
        // Get the callback ID that was generated
        String callbackId = null;
        for (String id : bridgeWebView.getCallbacks().keySet()) {
            if (bridgeWebView.getPersistentCallbacks().containsKey(id)) {
                callbackId = id;
                break;
            }
        }
        
        assertNotNull("Persistent callback should be stored", callbackId);

        // Simulate multiple responses from JavaScript
        // This should work without the callback being deleted
        bridgeWebView.sendResponse("response1", callbackId);
        bridgeWebView.sendResponse("response2", callbackId);
        bridgeWebView.sendResponse("response3", callbackId);

        // Verify the callback was called multiple times
        assertEquals("Persistent callback should be called 3 times", 3, callCount[0]);
        
        // Verify the callback is still available after multiple uses
        assertTrue("Persistent callback should still exist after multiple uses", 
                   bridgeWebView.getCallbacks().containsKey(callbackId));
        assertTrue("Persistent callback should be marked as persistent", 
                   bridgeWebView.getPersistentCallbacks().containsKey(callbackId));
    }

    @Test
    public void testNormalCallbackBehavior() {
        // This test verifies that normal (non-persistent) callbacks still work as before
        // and are deleted after first use
        
        final int[] callCount = {0};
        
        OnBridgeCallback normalCallback = new OnBridgeCallback() {
            @Override
            public void onCallBack(String data) {
                callCount[0]++;
            }
        };
        
        // Call handler with normal callback - should be deleted after first use
        bridgeWebView.callHandler("testHandler", "testData", normalCallback);
        
        // The callback should be stored initially
        assertFalse("Normal callbacks should be stored initially", 
                    bridgeWebView.getCallbacks().isEmpty());
        
        // Get the callback ID that was generated
        String callbackId = null;
        for (String id : bridgeWebView.getCallbacks().keySet()) {
            callbackId = id;
            break;
        }
        
        assertNotNull("Normal callback should be stored", callbackId);
        
        // Verify it's not marked as persistent
        assertFalse("Normal callback should not be marked as persistent", 
                    bridgeWebView.getPersistentCallbacks().containsKey(callbackId));
        
        // Simulate response from JavaScript
        bridgeWebView.sendResponse("response", callbackId);
        
        // Verify the callback was called
        assertEquals("Normal callback should be called once", 1, callCount[0]);
        
        // Try to call it again - should not work since it should be deleted
        bridgeWebView.sendResponse("response2", callbackId);
        
        // Verify the callback was not called again
        assertEquals("Normal callback should not be called again after deletion", 1, callCount[0]);
    }

    @Test
    public void testCallbackIdGeneration() {
        // Test that callback IDs are generated correctly
        OnBridgeCallback callback1 = mock(OnBridgeCallback.class);
        OnBridgeCallback callback2 = mock(OnBridgeCallback.class);
        
        bridgeWebView.callHandler("handler1", "data1", callback1);
        int size1 = bridgeWebView.getCallbacks().size();
        
        bridgeWebView.callHandler("handler2", "data2", callback2);
        int size2 = bridgeWebView.getCallbacks().size();
        
        assertEquals("Each callback should be stored with unique ID", size1 + 1, size2);
    }
}

================================================
FILE: settings.gradle
================================================
include ':example', ':library'
Download .txt
gitextract_ae0068gj/

├── .github/
│   └── workflows/
│       └── android.yml
├── .gitignore
├── README.md
├── build.gradle
├── example/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── assets/
│           │   ├── demo.html
│           │   └── persistent_callback_demo.html
│           ├── java/
│           │   └── com/
│           │       └── github/
│           │           └── lzyzsd/
│           │               └── jsbridge/
│           │                   └── example/
│           │                       ├── CustomWebView.java
│           │                       ├── MainActivity.java
│           │                       └── MainJavascriptInterface.java
│           └── res/
│               ├── layout/
│               │   └── activity_main.xml
│               └── values/
│                   ├── strings.xml
│                   └── styles.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── library/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── assets/
│       │   │   └── WebViewJavascriptBridge.js
│       │   └── java/
│       │       └── com/
│       │           └── github/
│       │               └── lzyzsd/
│       │                   └── jsbridge/
│       │                       ├── BridgeHandler.java
│       │                       ├── BridgeHelper.java
│       │                       ├── BridgeUtil.java
│       │                       ├── BridgeWebView.java
│       │                       ├── BridgeWebViewClient.java
│       │                       ├── DefaultHandler.java
│       │                       ├── IWebView.java
│       │                       ├── JSRequest.java
│       │                       ├── JSResponse.java
│       │                       ├── Message.java
│       │                       ├── OnBridgeCallback.java
│       │                       └── WebViewJavascriptBridge.java
│       └── test/
│           ├── assets/
│           │   └── test_persistent_callback.html
│           └── java/
│               └── com/
│                   └── github/
│                       └── lzyzsd/
│                           └── jsbridge/
│                               └── PersistentCallbackTest.java
└── settings.gradle
Download .txt
SYMBOL INDEX (168 symbols across 17 files)

FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java
  class CustomWebView (line 25) | @SuppressLint("SetJavaScriptEnabled")
    method CustomWebView (line 32) | public CustomWebView(Context context, AttributeSet attrs) {
    method CustomWebView (line 37) | public CustomWebView(Context context, AttributeSet attrs, int defStyle) {
    method CustomWebView (line 42) | public CustomWebView(Context context) {
    method init (line 47) | private void init() {
    method setDefaultHandler (line 73) | public void setDefaultHandler(BridgeHandler handler) {
    method registerHandler (line 84) | public void registerHandler(String handlerName, BridgeHandler handler) {
    method unregisterHandler (line 93) | public void unregisterHandler(String handlerName) {
    method callHandler (line 105) | public void callHandler(String handlerName, String data, OnBridgeCallb...
    method sendToWeb (line 109) | @Override
    method sendToWeb (line 114) | @Override
    method sendToWeb (line 119) | @Override
    method responseFromWeb (line 124) | @Override
    method setGson (line 129) | public void setGson(Gson gson){
    method getCallbacks (line 133) | public Map<String, OnBridgeCallback> getCallbacks() {
    method getWebView (line 137) | @Override

FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java
  class MainActivity (line 19) | public class MainActivity extends Activity implements OnClickListener {
    class Location (line 33) | static class Location {
    class User (line 37) | static class User {
    method onCreate (line 43) | @Override
    method pickFile (line 100) | public void pickFile() {
    method onActivityResult (line 106) | @Override
    method onClick (line 129) | @Override

FILE: example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascriptInterface.java
  class MainJavascriptInterface (line 17) | public class MainJavascriptInterface extends BridgeWebView.BaseJavascrip...
    method MainJavascriptInterface (line 22) | public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks...
    method MainJavascriptInterface (line 27) | public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks...
    method MainJavascriptInterface (line 32) | public MainJavascriptInterface(Map<String, OnBridgeCallback> callbacks) {
    method send (line 36) | @Override
    method submitFromWeb (line 42) | @JavascriptInterface

FILE: library/src/main/assets/WebViewJavascriptBridge.js
  function _createQueueReadyIframe (line 25) | function _createQueueReadyIframe() {
  function _createQueueReadyIframe4biz (line 32) | function _createQueueReadyIframe4biz() {
  function init (line 38) | function init(messageHandler) {
  function send (line 54) | function send(data, responseCallback) {
  function registerHandler (line 59) | function registerHandler(handlerName, handler) {
  function removeHandler (line 63) | function removeHandler(handlerName, handler) {
  function registerPersistentCallback (line 68) | function registerPersistentCallback(callbackId, callback) {
  function removePersistentCallback (line 74) | function removePersistentCallback(callbackId) {
  function callHandler (line 80) | function callHandler(handlerName, data, responseCallback, persistent) {
  function callHandlerPersistent (line 90) | function callHandlerPersistent(handlerName, data, responseCallback) {
  function _doSend (line 99) | function _doSend(handlerName, message, responseCallback, persistent) {
  function _fetchQueue (line 138) | function _fetchQueue() {
  function _dispatchMessageFromNative (line 161) | function _dispatchMessageFromNative(messageJSON) {
  function _handleMessageFromNative (line 202) | function _handleMessageFromNative(messageJSON) {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java
  type BridgeHandler (line 3) | public interface BridgeHandler {
    method handler (line 4) | void handler(String data, OnBridgeCallback callBackFunction);

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java
  class BridgeHelper (line 25) | public class BridgeHelper implements WebViewJavascriptBridge {
    method getStartupMessage (line 36) | private List<Message> getStartupMessage() {
    method setStartupMessage (line 40) | private void setStartupMessage(List<Message> startupMessage) {
    method BridgeHelper (line 48) | public BridgeHelper(IWebView webView) {
    method setDefaultHandler (line 56) | public void setDefaultHandler(BridgeHandler handler) {
    method handlerReturnData (line 65) | private void handlerReturnData(String url) {
    method doSend (line 82) | private void doSend(String handlerName, String data, OnBridgeCallback ...
    method queueMessage (line 103) | private void queueMessage(Message m) {
    method dispatchMessage (line 116) | private void dispatchMessage(Message m) {
    method flushMessageQueue (line 131) | private void flushMessageQueue() {
    method loadUrl (line 196) | private void loadUrl(String jsUrl, OnBridgeCallback returnCallback) {
    method loadUrl (line 202) | private void loadUrl(String jsUrl) {
    method registerHandler (line 213) | public void registerHandler(String handlerName, BridgeHandler handler) {
    method unregisterHandler (line 225) | public void unregisterHandler(String handlerName) {
    method callHandler (line 239) | public void callHandler(String handlerName, String data, OnBridgeCallb...
    method onPageFinished (line 243) | public void onPageFinished() {
    method webViewLoadLocalJs (line 254) | private void webViewLoadLocalJs() {
    method shouldOverrideUrlLoading (line 259) | public boolean shouldOverrideUrlLoading(String url) {
    method sendToWeb (line 278) | @Override
    method sendToWeb (line 283) | @Override
    method sendToWeb (line 288) | @Override
    method sendResponse (line 297) | public void sendResponse(Object data, String callbackId) {
    method responseFromWeb (line 315) | @Override
    method getCallbacks (line 320) | public Map<String, OnBridgeCallback> getCallbacks() {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java
  class BridgeUtil (line 12) | public class BridgeUtil {
    method webViewLoadJs (line 34) | public static void webViewLoadJs(WebView view, String url){
    method webViewLoadLocalJs (line 46) | public static void webViewLoadLocalJs(WebView view, String path){
    method assetFile2Str (line 57) | public static String assetFile2Str(Context c, String urlStr){
    method getFunctionFromReturnUrl (line 89) | public static String getFunctionFromReturnUrl(String url) {
    method getDataFromReturnUrl (line 93) | public static String getDataFromReturnUrl(String url) {
    method parseFunctionName (line 97) | public static String parseFunctionName(String jsUrl) {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java
  class BridgeWebView (line 31) | @SuppressLint("SetJavaScriptEnabled")
    method BridgeWebView (line 48) | public BridgeWebView(Context context, AttributeSet attrs) {
    method BridgeWebView (line 53) | public BridgeWebView(Context context, AttributeSet attrs, int defStyle) {
    method BridgeWebView (line 58) | public BridgeWebView(Context context) {
    method init (line 63) | private void init() {
    method setGson (line 78) | public void setGson(Gson gson) {
    method isJSLoaded (line 82) | public boolean isJSLoaded() {
    method getCallbacks (line 86) | public Map<String, OnBridgeCallback> getCallbacks() {
    method getPersistentCallbacks (line 90) | public Map<String, OnBridgeCallback> getPersistentCallbacks() {
    method setWebViewClient (line 94) | @Override
    method onLoadStart (line 99) | @Override
    method onLoadFinished (line 104) | @Override
    method sendToWeb (line 115) | @Override
    method sendToWeb (line 120) | @Override
    method callHandler (line 133) | public void callHandler(String handlerName, String data, OnBridgeCallb...
    method callHandlerPersistent (line 145) | public void callHandlerPersistent(String handlerName, String data, OnB...
    method sendToWeb (line 150) | @Override
    method responseFromWeb (line 160) | @Override
    method doSend (line 172) | private void doSend(String handlerName, Object data, OnBridgeCallback ...
    method doSendPersistent (line 198) | private void doSendPersistent(String handlerName, Object data, OnBridg...
    method queueMessage (line 223) | private void queueMessage(Object message) {
    method dispatchMessage (line 236) | private void dispatchMessage(Object message) {
    method sendResponse (line 256) | public void sendResponse(Object data, String callbackId) {
    method destroy (line 277) | @Override
    class BaseJavascriptInterface (line 284) | public static abstract class BaseJavascriptInterface {
      method BaseJavascriptInterface (line 289) | public BaseJavascriptInterface(Map<String, OnBridgeCallback> callbac...
      method BaseJavascriptInterface (line 293) | public BaseJavascriptInterface(Map<String, OnBridgeCallback> callbac...
      method send (line 298) | @JavascriptInterface
      method response (line 304) | @JavascriptInterface
      method send (line 319) | public abstract String send(String data);

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java
  class BridgeWebViewClient (line 28) | class BridgeWebViewClient extends WebViewClient {
    method BridgeWebViewClient (line 35) | public BridgeWebViewClient(OnLoadJSListener listener) {
    method setWebViewClient (line 39) | public void setWebViewClient(WebViewClient client) {
    method shouldOverrideUrlLoading (line 43) | @Override
    method interceptUrl (line 51) | private boolean interceptUrl(String url) {
    method onPageStarted (line 66) | @Override
    method onPageFinished (line 76) | @Override
    method onLoadResource (line 88) | @Override
    method onPageCommitVisible (line 97) | @Override
    method shouldInterceptRequest (line 106) | @Nullable
    method shouldInterceptRequest (line 115) | @Nullable
    method onTooManyRedirects (line 124) | @Override
    method onReceivedError (line 133) | @Override
    method onReceivedError (line 142) | @Override
    method onReceivedHttpError (line 151) | @Override
    method onFormResubmission (line 161) | @Override
    method doUpdateVisitedHistory (line 171) | @Override
    method onReceivedSslError (line 180) | @Override
    method onReceivedClientCertRequest (line 189) | @Override
    method onReceivedHttpAuthRequest (line 198) | @Override
    method shouldOverrideKeyEvent (line 207) | @Override
    method onUnhandledKeyEvent (line 215) | @Override
    method onScaleChanged (line 224) | @Override
    method onReceivedLoginRequest (line 233) | @Override
    method onRenderProcessGone (line 242) | @Override
    method onSafeBrowsingHit (line 250) | @Override
    type OnLoadJSListener (line 260) | public interface OnLoadJSListener {
      method onLoadStart (line 262) | void onLoadStart();
      method onLoadFinished (line 264) | void onLoadFinished();

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java
  class DefaultHandler (line 3) | public class DefaultHandler implements BridgeHandler {
    method handler (line 4) | @Override

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java
  type IWebView (line 12) | public interface IWebView {
    method getContext (line 14) | Context getContext();
    method loadUrl (line 16) | void loadUrl(String url);
    method getWebView (line 21) | WebView getWebView();

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java
  class JSRequest (line 8) | class JSRequest {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java
  class JSResponse (line 8) | class JSResponse {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/Message.java
  class Message (line 8) | public class Message {
    method toArrayList (line 15) | public static List<Message> toArrayList(String data) {
    method getResponseId (line 19) | public String getResponseId() {
    method setResponseId (line 23) | public void setResponseId(String responseId) {
    method getResponseData (line 27) | public String getResponseData() {
    method setResponseData (line 31) | public void setResponseData(String responseData) {
    method getCallbackId (line 35) | public String getCallbackId() {
    method setData (line 39) | public void setData(String data) {
    method getData (line 43) | public String getData() {
    method setCallbackId (line 47) | public void setCallbackId(String callbackStr) {
    method setHandlerName (line 51) | public void setHandlerName(String handlerName) {
    method getHandlerName (line 55) | public String getHandlerName() {
    method toJson (line 59) | public String toJson() {

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java
  type OnBridgeCallback (line 3) | public interface OnBridgeCallback {
    method onCallBack (line 5) | void onCallBack(String data);

FILE: library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java
  type WebViewJavascriptBridge (line 4) | public interface WebViewJavascriptBridge {
    method sendToWeb (line 6) | void sendToWeb(String data);
    method sendToWeb (line 8) | void sendToWeb(String data, OnBridgeCallback responseCallback);
    method sendToWeb (line 10) | void sendToWeb(String function, Object... values);
    method responseFromWeb (line 17) | void responseFromWeb(String data,String callbackId);

FILE: library/src/test/java/com/github/lzyzsd/jsbridge/PersistentCallbackTest.java
  class PersistentCallbackTest (line 23) | @RunWith(AndroidJUnit4.class)
    method setUp (line 32) | @Before
    method testPersistentCallbackReuse (line 40) | @Test
    method testNormalCallbackBehavior (line 85) | @Test
    method testCallbackIdGeneration (line 132) | @Test
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (100K chars).
[
  {
    "path": ".github/workflows/android.yml",
    "chars": 253,
    "preview": "name: Android CI\n\non: [push]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n   "
  },
  {
    "path": ".gitignore",
    "chars": 701,
    "preview": "#built application files\n*.apk\n*.ap_\n\n\n# files for the dex VM\n*.dex\n\n\n# Java class files\n*.class\n\n\n# generated files\nbin"
  },
  {
    "path": "README.md",
    "chars": 6829,
    "preview": "# JsBridge\n\n-----\n\nInspired and modified from [this](https://github.com/jacin1/JsBridge) and WeChat jsBridge file, with "
  },
  {
    "path": "build.gradle",
    "chars": 813,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "example/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "example/build.gradle",
    "chars": 703,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 28\n//    buildToolsVersion \"25.0.3\"\n\n    defaul"
  },
  {
    "path": "example/proguard-rules.pro",
    "chars": 647,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "example/src/main/AndroidManifest.xml",
    "chars": 752,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.github.lzyzsd.jsbridge.examp"
  },
  {
    "path": "example/src/main/assets/demo.html",
    "chars": 3407,
    "preview": "<html>\n<head>\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">\n    <title>\n        js调用java\n    <"
  },
  {
    "path": "example/src/main/assets/persistent_callback_demo.html",
    "chars": 3795,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\">\n    <title>Persist"
  },
  {
    "path": "example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java",
    "chars": 3997,
    "preview": "package com.github.lzyzsd.jsbridge.example;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimp"
  },
  {
    "path": "example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java",
    "chars": 3889,
    "preview": "package com.github.lzyzsd.jsbridge.example;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android."
  },
  {
    "path": "example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascriptInterface.java",
    "chars": 1435,
    "preview": "package com.github.lzyzsd.jsbridge.example;\n\nimport android.util.Log;\nimport android.webkit.JavascriptInterface;\n\nimport"
  },
  {
    "path": "example/src/main/res/layout/activity_main.xml",
    "chars": 719,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "example/src/main/res/values/strings.xml",
    "chars": 121,
    "preview": "<resources>\n    <string name=\"app_name\">JsBridge</string>\n    <string name=\"button_name\">Java调用Web</string>\n</resources>"
  },
  {
    "path": "example/src/main/res/values/styles.xml",
    "chars": 194,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Wed Mar 24 16:24:00 CST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 945,
    "preview": "## Project-wide Gradle settings.\n#\n# For more details on how to configure your build environment visit\n# http://www.grad"
  },
  {
    "path": "gradlew",
    "chars": 5080,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "library/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "chars": 1797,
    "preview": "apply plugin: 'com.android.library'\n\nversion = \"1.0.0\"\n\nandroid {\n    compileSdkVersion 28\n//    buildToolsVersion \"25.0"
  },
  {
    "path": "library/proguard-rules.pro",
    "chars": 647,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "chars": 254,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.github.lzyzsd.library\">\n\n   "
  },
  {
    "path": "library/src/main/assets/WebViewJavascriptBridge.js",
    "chars": 8617,
    "preview": "//notation: js file can only use this kind of comments\n//since comments will cause error when use in webview.loadurl,\n//"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java",
    "chars": 138,
    "preview": "package com.github.lzyzsd.jsbridge;\n\npublic interface BridgeHandler {\n    void handler(String data, OnBridgeCallback cal"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java",
    "chars": 10947,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.os.Looper;\nimport android.os.SystemClock;\nimport android.text.TextUt"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java",
    "chars": 2919,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.content.Context;\nimport android.webkit.WebView;\n\nimport java.io.Buff"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java",
    "chars": 10338,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport andr"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java",
    "chars": 8652,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.graphics.Bitmap;\nimport android.net.http.SslError;\nimport android.os"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java",
    "chars": 189,
    "preview": "package com.github.lzyzsd.jsbridge;\n\npublic class DefaultHandler implements BridgeHandler {\n    @Override\n    public voi"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java",
    "chars": 317,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.content.Context;\nimport android.webkit.WebView;\n\n/**\n * WebView功能接口."
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java",
    "chars": 213,
    "preview": "package com.github.lzyzsd.jsbridge;\n\n/**\n * Created on 2019/7/10.\n * Author: bigwang\n * Description:\n */\nclass JSRequest"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java",
    "chars": 190,
    "preview": "package com.github.lzyzsd.jsbridge;\n\n/**\n * Created on 2019/7/10.\n * Author: bigwang\n * Description:\n */\nclass JSRespons"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/Message.java",
    "chars": 1347,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport com.google.gson.Gson;\nimport com.google.gson.reflect.TypeToken;\n\nimport java"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java",
    "chars": 109,
    "preview": "package com.github.lzyzsd.jsbridge;\n\npublic interface OnBridgeCallback {\n\t\n\tvoid onCallBack(String data);\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java",
    "chars": 369,
    "preview": "package com.github.lzyzsd.jsbridge;\n\n\npublic interface WebViewJavascriptBridge {\n\t\n\tvoid sendToWeb(String data);\n\n\tvoid "
  },
  {
    "path": "library/src/test/assets/test_persistent_callback.html",
    "chars": 3800,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>Persistent Callback Test</title>\n</head>\n<body>\n    "
  },
  {
    "path": "library/src/test/java/com/github/lzyzsd/jsbridge/PersistentCallbackTest.java",
    "chars": 5404,
    "preview": "package com.github.lzyzsd.jsbridge;\n\nimport android.content.Context;\nimport android.webkit.WebView;\nimport androidx.test"
  },
  {
    "path": "settings.gradle",
    "chars": 31,
    "preview": "include ':example', ':library'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the happydog-intj/JsBridge GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (91.0 KB), approximately 22.2k tokens, and a symbol index with 168 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!