├── app ├── .gitignore ├── simple.jks ├── app-release.apk ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ └── layout │ │ │ │ ├── content_main.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── fragment_webview.xml │ │ ├── assets │ │ │ ├── connect_error.html │ │ │ ├── bridge_demo.html │ │ │ └── bridge.js │ │ ├── java │ │ │ └── com │ │ │ │ └── bridge │ │ │ │ └── view │ │ │ │ ├── ResponseStatus.java │ │ │ │ ├── IInvokeJS.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── JavaInterfaces4JS.java │ │ │ │ └── fragment │ │ │ │ └── WebViewFragment.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── bridge │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── bridge │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── simplejsjavabridgeLib ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── simplejsjavabridge │ │ │ │ └── lib │ │ │ │ ├── IJavaCallback2JS.java │ │ │ │ ├── exception │ │ │ │ └── SimpleJSBridgeException.java │ │ │ │ ├── annotation │ │ │ │ ├── InvokeJSInterface.java │ │ │ │ ├── JavaCallback4JS.java │ │ │ │ ├── JavaInterface4JS.java │ │ │ │ ├── ParamCallback.java │ │ │ │ ├── ParamResponseStatus.java │ │ │ │ └── Param.java │ │ │ │ ├── MethodHandler.java │ │ │ │ ├── SimpleJavaJSWebChromeClient.java │ │ │ │ ├── RequestResponseBuilder.java │ │ │ │ ├── Params.java │ │ │ │ └── SimpleJavaJsBridge.java │ │ ├── AndroidManifest.xml │ │ └── assets │ │ │ └── js_native_bridge.js │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── simplejsjavabridge │ │ │ └── lib │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── simplejsjavabridge │ │ └── lib │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':simplejsjavabridgeLib' 2 | -------------------------------------------------------------------------------- /app/simple.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/simple.jks -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/app-release.apk -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SimpleJSJavaBridge 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SimpleJSJavaBridge 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/IJavaCallback2JS.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | /** 4 | * 凡是为js提供的回调都必须继承该接口 5 | * 6 | * Created by niuxiaowei on 16/9/18. 7 | */ 8 | public class IJavaCallback2JS { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/assets/connect_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 链接失败 6 | 7 | 8 | 9 |
链接失败,请检查网络
10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | .svn/* 3 | *.class 4 | #*.apk 5 | bin/ 6 | bin/* 7 | gen/ 8 | gen/* 9 | lint.xml 10 | .DS_Store 11 | .settings/ 12 | .settings/* 13 | .gradle/* 14 | *.iml 15 | build/* 16 | import-summary.txt 17 | .idea/* 18 | local.properties 19 | .git 20 | obj/ 21 | 22 | .gradle/ 23 | .idea/ 24 | build/ 25 | 26 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/test/java/com/bridge/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bridge; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bridge/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.bridge; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/exception/SimpleJSBridgeException.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib.exception; 2 | 3 | /** 4 | * Created by niuxiaowei on 16/9/10. 5 | */ 6 | public class SimpleJSBridgeException extends RuntimeException { 7 | 8 | public SimpleJSBridgeException() { 9 | } 10 | 11 | public SimpleJSBridgeException(String detailMessage) { 12 | super(detailMessage); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/test/java/com/simplejsjavabridge/lib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/niuxiaowei/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/niuxiaowei/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/ResponseStatus.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | /** 4 | * java发送给js的响应状态 5 | * 6 | * Created by niuxiaowei on 16/10/21. 7 | */ 8 | public enum ResponseStatus { 9 | 10 | OK(1,"ok"), FAILED(0,"failed"), FAILED_METHOD_NOT_DEFINED(-1,"method not defined"), FAILED_PARAM_ERROR(-2,"param error") 11 | , FAILED_TIME_OUT(-3,"time out"), FAILED_USER_CANCEL(-4,"user cancel"); 12 | 13 | 14 | private int status; 15 | 16 | private String msg; 17 | 18 | ResponseStatus(int status, String msg) { 19 | this.status = status; 20 | this.msg = msg; 21 | } 22 | 23 | public int getStatus() { 24 | return status; 25 | } 26 | 27 | public String getMsg() { 28 | return msg; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | --> 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/assets/js_native_bridge.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | if (window._JSNativeBridge) { 4 | return; 5 | } 6 | 7 | 8 | var messageHandlers = {}; 9 | 10 | 11 | var responseCallbacks = {}; 12 | var uniqueId = 1; 13 | 14 | 15 | function registerHandler(handlerName, handler) { 16 | messageHandlers[handlerName] = handler; 17 | } 18 | 19 | 20 | /*向native发送请求信息*/ 21 | function _doSendRequest(handlerName, params, responseCallback) { 22 | 23 | var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); 24 | responseCallbacks[callbackId] = responseCallback; 25 | 26 | var request = {}; 27 | request[_JSNativeBridge.request.interfaceName]=handlerName; 28 | request[_JSNativeBridge.request.valuesName] = params; 29 | request[_JSNativeBridge.request.callbackIdName] = callbackId; 30 | _doSend(request); 31 | } 32 | 33 | /*向native发送response(响应信息)*/ 34 | function _doSendResponse(responseId, responseData){ 35 | var response = {}; 36 | response[_JSNativeBridge.response.responseIdName] = responseId; 37 | response[_JSNativeBridge.response.responseName] = responseData; 38 | _doSend(response); 39 | } 40 | 41 | //sendMessage add message, 触发native处理 sendMessage 42 | function _doSend(message) { 43 | if(_isEmpty(_JSNativeBridge.protocol.scheme) || _isEmpty(_JSNativeBridge.protocol.host)){ 44 | throw "_JSNativeBridge.protocol.scheme 或 _JSNativeBridge.protocol.host不能为空"; 45 | } 46 | prompt( _JSNativeBridge.protocol.scheme+'://'+ _JSNativeBridge.protocol.host+'?'+ JSON.stringify(message)); 47 | } 48 | 49 | function _isEmpty(str){ 50 | if(str === null){ 51 | return true; 52 | } 53 | 54 | if (str.replace(/(^s*)|(s*$)/g, "").length ==0){ 55 | return true; 56 | } 57 | } 58 | 59 | 60 | function _doNativeResponse(response){ 61 | if(_JSNativeBridge.debug){ 62 | 63 | console.log(' response='+JSON.stringify(response)); 64 | } 65 | var responseIdValue = response[_JSNativeBridge.response.responseIdName]; 66 | if (responseIdValue) { 67 | responseCallback = responseCallbacks[responseIdValue]; 68 | if (!responseCallback) { 69 | alert('responseCallback doesn\'t exist!'); 70 | return; 71 | } 72 | responseCallback(response[_JSNativeBridge.response.responseName]); 73 | /*移除回调接口*/ 74 | delete responseCallbacks[responseIdValue]; 75 | return true; 76 | } 77 | 78 | return false; 79 | } 80 | 81 | /*处理native的请求*/ 82 | function _doNativeRequest(request){ 83 | var responseCallback; 84 | var callbackId = request[_JSNativeBridge.request.callbackIdName]; 85 | /*native的request携带callback id*/ 86 | if (callbackId) { 87 | responseCallback = function(responseData) { 88 | _doSendResponse(callbackId, 89 | responseData || {} 90 | ); 91 | }; 92 | } 93 | 94 | var handler = _JSNativeBridge._messageHandler; 95 | var handlerName = request[_JSNativeBridge.request.interfaceName]; 96 | if (handlerName) { 97 | /*说明js存在这样的接口*/ 98 | if (messageHandlers[handlerName]) { 99 | handler = messageHandlers[handlerName]; 100 | }else{ 101 | /*否则发送失败的信息*/ 102 | _doSendResponse( callbackId, 103 | { 104 | status: "-1", 105 | msg: "Js can't find correspond method." 106 | } 107 | ); 108 | } 109 | } 110 | try { 111 | /*发送*/ 112 | handler(request[_JSNativeBridge.request.valuesName], responseCallback); 113 | } catch (exception) { 114 | if (typeof console != 'undefined') { 115 | alert("_JSNativeBridge: WARNING: javascript handler threw.", message, exception); 116 | } 117 | } 118 | } 119 | 120 | /*分发从native传递的信息*/ 121 | function _dispatchMessageFromNative(messageJSON) { 122 | setTimeout(function() { 123 | var message = JSON.parse(messageJSON); 124 | if(!_doNativeResponse(message)){ 125 | _doNativeRequest(message); 126 | } 127 | 128 | }); 129 | } 130 | 131 | //暴漏给native的唯一一个通信方法 132 | function _handleMessageFromNative(messageJSON) { 133 | _dispatchMessageFromNative(messageJSON); 134 | } 135 | 136 | var _JSNativeBridge = window._JSNativeBridge = { 137 | registerHandler: registerHandler, 138 | _doSendRequest: _doSendRequest, 139 | _handleMessageFromNative: _handleMessageFromNative, 140 | request:{ 141 | interfaceName:"handlerName", 142 | callbackIdName:"callbackId", 143 | valuesName:"params" 144 | }, 145 | response:{ 146 | responseIdName:"responseId", 147 | responseName:"data", 148 | }, 149 | protocol:{ 150 | scheme:"", 151 | host:"", 152 | }, 153 | debug:true 154 | 155 | }; 156 | 157 | var doc = document; 158 | var readyEvent = doc.createEvent('Events'); 159 | readyEvent.initEvent('JsBridgeInit'); 160 | readyEvent.bridge = _JSNativeBridge; 161 | doc.dispatchEvent(readyEvent); 162 | if(_JSNativeBridge.debug){ 163 | console.log(" -- initbridge end"); 164 | } 165 | })(); 166 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type data 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/assets/bridge.js: -------------------------------------------------------------------------------- 1 | // Uses Node, AMD or browser globals to create a module. This example creates 2 | // a global even when AMD is used. This is useful if you have some scripts 3 | // that are loaded by an AMD loader, but they still want access to globals. 4 | // If you do not need to export a global for the AMD case, 5 | // see returnExports.js. 6 | 7 | // If you want something that will work in other stricter CommonJS environments, 8 | // or if you need to create a circular dependency, see commonJsStrictGlobal.js 9 | 10 | // Defines a module "returnExportsGlobal" that depends another module called 11 | // "b". Note that the name of the module is implied by the file name. It is 12 | // best if the file name and the exported global have matching names. 13 | 14 | // If the 'b' module also uses this type of boilerplate, then 15 | // in the browser, it will create a global .b that is used below. 16 | 17 | 18 | //document.write(''); 19 | 20 | 21 | 22 | 23 | (function (root, factory) { 24 | root.JsBridge = factory(root); 25 | }(typeof window == "undefined" ? this : window, function (win) { 26 | if (!win.document) { return {};} 27 | 28 | 29 | var doc = win.document, 30 | title = doc.title, 31 | ua = navigator.userAgent.toLowerCase(), 32 | platform = navigator.platform.toLowerCase(), 33 | isMacorWin = !(!platform.match("mac") && !platform.match("win")), 34 | isandroid = -1 != ua.indexOf("android"), 35 | isphoneorpad = -1 != ua.indexOf("iphone") || -1 != ua.indexOf("ipad"), 36 | JsBridge = { 37 | usable: false, 38 | init: function (bridge) { 39 | /*注册提供给native的接口*/ 40 | bridge.registerHandler("exam",function(message, responseCallback){ 41 | 42 | var result=document.getElementById("result"); 43 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 44 | responseCallback({ 45 | status: "1", 46 | msg: "ok", 47 | values:{ 48 | msg: "js回调native" 49 | } 50 | }); 51 | }); 52 | 53 | bridge.registerHandler("exam1",function(message, responseCallback){ 54 | 55 | var result=document.getElementById("result"); 56 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 57 | responseCallback({ 58 | status: "1", 59 | msg: "ok", 60 | values:{ 61 | cityName:message.cityName, 62 | cityProvince: message.cityProvince 63 | } 64 | }); 65 | }); 66 | 67 | bridge.registerHandler("exam2",function(message, responseCallback){ 68 | 69 | var result=document.getElementById("result"); 70 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 71 | responseCallback({ 72 | status: "1", 73 | msg: "ok", 74 | values:{ 75 | city:{ 76 | cityName:message.cityName, 77 | cityProvince: message.cityProvince 78 | } 79 | 80 | } 81 | }); 82 | }); 83 | 84 | bridge.registerHandler("exam3",function(message, responseCallback){ 85 | 86 | var result=document.getElementById("result"); 87 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 88 | responseCallback({ 89 | status: "1", 90 | msg: "ok", 91 | values:{ 92 | cityName:'北京', 93 | cityProvince: '北京' 94 | 95 | } 96 | }); 97 | }); 98 | 99 | bridge.registerHandler("exam4",function(message, responseCallback){ 100 | 101 | var result=document.getElementById("result"); 102 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 103 | responseCallback({ 104 | status: "1", 105 | msg: "ok", 106 | values:{ 107 | 108 | } 109 | }); 110 | }); 111 | 112 | return this; 113 | }, 114 | checkUsable: function (methodName, params, cb) { 115 | var _this = this; 116 | if (!window._JSNativeBridge) { 117 | //JS not be injected success 118 | cb({ 119 | status: "-1", 120 | msg: "window._JSNativeBridge is undefined" 121 | }, {}); 122 | return; 123 | } 124 | 125 | try { 126 | window._JSNativeBridge._doSendRequest(methodName, params, cb); 127 | } catch (e) { 128 | cb({status: "-1", msg: e},{}); 129 | } 130 | }, 131 | 132 | test: function(params,cb){ 133 | this.checkUsable("test",{ 134 | "msg": "js发送的请求" 135 | },cb); 136 | }, 137 | 138 | test1: function(params,cb){ 139 | this.checkUsable("test1",{ 140 | "age": 10, 141 | "name":"wangwu" 142 | },cb); 143 | }, 144 | 145 | test2: function(params,cb){ 146 | this.checkUsable("test2",{ 147 | person:{ 148 | "age": 10, 149 | "name":"wangwu" 150 | } 151 | 152 | },cb); 153 | }, 154 | 155 | test3: function(params,cb){ 156 | this.checkUsable("test3",{ 157 | "jiguan":"北京", 158 | person:{ 159 | "age": 10, 160 | "name":"wangwu" 161 | } 162 | 163 | },cb); 164 | }, 165 | test4: function(params,cb){ 166 | this.checkUsable("test4",{ 167 | 168 | },cb); 169 | } 170 | 171 | 172 | }; 173 | 174 | if (window._JSNativeBridge) { 175 | console.log("native injection js success!"); 176 | window._JSNativeBridge.protocol.scheme = 'niu'; 177 | window._JSNativeBridge.protocol.host = 'receive_msg'; 178 | window._JSNativeBridge.debug = true; 179 | JsBridge.init(window._JSNativeBridge); 180 | } else { 181 | console.log("native injection js wrong!"); 182 | document.addEventListener( 183 | 'JsBridgeInit', 184 | function(event) { 185 | console.log('------------------bridge'); 186 | window._JSNativeBridge.protocol.scheme = 'niu'; 187 | window._JSNativeBridge.protocol.host = 'receive_msg'; 188 | event.bridge.debug = true; 189 | JsBridge.init(event.bridge); 190 | }, 191 | false 192 | ); 193 | } 194 | 195 | return JsBridge; 196 | })); 197 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/fragment/WebViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view.fragment; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.text.TextUtils; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.webkit.WebResourceRequest; 12 | import android.webkit.WebResourceResponse; 13 | import android.webkit.WebSettings; 14 | import android.webkit.WebView; 15 | import android.webkit.WebViewClient; 16 | import android.widget.TextView; 17 | 18 | 19 | import com.bridge.R; 20 | 21 | import com.bridge.view.JavaInterfaces4JS; 22 | import com.bridge.view.IInvokeJS; 23 | import com.simplejsjavabridge.lib.IJavaCallback2JS; 24 | import com.simplejsjavabridge.lib.SimpleJavaJsBridge; 25 | import com.simplejsjavabridge.lib.annotation.JavaCallback4JS; 26 | import com.simplejsjavabridge.lib.annotation.Param; 27 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus; 28 | 29 | 30 | /** 31 | * Created by niuxiaowei on 16/5/25. 32 | */ 33 | public class WebViewFragment extends Fragment { 34 | 35 | public static final String WEBVIEW_URL = "webview_url"; 36 | 37 | private WebView mWebView; 38 | private String mUrl; 39 | private TextView mResultView; 40 | 41 | private SimpleJavaJsBridge mSimpleJavaJsBridge; 42 | 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_webview, container, false); 48 | return view; 49 | } 50 | 51 | 52 | @Override 53 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 54 | parseArguments(); 55 | initView(); 56 | initData(); 57 | super.onViewCreated(view, savedInstanceState); 58 | } 59 | 60 | public static Bundle createBundle(String url) { 61 | Bundle args = new Bundle(); 62 | args.putString(WEBVIEW_URL, url); 63 | 64 | return args; 65 | } 66 | 67 | public static WebViewFragment createWebViewFragment(Bundle args) { 68 | WebViewFragment instance = new WebViewFragment(); 69 | instance.setArguments(args); 70 | return instance; 71 | } 72 | 73 | public static WebViewFragment createWebViewFragment(String url) { 74 | return createWebViewFragment(createBundle(url)); 75 | } 76 | 77 | protected void parseArguments() { 78 | Bundle args = getArguments(); 79 | if (args == null) { 80 | return; 81 | } 82 | mUrl = args.getString(WEBVIEW_URL); 83 | 84 | } 85 | 86 | public void initData() { 87 | JavaInterfaces4JS javaInterfaces4JS = new JavaInterfaces4JS(this); 88 | mSimpleJavaJsBridge = new SimpleJavaJsBridge.Builder().addJavaInterface4JS(javaInterfaces4JS) 89 | .setWebView(mWebView) 90 | .setJSMethodName4Java("_JSNativeBridge._handleMessageFromNative") 91 | .setProtocol("niu","receive_msg").create(); 92 | } 93 | 94 | private void showLoading(){ 95 | mResultView.setText("正在调用js的接口......"); 96 | } 97 | 98 | public void setResult(String result){ 99 | if(result != null){ 100 | mResultView.setText(result); 101 | } 102 | } 103 | 104 | public void initView() { 105 | initWebView(); 106 | 107 | mResultView = (TextView)getView().findViewById(R.id.result); 108 | getView().findViewById(R.id.invoke_js_exam).setOnClickListener(new View.OnClickListener() { 109 | @Override 110 | public void onClick(View v) { 111 | showLoading(); 112 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 113 | invokeJS.exam("你好啊js", 7,new IJavaCallback2JS() { 114 | 115 | @JavaCallback4JS 116 | public void test(@ParamResponseStatus("msg")String statusMsg, @Param("msg") String msg) { 117 | mResultView.setText(" 状态信息="+statusMsg+" msg="+msg); 118 | } 119 | 120 | }); 121 | } 122 | }); 123 | 124 | getView().findViewById(R.id.invoke_js_exam1).setOnClickListener(new View.OnClickListener() { 125 | @Override 126 | public void onClick(View v) { 127 | showLoading(); 128 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 129 | IInvokeJS.City city = new IInvokeJS.City(); 130 | city.cityId = 10; 131 | city.cityName = "长治"; 132 | city.cityProvince="山西"; 133 | invokeJS.exam1(city, new IJavaCallback2JS() { 134 | 135 | @JavaCallback4JS 136 | public void test(@Param IInvokeJS.City city1) { 137 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 138 | } 139 | 140 | }); 141 | } 142 | }); 143 | getView().findViewById(R.id.invoke_js_exam2).setOnClickListener(new View.OnClickListener() { 144 | @Override 145 | public void onClick(View v) { 146 | showLoading(); 147 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 148 | IInvokeJS.City city = new IInvokeJS.City(); 149 | city.cityId = 10; 150 | city.cityName = "长治"; 151 | city.cityProvince="山西"; 152 | invokeJS.exam2(city,"中国", new IJavaCallback2JS() { 153 | 154 | @JavaCallback4JS 155 | public void test(@Param(value = "city") IInvokeJS.City city1) { 156 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 157 | } 158 | 159 | }); 160 | } 161 | }); 162 | getView().findViewById(R.id.invoke_js_exam3).setOnClickListener(new View.OnClickListener() { 163 | @Override 164 | public void onClick(View v) { 165 | showLoading(); 166 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 167 | IInvokeJS.City city = new IInvokeJS.City(); 168 | city.cityId = 10; 169 | city.cityName = "长治"; 170 | city.cityProvince="山西"; 171 | invokeJS.exam3(city,"中国", new IJavaCallback2JS() { 172 | 173 | @JavaCallback4JS 174 | public void test(@Param IInvokeJS.City city1) { 175 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 176 | } 177 | 178 | }); 179 | } 180 | }); 181 | 182 | getView().findViewById(R.id.invoke_js_exam4).setOnClickListener(new View.OnClickListener() { 183 | @Override 184 | public void onClick(View v) { 185 | showLoading(); 186 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 187 | 188 | invokeJS.exam4( new IJavaCallback2JS() { 189 | 190 | @JavaCallback4JS 191 | public void test(@ParamResponseStatus("status") String status, @ParamResponseStatus("msg") String statusMsg) { 192 | mResultView.setText("js返回信息: status="+status+" statusMsg="+statusMsg); 193 | } 194 | 195 | }); 196 | } 197 | }); 198 | 199 | 200 | } 201 | 202 | private void initWebView() { 203 | mWebView = (WebView) getView().findViewById(R.id.webView); 204 | 205 | mWebView.setWebViewClient(new WebViewClient() { 206 | 207 | 208 | @Override 209 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 210 | // mWebViewCallBack.shouldOverrideUrlLoading(view, url); 211 | return super.shouldOverrideUrlLoading(view, url); 212 | } 213 | 214 | @Override 215 | public void onReceivedError(WebView view, int errorCode, 216 | String description, String failingUrl) { 217 | super.onReceivedError(view, errorCode, description, failingUrl); 218 | 219 | } 220 | 221 | 222 | @Override 223 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 224 | super.onPageStarted(view, url, favicon); 225 | // mWebViewCallBack.onPageStarted(view, url, favicon); 226 | 227 | } 228 | 229 | @Override 230 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 231 | return super.shouldInterceptRequest(view, request); 232 | } 233 | }); 234 | 235 | 236 | WebSettings settings = mWebView.getSettings(); 237 | settings.setJavaScriptEnabled(true); 238 | settings.setPluginState(WebSettings.PluginState.ON); 239 | settings.setBuiltInZoomControls(true); 240 | settings.setBlockNetworkImage(false); 241 | settings.setUseWideViewPort(true); 242 | settings.setLoadWithOverviewMode(true); 243 | settings.setDefaultTextEncodingName("UTF-8"); 244 | settings.setLoadsImagesAutomatically(true); 245 | 246 | // settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); 247 | 248 | /* 解决空白页问题 */ 249 | settings.setDomStorageEnabled(true); 250 | settings.setCacheMode(WebSettings.LOAD_DEFAULT); 251 | settings.setJavaScriptCanOpenWindowsAutomatically(false); 252 | 253 | 254 | if (!TextUtils.isEmpty(mUrl)) { 255 | mWebView.loadUrl(mUrl); 256 | } 257 | } 258 | 259 | 260 | } 261 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/SimpleJavaJSWebChromeClient.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.os.Message; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.webkit.ConsoleMessage; 9 | import android.webkit.GeolocationPermissions; 10 | import android.webkit.JsPromptResult; 11 | import android.webkit.JsResult; 12 | import android.webkit.PermissionRequest; 13 | import android.webkit.WebChromeClient; 14 | import android.webkit.WebStorage; 15 | import android.webkit.WebView; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.InputStreamReader; 21 | 22 | 23 | /** 24 | *该类的主要作用是在{@link #onJsPrompt(WebView, String, String, String, JsPromptResult)}对js传递的数据交给{@link SimpleJavaJsBridge}进行 25 | * 处理 并且在{@link #onProgressChanged(WebView, int)}方法里把内嵌的js文件注入h5页面中,这样就省得使用者在关心这些环节了。 26 | * {@link #mWebChromeClient}的主要作用是使用者直接把自己生成的{@link WebChromeClient}传递进来,该类负责调用相应的方法 27 | */ 28 | public class SimpleJavaJSWebChromeClient extends WebChromeClient { 29 | 30 | 31 | private WebChromeClient mWebChromeClient; 32 | 33 | public boolean mIsInjectedJS; 34 | 35 | private SimpleJavaJsBridge mSimpleJavaJsBridge; 36 | 37 | SimpleJavaJSWebChromeClient(WebChromeClient webChromeClient, SimpleJavaJsBridge simpleJavaJsBridge) { 38 | mWebChromeClient = webChromeClient; 39 | mSimpleJavaJsBridge = simpleJavaJsBridge; 40 | } 41 | 42 | private static void webViewLoadLocalJs(WebView view, String path) { 43 | String jsContent = assetFile2Str(view.getContext(), path); 44 | view.loadUrl("javascript:" + jsContent); 45 | } 46 | 47 | private static String assetFile2Str(Context c, String urlStr) { 48 | InputStream in = null; 49 | try { 50 | in = c.getAssets().open(urlStr); 51 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); 52 | String line = null; 53 | StringBuilder sb = new StringBuilder(); 54 | do { 55 | line = bufferedReader.readLine(); 56 | if (line != null) { 57 | line = line.replaceAll("\\t", " "); 58 | if (!line.matches("^\\s*\\/\\/.*")) { 59 | sb.append(line); 60 | } 61 | } 62 | } while (line != null); 63 | 64 | bufferedReader.close(); 65 | in.close(); 66 | 67 | return sb.toString(); 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } finally { 71 | if (in != null) { 72 | try { 73 | in.close(); 74 | } catch (IOException e) { 75 | } 76 | } 77 | } 78 | return null; 79 | } 80 | 81 | @Override 82 | public void onProgressChanged(WebView view, int newProgress) { 83 | 84 | Log.i("test", "--new pro=" + newProgress); 85 | 86 | // if (newProgress <= 98) { 87 | // mIsInjectedJS = false; 88 | // } else if (!mIsInjectedJS) { 89 | // mIsInjectedJS = true; 90 | // webViewLoadLocalJs(view, "js_native_bridge.js"); 91 | // } 92 | // 93 | if (checkObjectNotNull(mWebChromeClient)) { 94 | mWebChromeClient.onProgressChanged(view, newProgress); 95 | } 96 | super.onProgressChanged(view, newProgress); 97 | 98 | 99 | } 100 | 101 | private boolean checkObjectNotNull(Object object) { 102 | return object != null; 103 | } 104 | 105 | @Override 106 | public boolean onJsAlert(WebView view, String url, String message, JsResult result) { 107 | // result.cancel(); 108 | if (checkObjectNotNull(mWebChromeClient)) { 109 | return mWebChromeClient.onJsAlert(view, url, message, result); 110 | } 111 | return super.onJsAlert(view, url, message, result); 112 | } 113 | 114 | 115 | @Override 116 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 117 | if (mSimpleJavaJsBridge.parseJsonFromJs(message)) { 118 | /*必须得有这行代码,否则会阻塞当前h5页面*/ 119 | result.cancel(); 120 | return true; 121 | } 122 | 123 | if (checkObjectNotNull(mWebChromeClient)) { 124 | return mWebChromeClient.onJsPrompt(view, url, message, defaultValue, result); 125 | } 126 | 127 | return super.onJsPrompt(view, url, message, defaultValue, result); 128 | } 129 | 130 | @Override 131 | public void onReceivedTitle(WebView view, String title) { 132 | if (checkObjectNotNull(mWebChromeClient)) { 133 | mWebChromeClient.onReceivedTitle(view, title); 134 | } 135 | super.onReceivedTitle(view, title); 136 | } 137 | 138 | @Override 139 | public void onReceivedIcon(WebView view, Bitmap icon) { 140 | if (checkObjectNotNull(mWebChromeClient)) { 141 | mWebChromeClient.onReceivedIcon(view, icon); 142 | } 143 | super.onReceivedIcon(view, icon); 144 | } 145 | 146 | @Override 147 | public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { 148 | if (checkObjectNotNull(mWebChromeClient)) { 149 | mWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed); 150 | } 151 | super.onReceivedTouchIconUrl(view, url, precomposed); 152 | } 153 | 154 | @Override 155 | public void onShowCustomView(View view, CustomViewCallback callback) { 156 | if (checkObjectNotNull(mWebChromeClient)) { 157 | mWebChromeClient.onShowCustomView(view, callback); 158 | } 159 | super.onShowCustomView(view, callback); 160 | } 161 | 162 | @Override 163 | public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { 164 | if (checkObjectNotNull(mWebChromeClient)) { 165 | mWebChromeClient.onShowCustomView(view, requestedOrientation, callback); 166 | } 167 | super.onShowCustomView(view, requestedOrientation, callback); 168 | } 169 | 170 | @Override 171 | public void onHideCustomView() { 172 | if (checkObjectNotNull(mWebChromeClient)) { 173 | mWebChromeClient.onHideCustomView(); 174 | } 175 | super.onHideCustomView(); 176 | } 177 | 178 | @Override 179 | public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 180 | if (checkObjectNotNull(mWebChromeClient)) { 181 | return mWebChromeClient.onCreateWindow(view, isDialog, isUserGesture, resultMsg); 182 | } 183 | return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg); 184 | } 185 | 186 | @Override 187 | public void onRequestFocus(WebView view) { 188 | if (checkObjectNotNull(mWebChromeClient)) { 189 | mWebChromeClient.onRequestFocus(view); 190 | } 191 | super.onRequestFocus(view); 192 | } 193 | 194 | @Override 195 | public void onCloseWindow(WebView window) { 196 | if (checkObjectNotNull(mWebChromeClient)) { 197 | mWebChromeClient.onCloseWindow(window); 198 | } 199 | super.onCloseWindow(window); 200 | } 201 | 202 | @Override 203 | public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { 204 | if (checkObjectNotNull(mWebChromeClient)) { 205 | return mWebChromeClient.onJsConfirm(view, url, message, result); 206 | } 207 | return super.onJsConfirm(view, url, message, result); 208 | } 209 | 210 | @Override 211 | public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) { 212 | if (checkObjectNotNull(mWebChromeClient)) { 213 | return mWebChromeClient.onJsBeforeUnload(view, url, message, result); 214 | } 215 | return super.onJsBeforeUnload(view, url, message, result); 216 | } 217 | 218 | @Override 219 | public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater) { 220 | if (checkObjectNotNull(mWebChromeClient)) { 221 | mWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); 222 | } 223 | super.onExceededDatabaseQuota(url, databaseIdentifier, quota, estimatedDatabaseSize, totalQuota, quotaUpdater); 224 | } 225 | 226 | @Override 227 | public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) { 228 | if (checkObjectNotNull(mWebChromeClient)) { 229 | mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); 230 | } 231 | super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater); 232 | } 233 | 234 | @Override 235 | public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { 236 | if (checkObjectNotNull(mWebChromeClient)) { 237 | mWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback); 238 | } 239 | super.onGeolocationPermissionsShowPrompt(origin, callback); 240 | } 241 | 242 | @Override 243 | public void onGeolocationPermissionsHidePrompt() { 244 | if (checkObjectNotNull(mWebChromeClient)) { 245 | mWebChromeClient.onGeolocationPermissionsHidePrompt(); 246 | } 247 | super.onGeolocationPermissionsHidePrompt(); 248 | } 249 | 250 | @Override 251 | public void onPermissionRequest(PermissionRequest request) { 252 | if (checkObjectNotNull(mWebChromeClient)) { 253 | mWebChromeClient.onPermissionRequest(request); 254 | } 255 | super.onPermissionRequest(request); 256 | } 257 | 258 | @Override 259 | public void onPermissionRequestCanceled(PermissionRequest request) { 260 | if (checkObjectNotNull(mWebChromeClient)) { 261 | mWebChromeClient.onPermissionRequestCanceled(request); 262 | } 263 | super.onPermissionRequestCanceled(request); 264 | } 265 | 266 | @Override 267 | public boolean onJsTimeout() { 268 | if (checkObjectNotNull(mWebChromeClient)) { 269 | mWebChromeClient.onJsTimeout(); 270 | } 271 | return super.onJsTimeout(); 272 | } 273 | 274 | @Override 275 | public void onConsoleMessage(String message, int lineNumber, String sourceID) { 276 | if (checkObjectNotNull(mWebChromeClient)) { 277 | mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID); 278 | } 279 | super.onConsoleMessage(message, lineNumber, sourceID); 280 | } 281 | 282 | @Override 283 | public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 284 | if (checkObjectNotNull(mWebChromeClient)) { 285 | mWebChromeClient.onConsoleMessage(consoleMessage); 286 | } 287 | return super.onConsoleMessage(consoleMessage); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/RequestResponseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | import android.text.TextUtils; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | /** 9 | * java与js之间可以进行互相通信,主动发起通信时,传输的数据我们称作request(请求数据)对应{@link Request},当把处理结果进行返回时的数据我们称作response(响应数据) 10 | * 对应{@link Response},因此该类的主要作用就是用来构建request或者response数据的。每次只能构建request或者response其中一种数据, 11 | * {@link RequestResponseBuilder}可以从json中解析出相应的数据,也可以转化为json 12 | * 13 | * Created by niuxiaowei on 16/9/14. 14 | */ 15 | public class RequestResponseBuilder { 16 | 17 | 18 | /** 19 | * 是否是构建request请求 20 | */ 21 | private boolean mIsBuildRequest; 22 | 23 | /** 24 | * 请求数据 25 | */ 26 | private Request mRequest; 27 | /** 28 | * 响应数据 29 | */ 30 | private Response mResponse; 31 | 32 | public RequestResponseBuilder(boolean isBuildRequest){ 33 | this(isBuildRequest,null); 34 | } 35 | 36 | /** 37 | * @param isBuildRequest 是否是构造请求数据 38 | * @param data json数据 39 | */ 40 | public RequestResponseBuilder(boolean isBuildRequest, JSONObject data) { 41 | mIsBuildRequest = isBuildRequest; 42 | if (mIsBuildRequest) { 43 | mRequest = new Request(); 44 | if(data != null){ 45 | 46 | mRequest.parseRequest(data); 47 | } 48 | } else { 49 | mResponse = new Response(); 50 | if(data != null){ 51 | 52 | mResponse.parseResponse(data); 53 | } 54 | } 55 | } 56 | 57 | 58 | /** 59 | * 60 | * 请求数据格式: 61 | * 62 | *
 63 |      *    {
 64 |      *      "handlerName":"test",
 65 |      *      "callbackId":"c_111111",
 66 |      *      "params":{
 67 |      *          ....
 68 |      *      }
 69 |      *    }
 70 |      *
 71 |      *    hanlerName 代表java与js之间给对方暴漏的接口的名称,
 72 |      *    callbackId 代表对方在发起请求时,会为回调方法生产一个唯一的id值,它就代表这个唯一的id值
 73 |      *    params     代表传递的数据
 74 |      * 
75 | * } 76 | */ 77 | private static class Request { 78 | 79 | private static String sRequestInterfaceName = "handlerName"; 80 | private static String sRequestCallbackIdName = "callbackId"; 81 | private static String sRequestValuesName = "params"; 82 | 83 | /*request相关的属性*/ 84 | private String interfaceName; 85 | private String callbackId; 86 | private JSONObject requestValues; 87 | private IJavaCallback2JS iJavaCallback2JS; 88 | 89 | private static void init(String requestInterfaceName, String requestCallbackIdName, String requestValuesName) { 90 | if (!TextUtils.isEmpty(requestCallbackIdName)) { 91 | Request.sRequestCallbackIdName = requestCallbackIdName; 92 | } 93 | 94 | if (!TextUtils.isEmpty(requestValuesName)) { 95 | Request.sRequestValuesName = requestValuesName; 96 | } 97 | if (!TextUtils.isEmpty(requestInterfaceName)) { 98 | Request.sRequestInterfaceName = requestInterfaceName; 99 | } 100 | } 101 | 102 | private void parseRequest(JSONObject json) { 103 | if (json != null) { 104 | callbackId = json.optString(sRequestCallbackIdName); 105 | interfaceName = json.optString(sRequestInterfaceName); 106 | requestValues = json.optJSONObject(sRequestValuesName); 107 | } 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | JSONObject jsonObject = new JSONObject(); 113 | try { 114 | jsonObject.put(sRequestCallbackIdName, callbackId); 115 | jsonObject.put(sRequestInterfaceName, interfaceName); 116 | if (requestValues != null) { 117 | jsonObject.put(sRequestValuesName, requestValues); 118 | } 119 | } catch (JSONException e) { 120 | e.printStackTrace(); 121 | } 122 | return "'" + jsonObject.toString() + "'"; 123 | } 124 | } 125 | 126 | /** 127 | * 128 | * response数据格式: 129 | *
130 |      *  {
131 |      *      "responseId":"iii",
132 |      *      "data":{
133 |      *          "status":"1",
134 |      *          "msg":"ok",
135 |      *          "values":{
136 |      *              ......
137 |      *          }
138 |      *      }
139 |      *  }
140 |      *
141 |      *  responseId 代表request中的callbackId
142 |      *  data       代表响应的数据
143 |      *  status     代表响应状态
144 |      *  msg        代表响应状态对应的消息
145 |      *  values     代表响应数据包含的值
146 |      * 
147 | */ 148 | private static class Response { 149 | private static String sResponseIdName = "responseId"; 150 | private static String sResponseValuesName = "values"; 151 | private static String sResponseName = "data"; 152 | 153 | private String responseId; 154 | private JSONObject response = new JSONObject(); 155 | private JSONObject responseValues; 156 | 157 | private static void init(String responseIdName, String responseName, String responseValuesName) { 158 | if (!TextUtils.isEmpty(responseValuesName)) { 159 | 160 | Response.sResponseValuesName = responseValuesName; 161 | } 162 | if (!TextUtils.isEmpty(responseIdName)) { 163 | Response.sResponseIdName = responseIdName; 164 | } 165 | 166 | if (!TextUtils.isEmpty(responseName)) { 167 | 168 | Response.sResponseName = responseName; 169 | } 170 | 171 | } 172 | 173 | private void parseResponse(JSONObject json) { 174 | if (json != null) { 175 | responseId = json.optString(sResponseIdName); 176 | response = json.optJSONObject(sResponseName); 177 | if (response != null) { 178 | responseValues = response.optJSONObject(sResponseValuesName); 179 | } 180 | } 181 | } 182 | 183 | @Override 184 | public String toString() { 185 | JSONObject jsonObject = new JSONObject(); 186 | try { 187 | jsonObject.put(sResponseIdName, responseId); 188 | if (responseValues != null) { 189 | response.put(sResponseValuesName, responseValues); 190 | } 191 | jsonObject.put(sResponseName, response); 192 | } catch (JSONException e) { 193 | e.printStackTrace(); 194 | } 195 | 196 | return "'" + jsonObject.toString() + "'"; 197 | } 198 | } 199 | 200 | /** 201 | * 获取请求时为回调函数生成的 callbackId 202 | * @return 203 | */ 204 | public String getCallbackId(){ 205 | return mRequest == null?null: mRequest.callbackId; 206 | } 207 | 208 | /** 209 | * 获取请求的接口的名字 210 | * @return 211 | */ 212 | public String getInterfaceName() { 213 | return mRequest == null ? null : mRequest.interfaceName; 214 | } 215 | 216 | public void setRequestCallback(IJavaCallback2JS callback) { 217 | initRequest(); 218 | this.mRequest.iJavaCallback2JS = callback; 219 | } 220 | 221 | private void initRequest() { 222 | if (mRequest == null) { 223 | mRequest = new Request(); 224 | } 225 | } 226 | 227 | /** 228 | * 设置请求的接口的名字 229 | * @param interfaceName 230 | */ 231 | public void setInterfaceName(String interfaceName) { 232 | initRequest(); 233 | this.mRequest.interfaceName = interfaceName; 234 | } 235 | 236 | /** 237 | * 为回调方法设置回调id 238 | * @param callbackId 239 | */ 240 | public void setCallbackId(String callbackId) { 241 | initRequest(); 242 | this.mRequest.callbackId = callbackId; 243 | } 244 | 245 | 246 | /** 247 | * 获取request或者response的 values值 248 | * @return 249 | */ 250 | public JSONObject getValues() { 251 | if (mIsBuildRequest) { 252 | return mRequest == null ? null : mRequest.requestValues; 253 | } else { 254 | return mResponse == null ? null : mResponse.responseValues; 255 | } 256 | } 257 | 258 | /** 259 | * 往request或者response中存放 数据 260 | * @param key 261 | * @param value 262 | */ 263 | public void putValue(String key, Object value) { 264 | if(TextUtils.isEmpty(key) || value == null){ 265 | return; 266 | } 267 | JSONObject values = null; 268 | if (mIsBuildRequest) { 269 | initRequest(); 270 | if (mRequest.requestValues == null) { 271 | mRequest.requestValues = new JSONObject(); 272 | } 273 | values = mRequest.requestValues; 274 | } else { 275 | initResponse(); 276 | if (mResponse.responseValues == null) { 277 | mResponse.responseValues = new JSONObject(); 278 | } 279 | values = mResponse.responseValues; 280 | } 281 | 282 | try { 283 | values.put(key, value); 284 | } catch (JSONException e) { 285 | e.printStackTrace(); 286 | } 287 | 288 | } 289 | 290 | public IJavaCallback2JS getCallback() { 291 | return mRequest == null ? null : mRequest.iJavaCallback2JS; 292 | } 293 | 294 | /** 295 | * @param responseIdName 296 | * @param responseName 297 | * @param responseValuesName 298 | */ 299 | public static void init(String responseIdName, String responseName, String responseValuesName, String requestInterfaceName, String requestCallbackIdName, String requestValuesName) { 300 | Response.init(responseIdName, responseName, responseValuesName); 301 | Request.init(requestInterfaceName, requestCallbackIdName, requestValuesName); 302 | } 303 | 304 | 305 | private void initResponse() { 306 | if (mResponse == null) { 307 | mResponse = new Response(); 308 | } 309 | } 310 | 311 | public String getResponseId() { 312 | return mResponse == null ? null : mResponse.responseId; 313 | } 314 | 315 | public void setResponseId(String responseId) { 316 | initResponse(); 317 | this.mResponse.responseId = responseId; 318 | } 319 | 320 | /** 321 | * 获取response的 状态数据 322 | * @return 323 | */ 324 | public JSONObject getResponseStatus() { 325 | return mResponse == null ? null : mResponse.response; 326 | } 327 | 328 | /** 329 | * 往response中存放 数据 330 | * @param key 331 | * @param value 332 | */ 333 | public void putResponseStatus(String key, Object value) { 334 | if(TextUtils.isEmpty(key) || value == null){ 335 | return; 336 | } 337 | initResponse(); 338 | try { 339 | mResponse.response.put(key, value); 340 | } catch (JSONException e) { 341 | e.printStackTrace(); 342 | } 343 | } 344 | 345 | 346 | /** 347 | * 从json中创建一个{@link RequestResponseBuilder}对象,其实最终创建的是一个 request或者response 348 | * @param json 349 | * @return 350 | */ 351 | static RequestResponseBuilder create(JSONObject json) { 352 | if (json == null) { 353 | return null; 354 | } 355 | RequestResponseBuilder requestResponseBuilder = null; 356 | /*响应数据*/ 357 | if (json.has(Response.sResponseIdName)) { 358 | requestResponseBuilder = new RequestResponseBuilder(false, json); 359 | } else { 360 | requestResponseBuilder = new RequestResponseBuilder(true, json); 361 | } 362 | 363 | return requestResponseBuilder; 364 | } 365 | 366 | /** 367 | * 是否构建的时request数据 368 | * @return 369 | */ 370 | public boolean isBuildRequest() { 371 | return mIsBuildRequest; 372 | } 373 | 374 | @Override 375 | public String toString() { 376 | if (mIsBuildRequest) { 377 | return mRequest == null ? super.toString() : mRequest.toString(); 378 | } else { 379 | return mResponse == null ? super.toString() : mResponse.toString(); 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #前言 2 | 最近接触android中js与java交互的东西很多,当然它们之间的交互方式有几种,但是我觉得这几种交互方式都存在一定的不足,这是我决定编写SimpleJavaJsBridge这个库的关键原因。 3 | 4 | 我会按以下顺序进行本文章: 5 | 6 | 1. 现有js与java通信方案及不足 7 | 2. js与java完美通信方案设计 8 | 3. SimpleJavaJsBridge 9 | 10 | 现在进入正题 11 | #1. 现有js与java通信方案及不足 12 | 先来说明一点js与java通信,指的是js既可以给java发送消息,同时java也可以给js发送消息。那就来屡屡它们之间的通信方案。 13 | 14 | **1.1 java给js发送消息** 15 | 16 | 官方唯一指定方法是通过webview的loadUrl(String)方法进行的,看下的伪代码: 17 | 18 | //例子:调用js的test(param)方法 19 | webView.loadUrl("javascript:test(1)"); 20 | 21 | 22 | 23 | 调用方法非常的简单,"javascript:"+js方法的名字+方法的参数值拼接成一个字符串就可以给js发送消息了,犹如是在直接调用js的方法。 24 | 25 | **1.2 js给java发送消息** 26 | 27 | js给java发送消息实际上只有2种方案,依次来分析下这2种方案。 28 | 29 | **1.2.1 官方方法** 30 | 31 | 先看下伪代码: 32 | 33 | //该类封装了提供给js调用的方法 34 | public class JSBridge{ 35 | //提供给js的方法 36 | public void invokeByJs(String msg){ 37 | 38 | } 39 | } 40 | 41 | //把JSBridge对象注入WebView中,同时起一个别名 42 | webView.addJavascriptInterface(new JSBridge(),"jsBridge"); 43 | 44 | //js调用java方法 45 | window.jsBridge.invokeByJs('hello java'); 46 | 47 | 48 | 49 | 这种方法其实是把一个对象注入到WebView中,js给java发送消息的方式是 50 | 51 | window.注入WebView的java对象的所对应name值.javaMethod(param...); 52 | 53 | 这其实也犹如在java代码中调用java的方法,因为java提供给js的方法名,方法参数是啥,js在发送消息时,方法名与参数必须保持一致,这也是这些java代码不能进行混淆的原因。 54 | 55 | 但是这种方法存在一个严重的漏洞,虽然官方在android4.4的时候给出了相应的解决方案,但是android4.4以下的版本还得解决该漏洞,因此一些巨人们就开始琢磨着解决这个坑,第二种方法由此诞生。 56 | 57 | **1.2.2 js传递约定好的字符串给java** 58 | 59 | 这种方案的主要原理是: 60 | - 找到一个js可以给java发送消息的入口(这个入口有onJsPrompt,onJsAlert等等) 61 | - js通过入口把消息按既定好的规则拼接成字符串传递给java 62 | - java按照既定好的规则对字符串进行解析 63 | - 根据解析数据,通过反射来调用自己相应方法 64 | 65 | 这种方法使用起来要比官方方法(第一种方法)麻烦。 66 | 67 | **1.3 存在的不足** 68 | 69 | 上面介绍了js与java的通信方法,那我就来分析下我认为存在的不足。 70 | 71 | **1.3.1 java给js发送消息方法和js给java发送消息的官方方法存在的不足** 72 | 73 | **1.3.1.1 强依赖** 74 | 75 | java给js发送消息的方法,和js给java发送消息的官方方法都存在着**强依赖**的问题,都要高度依赖对方的方法名字,方法参数。强依赖发生于同一模块内,个人觉得不是问题甚至是高内聚的体现。但是java与js可以说是处于两个不同的模块或者叫两个不同世界,只要js提供给java的方法发生变化,java也得改动,同理java提供给js的方法也如此。处于两个不同模块知道对方的细节越少越好,这样耦合性就会降低,耦合性降低的好处就不用说了。 76 | 77 | **1.3.1.2 强依赖导致js需要兼容不同的系统** 78 | 79 | 先看段伪代码: 80 | 81 | function location(){ 82 | //是ios系统,采用给ios发送消息的方法 83 | if(isIOS){ 84 | 给ios发送消息; 85 | }else if(isAndroid){ 86 | 给android发送消息; 87 | } 88 | } 89 | 上面的代码展示的是js使用native的定位功能的代码,因为js在给不同的系统发送消息的方式不一样,就会出现if else if 这样的兼容语句。当前js代码只被ios和android使用,假如还会被wp或pc来使用,那if else if岂不是要恶心死。产生该问题的主要原因是:js代码在针对不同的系统自己独有的通信方式进行通信。 90 | 91 | **1.3.1.3 给不存在的接口发送消息没反馈** 92 | 93 | java在给js的一个不存在接口发送消息时,java根本不知道该接口不存在,java只会傻傻的等待。同理js在给java的一个不存在接口发送消息时,js是可以通过捕获异常来知道该接口不存在,但是这不是最好的解决方案。 94 | 给不存在接口发送消息没反馈会导致js代码充斥着if else if语句,看段伪代码: 95 | 96 | //调用java的定位方法 97 | function location(){ 98 | //1.1版本以上才会调用定位功能 99 | if(androidAppVersion > '1.1'){ 100 | 发送消息给java; 101 | }else{ 102 | 给用户提示,暂不支持定位功能; 103 | } 104 | } 105 | 106 | 这是一段调用java进行定位的js代码,android app在版本1.1的时候才增加了定位的功能,因此对于1.1以下版本是不支持这功能的,因此js代码里面非常有必要根据版本号进行判断。这只是由于版本问题导致if else if的一个小小的缩影。还有一些其他情况导致if else if的产生比如一份js代码被多个业务使用。 107 | 108 | **1.3.2 js给java发送消息的第二种方法存在不足** 109 | 110 | 上文提到的js给java发送消息的第二种方法,它解决了存在的漏洞,但是这种方法,使用起来要比第一种方法复杂,java会多做以下工作: 111 | - 解析js传递给java的字符串,把调用的接口,参数解析出来 112 | - 把调用的接口,参数映射到相应的方法 113 | 114 | 不论js传递给java的字符串是json格式还是其他格式,解析这样的字符串肯定是一件无趣的重复的体力劳动。 115 | 116 | 117 | 若想解决以上的问题,我们有必要设计一套完美的通信方案。 118 | 119 | #2. js与java完美通信方案设计 120 | **2.1 一套完美的js与java的通信方案应满足以下几点:** 121 | 122 | - js与java知道对方的细节越少越好,越少它们的耦合性越低。那到底多少为好呢?我个人觉得互相暴漏给对方一个接口足矣。这样js与native的通信就类似于通过一个管道通信或者说类似于socket通信(降低强依赖) 123 | 124 | - js与java之间通信,需要定义好一套通信协议或者叫通信规则,在管道之间传递通信协议。这样它们之间的通信是针对一套定义好的协议进行的,而不是针对每个系统自己独有的通信方式(好处js就不会出现兼容不同的系统的if else if代码) 125 | 126 | - 主动发送消息给对方时,对方必须对该消息予以反馈,即使主动发送消息者对反馈消息不感兴趣,(反馈信息可以去掉由于版本兼容等带来的if else if兼容代码) 127 | 128 | 129 | **2.2 那我们就开始设计js与java之间的通信方案** 130 | 131 | **2.2.1 互相暴漏给对方一个接口** 132 | 133 | - js为java提供一个唯一的接口,这个接口可以是在java端写死的,也可以是js传递过来的(这样更灵活)。所有发送给js的消息(请求消息和反馈消息)都通过该接口 134 | - java为js提供的一个唯一的接口,因为官方的方法存在漏洞,我们采用在onJsPrompt方法中接收js发送的所有消息,当然大家还可以选择其他方法来接收js的消息,这不是重点。 135 | 136 | **2.2.2 js与java之间通信协议的制定** 137 | 138 | js与java之间的通信特别类似于网络请求,主动发起消息的行为可以称为request(请求消息),对该消息的反馈可以称为response(响应消息)。 139 | 140 | **request** 141 | 142 | 一个request封装了请求对方的哪个接口,以及该接口所需要的参数。 143 | 144 | **response** 145 | 146 | 一个response封装了状态信息(可以知道处理的结果是成功还是失败)和处理结果。 147 | 148 | **如何接收对方发送的response消息?** 149 | 150 | 大家都应该都会想到,在发送消息的时候传递一个回调接口就行了,但是因为js与java之间是跨语言的,尤其是java是不可能把回调接口传递给js,js虽然可以传递过来但是会有问题,所以这时候有一种解决办法: 151 | - 在给对方发送request消息时,为回调接口生成一个唯一的id值,把id值存入request中发出。 152 | - 同时把回调接口缓存起来。 153 | - 在接收到response时,从response解析这个id值,根据id值查找到回调接口。 154 | 155 | 因此request和response中还得包含回调id这个值。 156 | 157 | **通信协议的格式** 158 | 159 | request数据格式: 160 | 161 | { 162 | //接口名称 163 | "interfaceName":"test", 164 | //回调id值 165 | "callbackId":"c_111111", 166 | //传递的参数 167 | "params":{ 168 | .... 169 | } 170 | } 171 | 172 | response数据格式: 173 | 174 | { 175 | //回调id,同时这也是response的标志 176 | "responseId":"c_111111", 177 | //response数据 178 | "data":{ 179 | //状态数据 180 | "status":"1", 181 | "msg":"ok", 182 | //response的处理结果数据 183 | "values":{ 184 | ...... 185 | } 186 | } 187 | } 188 | 189 | 到此通信协议就已经定义好了。 190 | 191 | **2.2.3 让繁琐的无趣的重复的苦力活儿不再有** 192 | 193 | 大家可以看到通信协议request和response都是json格式,从json中解析数据或者把数据封装为json都是重复的苦力活儿。 194 | 这也是我一直想着力解决的痛点,解决之道是从retrofit中获得启发的,应用注解来解决以上问题。 195 | 196 | 关于js与java完美通信的设计思想到此为止,这也是SimpleJavaJsBridge这个库的核心思想,那我们就来看下SimpleJavaJsBridge。 197 | 198 | #3. SimpleJavaJsBridge 199 | SimpleJavaJsBridge我为什么要起一个这样的名字,首先它解决了上文中提到的**让繁琐的无趣的重复的苦力活儿不再有**的问题,对于不管是从json中解析数据还是把数据封装成json,使用者都不需要关心,让使用者很省心;并且它使用起来也非常的简单,在稍后的例子中大家会体会到,所以用了simple这个词儿。通过它java可以给js发送消息,并且接收js的响应消息;同时js也可以给java发送消息,同样接收java的响应消息。因此它是java与js之间通信的桥梁,因此它的名字叫为SimpleJavaJsBridge。 200 | 201 | **3.1 如何解决繁琐的无趣的重复的苦力活儿?** 202 | 203 | 解决这个问题思路来自于鼎鼎有名的Retrofit,Retrofit通过注解的方式解决了构建request和解析response的问题,因此注解也可以解决我现在遇到的问题。那我们就来认识下这些注解。 204 | **InvokeJSInterface**用来标注java给js发送消息的方法,它的value值代表js提供的功能的接口名字 205 | 206 | **JavaCallback4JS**用来标注java提供给js的回调方法的 207 | 208 | **JavaInterface4JS**用来标注java提供给js的接口,它的value值代表功能的接口名字 209 | 210 | **Param**用来标注参数或者类的实例属性,它的value值代表参数被存入json中的key值,它的needConvert代表当前的参数是否需要进行转换,因为通过JsonObject类往json中存放的数据是有要求的,JsonObject中只能存放基本数据和JsonObject和JsonArray这些数据类型,对于其他的类型就得进行转换了。因此只要是不能直接通过JsonObject存放的类型该值必须为true 211 | 212 | **ParamCallback**用来标注回调类型的参数,比如发送request给js的方法中,需要有一个回调参数,那这个参数必须用它来标注 213 | 214 | **ParamResponseStatus**用来标注响应状态类型的参数,比如:statusCode,StatusMsg这些参数,它的value值是json中的key值。 215 | 216 | 217 | **3.2 SimpleJavaJsBridge使用** 218 | 219 | **3.2.1 构建一个SimpleJavaJsBridge实例** 220 | 221 | SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder() 222 | .addJavaInterface4JS(javaInterfaces4JS) 223 | .setWebView(webView) 224 | .setJSMethodName4Java("_JSBridge._handleMessageFromNative") 225 | .setProtocol("niu","receive_msg").create(); 226 | 227 | 通过SimpleJavaJsBridge.Builder来构建一个SimpleJavaJsBridge对象, 228 | - addJavaInterface4JS用来添加java提供给js的接口 229 | - setWebView 设置WebView这是必须进行设置的 230 | - setJSMethodName4Java 设置js为java唯一暴漏的方法名字 231 | - setProtocol设置协议字段,这也是必须的,这个字段主要是为了ios而设置的 232 | 233 | 当然还可以调用其他的一些方法对SimpleJavaJsBridge进行设置 234 | 235 | **3.2.2 js给java发送消息** 236 | 237 | **js给java的无参接口发送消息** 238 | 239 | /** * 给js发送响应消息的接口*/ 240 | public interface IResponseStatusCallback { 241 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg); 242 | } 243 | 244 | //java提供给js的"tes4"接口,@ParamCallback标注的是给js发送消息的回调 245 | @JavaInterface4JS("test4") 246 | public void test3(@ParamCallback IResponseStatusCallback jsCallback) { 247 | 进行相应处理...; 248 | //给js发送响应消息 249 | jsCallback.callbackResponse(1, "ok"); 250 | } 251 | 252 | //下面是js代码,js给java的"test4"接口发送消息 253 | _JSNativeBridge._doSendRequest("test4", {}, function(responseData){ 254 | 255 | }); 256 | 257 | **js给java的有参接口发送消息** 258 | 259 | /** * 给js发送响应消息的接口*/ 260 | public interface IResponseStatusCallback { 261 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg); 262 | } 263 | 264 | /** * 必须有无参构造函数 ,只有被@Param注解的属性才会存入json中*/ 265 | public static class Person { 266 | @Param("name") 267 | String name; 268 | 269 | @Param("age") 270 | public int age; 271 | 272 | public Person() { } 273 | 274 | public Person(String name, int age) { 275 | this.name = name; 276 | this.age = age; 277 | } 278 | } 279 | 280 | //java提供给js的“test1”接口,Person是无法直接往JsonObject中存放的, 281 | //所以needConvert必须为true,会自动把Person中用注解标注的属性放入json中 282 | @JavaInterface4JS("test1") 283 | public void test(@Param(needConvert = true) Person personInfo, @ParamCallback IResponseStatusCallback jsCallback) { 284 | 对收到的数据进行处理....; 285 | jsCallback.callback(1, "ok"); 286 | } 287 | 288 | //下面是js代码,js给java的"test1"接口发送消息 289 | _JSNativeBridge._doSendRequest("test1", {"name":"niu","age":10}, function(responseData){ 290 | 291 | }); 292 | 293 | **3.2.3 java给js发送消息** 294 | 295 | //给js发送消息的方法要定义在一个interface中,这个过程是模仿Retrofit的 296 | public interface IInvokeJS { 297 | 298 | //复杂类型,只有用@Param标注的属性才会放入json中 299 | public static class City{ 300 | @Param("cityName") 301 | public String cityName; 302 | 303 | @Param("cityProvince") 304 | public String cityProvince; 305 | 306 | public int cityId; 307 | } 308 | 309 | //给js的“exam”接口发送数据,参数是需要传递的数据 310 | @InvokeJSInterface("exam") 311 | void exam(@Param("test") String testContent, @Param("id") int id,@ParamCallback IJavaCallback2JS iJavaCallback2JS); 312 | 313 | //给js的“exam1”接口发送数据,参数同样也是需要传递的数据 314 | @InvokeJSInterface("exam1") 315 | void exam1(@Param(needConvert = true) City city, @ParamCallback IJavaCallback2JS iJavaCallback2JS); 316 | } 317 | 318 | 319 | //使用,使用方式和Retrofit一样,先使用SimpleJavaJsBridge的 320 | //createInvokJSCommand实例方法生成一个IInvokeJS实例 321 | IInvokeJS invokeJs = simpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 322 | 323 | //给js的"exam"发送消息,发送的是基本数据类型 324 | invokeJs.exam("hello js",20, new IJavaCallback2JS{ 325 | //接收js发送的响应数据的回调方法,该方法的名字可以任意,但必须用@JavaCallback4JS标注 326 | @JavaCallback4JS 327 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) { 328 | 329 | } 330 | }); 331 | 332 | City city = new City(); 333 | city.cityName = "长治"; 334 | city.cityId = 11; 335 | city.cityProvince = "山西"; 336 | //给js的“exam1”发送消息,city是一个复杂对象 337 | invokeJs.exam1(city, new IJavaCallback2JS{ 338 | @JavaCallback4JS 339 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) { 340 | 341 | } 342 | }); 343 | 344 | #总结 345 | SimpleJavaJsBridge库在js与java的通信中带来以下优点: 346 | - js代码中不再有由于系统或者app版本甚至业务原因产生的if else if的兼容语句 347 | - java不需要再关心数据封装为json或者从json中解析数据的繁琐工作 348 | - 让js与java之间的通信更简单 349 | 350 | 若你动心了可以下载试用:[SimpleJavaJsBridge](https://github.com/niuxiaowei/SimpleJavaJsBridge.git) 351 | 352 | 参考:大头鬼的[https://github.com/lzyzsd/JsBridge.git](https://github.com/lzyzsd/JsBridge.git) 353 | 354 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/Params.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | import android.text.TextUtils; 4 | 5 | 6 | import com.simplejsjavabridge.lib.annotation.Param; 7 | import com.simplejsjavabridge.lib.annotation.ParamCallback; 8 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus; 9 | import com.simplejsjavabridge.lib.exception.SimpleJSBridgeException; 10 | 11 | import org.json.JSONArray; 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.InvocationHandler; 18 | import java.lang.reflect.Method; 19 | import java.lang.reflect.Modifier; 20 | import java.lang.reflect.Proxy; 21 | import java.util.Iterator; 22 | 23 | /** 24 | * 该类会把{@link Method}的用{@link Param},{@link ParamCallback},{@link ParamResponseStatus}这几个注解标注的param解析出来, 25 | * {@link Param}解析为{@link ParamItem},{@link ParamCallback}解析为{@link ParamCallbackItem},{@link ParamResponseStatus} 26 | * 解析为{@link ParamResponseStatusItem}。 27 | *

同时该类还有把一个json转化为参数值的功能,和把参数值转化为json的功能

28 | *

29 | * Created by niuxiaowei on 16/7/14. 30 | */ 31 | public class Params { 32 | 33 | /** 34 | * 解析出来的所有注解item 35 | */ 36 | private BaseParamItem[] mParamItems; 37 | private static SimpleJavaJsBridge sSimpleJavaJsBridge; 38 | 39 | Params() { 40 | } 41 | 42 | /** 43 | * 初始化方法 44 | * 45 | * @param simpleJavaJsBridge 46 | */ 47 | public static void init(SimpleJavaJsBridge simpleJavaJsBridge) { 48 | sSimpleJavaJsBridge = simpleJavaJsBridge; 49 | } 50 | 51 | /** 52 | * 把json转化为参数值 53 | * @param requestResponseBuilder 包含了一系列的json数据,json数据是request或者response 54 | * @return 55 | */ 56 | public Object[] convertJson2ParamValues(RequestResponseBuilder requestResponseBuilder) { 57 | if (requestResponseBuilder == null || mParamItems == null) { 58 | return null; 59 | } 60 | Object[] result = new Object[mParamItems.length]; 61 | BaseParamItem paramItem = null; 62 | for (int i = 0; i < mParamItems.length; i++) { 63 | paramItem = mParamItems[i]; 64 | if (paramItem != null) { 65 | 66 | result[i] = paramItem.convertJson2ParamValue(requestResponseBuilder); 67 | } 68 | } 69 | return result; 70 | 71 | } 72 | 73 | /** 74 | * 把参数值转化为json 75 | * @param requestResponseBuilder 76 | * @param paramValues 参数值 77 | */ 78 | public void convertParamValues2Json(RequestResponseBuilder requestResponseBuilder, Object[] paramValues) { 79 | if (requestResponseBuilder == null || paramValues == null) { 80 | return; 81 | } 82 | BaseParamItem paramItem = null; 83 | for (int i = 0; i < mParamItems.length; i++) { 84 | paramItem = mParamItems[i]; 85 | if (paramItem != null) { 86 | paramItem.convertParamValue2Json(requestResponseBuilder, paramValues[i]); 87 | } 88 | } 89 | } 90 | 91 | 92 | /** 93 | * 基础类,定义了一些基础的数据 94 | */ 95 | private static abstract class BaseParamItem { 96 | /** 97 | * 参数所对应的类型{@link Class} 98 | */ 99 | protected Class paramType; 100 | /** 101 | * 因为参数是由{@link Param},{@link ParamCallback},{@link ParamResponseStatus}其中的一个注解标注的, 102 | * 注解标注的参数,会以{key:value}的格式存入json中,key值就是注解的value()值,因此{@link #paramKey}来代表key值 103 | */ 104 | protected String paramKey; 105 | 106 | public BaseParamItem(Class paramType, String paramKey) { 107 | this.paramType = paramType; 108 | this.paramKey = paramKey; 109 | } 110 | 111 | /** 112 | * json的格式{key:value},该方法会从json中把value给解析出来,作为参数值 113 | * @param requestResponseBuilder 114 | * @return 115 | */ 116 | public abstract Object convertJson2ParamValue(RequestResponseBuilder requestResponseBuilder); 117 | 118 | /** 119 | * 该方法会把参数值以{key:value}的格式存入json中 120 | * @param requestResponseBuilder 121 | * @param obj 122 | */ 123 | public abstract void convertParamValue2Json(RequestResponseBuilder requestResponseBuilder, Object obj); 124 | } 125 | 126 | /** 127 | * 对应{@link Param}注解标注的参数 128 | */ 129 | private static class ParamItem extends BaseParamItem { 130 | 131 | 132 | public ParamItem(String paramKey, Class paramClass) { 133 | super(paramClass, paramKey); 134 | } 135 | 136 | protected void onReceiveKeyValue(RequestResponseBuilder requestResponseBuilder, String key, Object value) { 137 | requestResponseBuilder.putValue(key, value); 138 | } 139 | 140 | protected JSONObject getJson(RequestResponseBuilder requestResponseBuilder) { 141 | return requestResponseBuilder.getValues(); 142 | } 143 | 144 | @Override 145 | public Object convertJson2ParamValue(RequestResponseBuilder requestResponseBuilder) { 146 | if (requestResponseBuilder == null || requestResponseBuilder.getValues() == null) { 147 | return null; 148 | } 149 | JSONObject jsonObject = getJson(requestResponseBuilder); 150 | if (jsonObject != null) { 151 | if (!isObjectDirectPut2Json(paramType)) { 152 | try { 153 | JSONObject value = !TextUtils.isEmpty(paramKey) ? (JSONObject) jsonObject.opt(paramKey) : jsonObject; 154 | if (value == null) { 155 | return null; 156 | } 157 | Object instance = paramType.newInstance(); 158 | Field[] fields = paramType.getDeclaredFields(); 159 | for (Field field : fields 160 | ) { 161 | Param p = field.getAnnotation(Param.class); 162 | if (p != null) { 163 | /*可以访问不可以访问的变量*/ 164 | field.setAccessible(true); 165 | field.set(instance, value.opt(p.value())); 166 | } 167 | } 168 | return instance; 169 | } catch (InstantiationException e) { 170 | e.printStackTrace(); 171 | } catch (IllegalAccessException e) { 172 | e.printStackTrace(); 173 | } 174 | } else { 175 | return jsonObject.opt(paramKey); 176 | } 177 | } 178 | return null; 179 | } 180 | 181 | @Override 182 | public void convertParamValue2Json(RequestResponseBuilder requestResponseBuilder, Object obj) { 183 | 184 | if (requestResponseBuilder == null || obj == null) { 185 | return; 186 | } 187 | if (!isObjectDirectPut2Json(obj)) { 188 | JSONObject json = convertObjectFileds2Json(obj); 189 | if (json == null) { 190 | return; 191 | } 192 | if (!TextUtils.isEmpty(paramKey)) { 193 | onReceiveKeyValue(requestResponseBuilder, paramKey, json); 194 | } else { 195 | Iterator iterator = json.keys(); 196 | String key = null; 197 | while (iterator.hasNext()) { 198 | key = iterator.next(); 199 | onReceiveKeyValue(requestResponseBuilder, key, json.opt(key)); 200 | } 201 | } 202 | 203 | } else { 204 | onReceiveKeyValue(requestResponseBuilder, paramKey, obj); 205 | 206 | } 207 | } 208 | 209 | private JSONObject convertObjectFileds2Json(Object obj) { 210 | JSONObject objectParamJson = null; 211 | 212 | 213 | Class cl = obj.getClass(); 214 | Field[] fields = cl.getDeclaredFields(); 215 | 216 | /*说明当前类的不包含任何属性*/ 217 | if (fields.length == 0) { 218 | return objectParamJson; 219 | } 220 | 221 | Object inst = null; 222 | String jsonName = null; 223 | /*属性用Param进行了标注*/ 224 | Param filedAnnoByParam = null; 225 | for (Field field : fields 226 | ) { 227 | /*final或static类型的属性或枚举类型中的枚举常量不解析*/ 228 | if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()) || field.isEnumConstant()) { 229 | continue; 230 | } 231 | filedAnnoByParam = field.getAnnotation(Param.class); 232 | if (filedAnnoByParam != null) { 233 | jsonName = filedAnnoByParam.value(); /*可以访问不可以访问的变量*/ 234 | } else { 235 | jsonName = field.getName(); 236 | } 237 | 238 | field.setAccessible(true); 239 | try { 240 | inst = field.get(obj); 241 | if (inst != null) { 242 | if (objectParamJson == null) { 243 | objectParamJson = new JSONObject(); 244 | } 245 | 246 | if (isObjectDirectPut2Json(inst)) { 247 | objectParamJson.put(jsonName, inst); 248 | } else { 249 | /*检查当前的属性是否还包含着属性*/ 250 | JSONObject filedJson = convertObjectFileds2Json(inst); 251 | if (filedJson != null) { 252 | objectParamJson.put(jsonName, filedJson); 253 | } 254 | } 255 | 256 | 257 | } 258 | } catch (IllegalAccessException e) { 259 | e.printStackTrace(); 260 | } catch (JSONException e) { 261 | e.printStackTrace(); 262 | } 263 | } 264 | 265 | return objectParamJson.length() == 0 ? null : objectParamJson; 266 | 267 | } 268 | 269 | /** 270 | * 该对象是否可以直接往json中放 271 | * 272 | * @param type 273 | * @return 274 | */ 275 | private boolean isObjectDirectPut2Json(Class type) { 276 | return (type == String.class || type.isPrimitive() || type == JSONArray.class || type == JSONObject.class); 277 | } 278 | 279 | /** 280 | * 该对象是否可以直接往json中放 281 | * 282 | * @param object 283 | * @return 284 | */ 285 | private boolean isObjectDirectPut2Json(Object object) { 286 | if (object instanceof String || object instanceof Integer || object instanceof Double || object instanceof Long || 287 | object instanceof Boolean || object instanceof JSONArray || object instanceof JSONObject) { 288 | return true; 289 | } 290 | return false; 291 | } 292 | 293 | 294 | } 295 | 296 | 297 | /** 298 | * 对应{@link ParamResponseStatus}注解标注的参数 299 | */ 300 | private static class ParamResponseStatusItem extends ParamItem { 301 | 302 | public ParamResponseStatusItem(Class paramClass, String paramKey) { 303 | super(paramKey, paramClass); 304 | } 305 | 306 | @Override 307 | protected JSONObject getJson(RequestResponseBuilder requestResponseBuilder) { 308 | return requestResponseBuilder.getResponseStatus(); 309 | } 310 | 311 | @Override 312 | protected void onReceiveKeyValue(RequestResponseBuilder requestResponseBuilder, String key, Object value) { 313 | requestResponseBuilder.putResponseStatus(key, value); 314 | } 315 | 316 | 317 | } 318 | 319 | /** 320 | * 对应{@link ParamCallback}注解标注的参数 321 | */ 322 | private static class ParamCallbackItem extends BaseParamItem { 323 | 324 | 325 | public ParamCallbackItem(Class callbackClass, String paramKey) { 326 | super(callbackClass, paramKey); 327 | } 328 | 329 | @Override 330 | public Object convertJson2ParamValue(RequestResponseBuilder requestResponseBuilder) { 331 | if (requestResponseBuilder == null || requestResponseBuilder.getCallbackId() == null) { 332 | return null; 333 | } 334 | final String resId = requestResponseBuilder.getCallbackId(); 335 | return Proxy.newProxyInstance(paramType.getClassLoader(), new Class[]{paramType}, 336 | new InvocationHandler() { 337 | @Override 338 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 339 | 340 | RequestResponseBuilder response = new RequestResponseBuilder(false); 341 | response.setResponseId(resId); 342 | Params params = Params.createParams(method); 343 | params.convertParamValues2Json(response, args); 344 | if (sSimpleJavaJsBridge != null) { 345 | 346 | sSimpleJavaJsBridge.sendData2JS(response); 347 | } else { 348 | throw new SimpleJSBridgeException(SimpleJavaJsBridge.class.getName() + "必须得进行初始化"); 349 | } 350 | return new Object(); 351 | } 352 | } 353 | 354 | ); 355 | } 356 | 357 | @Override 358 | public void convertParamValue2Json(RequestResponseBuilder requestResponseBuilder, Object obj) { 359 | 360 | if (requestResponseBuilder == null || obj == null || !(obj instanceof IJavaCallback2JS)) { 361 | return; 362 | } 363 | requestResponseBuilder.setRequestCallback((IJavaCallback2JS) obj); 364 | 365 | } 366 | } 367 | 368 | 369 | /** 370 | * 从{@link Method}中解析它所包含的参数 371 | * 372 | * @param method 373 | * @return 374 | */ 375 | public static Params createParams(Method method) { 376 | if (method != null) { 377 | Annotation[][] annotations = method.getParameterAnnotations(); 378 | Class[] parameters = method.getParameterTypes(); 379 | if (annotations != null) { 380 | Params params = new Params(); 381 | params.mParamItems = new BaseParamItem[annotations.length]; 382 | BaseParamItem paramItem = null; 383 | for (int i = 0; i < annotations.length; i++) { 384 | Annotation annotation = null; 385 | if (annotations[i].length == 0) { 386 | throw new IllegalArgumentException("方法的所有参数必须都得用" + Param.class.getSimpleName() + "," + ParamCallback.class.getSimpleName() + "," + ParamResponseStatus.class.getSimpleName() + " 中的任意一个注解进行标注"); 387 | 388 | } 389 | for (int j = 0; j < annotations[i].length; j++) { 390 | annotation = annotations[i][j]; 391 | if (annotation != null && annotation instanceof Param) { 392 | Param paramKey = (Param) annotation; 393 | paramItem = new ParamItem(paramKey.value(), parameters[i]); 394 | params.mParamItems[i] = paramItem; 395 | } else if (annotation instanceof ParamCallback) { 396 | paramItem = new ParamCallbackItem(parameters[i], null); 397 | params.mParamItems[i] = paramItem; 398 | } else if (annotation instanceof ParamResponseStatus) { 399 | ParamResponseStatus paramResponseStatus = (ParamResponseStatus) annotation; 400 | paramItem = new ParamResponseStatusItem(parameters[i], paramResponseStatus.value()); 401 | params.mParamItems[i] = paramItem; 402 | } 403 | } 404 | } 405 | 406 | return params; 407 | } 408 | 409 | } 410 | return null; 411 | } 412 | 413 | 414 | } 415 | -------------------------------------------------------------------------------- /simplejsjavabridgeLib/src/main/java/com/simplejsjavabridge/lib/SimpleJavaJsBridge.java: -------------------------------------------------------------------------------- 1 | package com.simplejsjavabridge.lib; 2 | 3 | import android.net.Uri; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.webkit.WebChromeClient; 9 | import android.webkit.WebView; 10 | 11 | 12 | import com.simplejsjavabridge.lib.annotation.InvokeJSInterface; 13 | import com.simplejsjavabridge.lib.annotation.JavaCallback4JS; 14 | import com.simplejsjavabridge.lib.annotation.JavaInterface4JS; 15 | import com.simplejsjavabridge.lib.exception.SimpleJSBridgeException; 16 | 17 | import org.json.JSONObject; 18 | 19 | import java.lang.reflect.InvocationHandler; 20 | import java.lang.reflect.Method; 21 | import java.lang.reflect.Proxy; 22 | import java.util.ArrayList; 23 | import java.util.HashMap; 24 | 25 | /** 26 | * 该类是本库的核心类,看例子 27 | *

生成{@link SimpleJavaJsBridge}的实例:

28 | *
 29 |  *     SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder().addJavaInterface4JS(javaInterfaces4JS)
 30 |                                     .setWebView(mWebView)
 31 |                                     .setJSMethodName4Java("_JSBridge._handleMessageFromNative")
 32 |                                     .setProtocol("didiam://__QUEUE_MESSAGE__/?").create();
 33 |  *
 34 |  *      这样可以创建一个SimpleJavaJsBridge的实例,当然还可以设置其他参数,但是上面的几个参数是必须设定的,
 35 |  *      发送给对方的数据,我给起了个名字叫request(请求数据),
 36 |  *      request的格式是:
 37 |  *              
 38 |  *                  {
 39 |  *                      "handlerName":"test",
 40 |  *                      "callbackId":"c_111111",
 41 |  *                      "params":{
 42 |  *                          ....
 43 |  *                      }
 44 |  *                  }
 45 |  *              
46 | * request里面的这三个关键的key值的名字是可以重新定义的,比如"handlerName"可以使用 new SimpleJavaJsBridge.Builder().setHandlerName("interfaceName") 47 | * 来进行自定义。 48 | * 49 | * 接收到对方的数据,我给起个名字叫response(响应数据), 50 | * response的格式是: 51 | *
 52 |  *                  {
 53 |  *                      "responseId":"iii",
 54 |  *                      "data":{
 55 |  *                          "status":"1",
 56 |  *                          "msg":"ok",
 57 |  *                          "values":{
 58 |  *                              ......
 59 |  *                          }
 60 |  *                      }
 61 |  *                  }
 62 |  *              
63 | * 同理response里面的"responseId","data","values"这三个关键的key值的名字也是可以调用SimpleJavaJsBridge.Builder进行自定义的 64 | *
65 | * 66 | *

调用js接口的例子:

67 | *
 68 |  *     //声明一个调用js的interface
 69 |  *     interface IInvokeJS{
 70 |  *           //声明一个调用js的 exam4 的接口,该接口不需要传递参数
 71 |  *           :@InvokeJSInterface("exam4")
 72 |  *           void exam4(@ParamCallback IJavaCallback2JS iJavaCallback2JS);
 73 |  *     }
 74 |  *
 75 |  *     //开始调用js的接口
 76 |  *     IInvokeJs invokeJs = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJs.class);
 77 |  *     invokeJS.exam4(new IJavaCallback2JS{
 78 |  *         :@JavaCallback4JS
 79 |  *         public void callback(@ParamResponseStatus("status") String status){
 80 |  *
 81 |  *         }
 82 |  *     });
 83 |  *
 84 |  *     以上就是一个调用js的exam4接口的例子,其实该过程是模仿了retrofit的。这样做的好处是上层使用者完全不需要关心从json中解析数据这
 85 |  *     一繁琐的重复的体力劳动了
 86 |  *
 87 |  * 
88 | * 89 | * Created by niuxiaowei on 16/6/15. 90 | */ 91 | public class SimpleJavaJsBridge { 92 | 93 | private static final String TAG = SimpleJavaJsBridge.class.getSimpleName(); 94 | 95 | private static final String JAVASCRIPT = "javascript:"; 96 | /** 97 | * java调用js的功能时,java会为js提供回调函数,但是不可能把回调函数传递给js, 98 | * 所以为回调函数提供一个唯一的id, 99 | */ 100 | private static int sUniqueCallbackId = 1; 101 | 102 | 103 | /** 104 | * 保证发送给js数据时在ui线程中执行 105 | */ 106 | private Handler mMainHandler = new Handler(Looper.getMainLooper()); 107 | 108 | /** 109 | * 缓存java为js提供的接口 110 | */ 111 | private HashMap mJavaInterfaces4JSCache = new HashMap<>(); 112 | 113 | /** 114 | * 缓存java为js提供搞的回调方法 115 | */ 116 | private HashMap mJavaCallbackMethods4JSCache = new HashMap<>(); 117 | 118 | 119 | /*js为java敞开的唯一的一个可调用的方法,该方法接收一个字符串,字符串是json格式*/ 120 | private String mJSMethod4SendData2JS; 121 | /** 122 | * 协议的格式是:scheme+"://"+host+"?" 123 | */ 124 | private String mProtocol; 125 | private WebView mWebView; 126 | private SimpleJavaJSWebChromeClient mSimpleJavaJSWebChromeClient; 127 | 128 | /** 129 | * 是否是debug模式,debug模式可以把交互信息打出来 130 | */ 131 | private boolean mIsDebug; 132 | 133 | 134 | SimpleJavaJsBridge(Builder builder) { 135 | if (builder == null) { 136 | return; 137 | } 138 | Params.init(this); 139 | mWebView = builder.mWebView; 140 | mWebView.getSettings().setJavaScriptEnabled(true); 141 | mSimpleJavaJSWebChromeClient = new SimpleJavaJSWebChromeClient(builder.mWebChromeClient, this); 142 | mWebView.setWebChromeClient(mSimpleJavaJSWebChromeClient); 143 | saveJavaMethods4JS(builder.mJavaMethod4JS); 144 | RequestResponseBuilder.init(builder.mResponseIdName, builder.mResponseName, builder.mResponseValuesName, builder.mRequestInterfaceName, builder.mRequestCallbackIdName, builder.mRequestValuesName); 145 | mJSMethod4SendData2JS = builder.mJSMethodName4Java; 146 | mProtocol = builder.mProtocol; 147 | } 148 | 149 | /** 150 | * 生成SimpleJavaJsBridge的实例 151 | */ 152 | public static class Builder { 153 | 154 | private String mResponseName; 155 | private String mResponseValuesName; 156 | private String mResponseIdName; 157 | 158 | 159 | private String mRequestInterfaceName; 160 | private String mRequestCallbackIdName; 161 | private String mRequestValuesName; 162 | 163 | /*js为java敞开的唯一的一个可调用的方法,该方法接收一个字符串,字符串是json格式*/ 164 | private String mJSMethodName4Java; 165 | private String mProtocol; 166 | 167 | 168 | private WebChromeClient mWebChromeClient; 169 | private ArrayList mJavaMethod4JS; 170 | private WebView mWebView; 171 | 172 | /** 173 | * 是否是debug模式 174 | */ 175 | private boolean mIsDebug = true; 176 | 177 | public Builder() { 178 | 179 | } 180 | 181 | /** 182 | * debug模式下,可以把交互信息打印出来 183 | * @param debug 184 | * @return 185 | */ 186 | public Builder setDebug(boolean debug) { 187 | mIsDebug = debug; 188 | return this; 189 | } 190 | 191 | /** 192 | * 193 | *
194 |          *  response格式:
195 |          *  {
196 |          *      "responseId":"iii",
197 |          *      "data":{
198 |          *          "status":"1",
199 |          *          "msg":"ok",
200 |          *          "values":{
201 |          *              ......
202 |          *          }
203 |          *      }
204 |          *  }
205 |          *  responseId 代表request中的callbackId
206 |          *  data       代表响应的数据
207 |          *  status     代表响应状态
208 |          *  msg        代表响应状态对应的消息
209 |          *  values     代表响应数据包含的值
210 |          *  
211 | * 212 | *
213 |          *      responseName的默认名字是"data",可以对这个名字进行设置
214 |          *  
215 | * 216 | * @param responseName 217 | * @return 218 | */ 219 | public Builder setResponseName(String responseName) { 220 | mResponseName = responseName; 221 | return this; 222 | } 223 | 224 | /** 225 | * responseValuesName的默认名字是"values",可以对这个名字进行设置 226 | * 227 | * @param responseValuesName 228 | * @return 229 | * @see #setResponseName(String) 230 | */ 231 | public Builder setResponseValuesName(String responseValuesName) { 232 | mResponseValuesName = responseValuesName; 233 | return this; 234 | } 235 | 236 | /** 237 | * response中responseIdName的默认名字是"responseId",可以对起进行设置 238 | * @param responseIdName 239 | * @return 240 | * @see #setResponseName(String) 241 | */ 242 | public Builder setResponseIdName(String responseIdName) { 243 | mResponseIdName = responseIdName; 244 | return this; 245 | } 246 | 247 | /** 248 | *
249 |          *    {
250 |          *      "handlerName":"test",
251 |          *      "callbackId":"c_111111",
252 |          *      "params":{
253 |          *          ....
254 |          *      }
255 |          *    }
256 |          *
257 |          *    hanlerName 代表java与js之间给对方暴漏的接口的名称,
258 |          *    callbackId 代表对方在发起请求时,会为回调方法生产一个唯一的id值,它就代表这个唯一的id值
259 |          *    params     代表传递的数据
260 |          *  
261 | *
262 |          *      requestInterfaceName的默认值是"handlerName",可以进行设置它
263 |          *  
264 | * 265 | * @param requestInterfaceName 266 | * @return 267 | */ 268 | public Builder setRequestInterfaceName(String requestInterfaceName) { 269 | mRequestInterfaceName = requestInterfaceName; 270 | return this; 271 | } 272 | 273 | /** 274 | * 275 | * 同理requestCallbackIdName的默认值是"callbackId",可以对它进行设置 276 | * 277 | * @param requestCallbackIdName 278 | * @return 279 | * @see #setRequestInterfaceName(String) 280 | */ 281 | public Builder setRequestCallbackIdName(String requestCallbackIdName) { 282 | mRequestCallbackIdName = requestCallbackIdName; 283 | return this; 284 | } 285 | 286 | /** 287 | * 同理requestValuesName的默认值是"params",可以对它进行设置 288 | * 289 | * @param requestValuesName 290 | * @return 291 | * @see #setRequestInterfaceName(String) 292 | 293 | */ 294 | public Builder setRequestValuesName(String requestValuesName) { 295 | mRequestValuesName = requestValuesName; 296 | return this; 297 | } 298 | 299 | /** 300 | * 设置js为java暴漏的方法的名字,只需要提供方法名字即可,具体的关于"()"和参数不需要提供,因为该方法接收的是一个json字符串 301 | * 302 | * @param JSMethodName 方法名字 比如:handleMsgFromJava 303 | * @return 304 | */ 305 | public Builder setJSMethodName4Java(String JSMethodName) { 306 | mJSMethodName4Java = JSMethodName; 307 | if (!TextUtils.isEmpty(mJSMethodName4Java) && !mJSMethodName4Java.startsWith(JAVASCRIPT)) { 308 | mJSMethodName4Java = JAVASCRIPT + mJSMethodName4Java ; 309 | if(!mJSMethodName4Java.contains("%s")){ 310 | mJSMethodName4Java = mJSMethodName4Java + "(%s)"; 311 | } 312 | } 313 | return this; 314 | } 315 | 316 | /** 317 | * 设置协议,协议格式:scheme://host?,协议是必须进行设置的,否则报错 318 | * 319 | * @param scheme 比如 file或http等 320 | * @param host 321 | * @return 322 | */ 323 | public Builder setProtocol(String scheme,String host) { 324 | if(TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)){ 325 | return this; 326 | } 327 | mProtocol = scheme+"://"+host+"?"; 328 | return this; 329 | } 330 | 331 | public Builder setWebChromeClient(WebChromeClient webChromeClient) { 332 | mWebChromeClient = webChromeClient; 333 | return this; 334 | } 335 | 336 | /** 337 | * 添加java提供给js的接口 338 | * @param javaMethod4JS 339 | * @return 340 | */ 341 | public Builder addJavaInterface4JS(Object javaMethod4JS) { 342 | if (javaMethod4JS == null) { 343 | return this; 344 | } 345 | if (mJavaMethod4JS == null) { 346 | mJavaMethod4JS = new ArrayList(); 347 | } 348 | mJavaMethod4JS.add(javaMethod4JS); 349 | return this; 350 | } 351 | 352 | /** 353 | * 必须进行设置 354 | * 355 | * @param webView 356 | * @return 357 | */ 358 | public Builder setWebView(WebView webView) { 359 | mWebView = webView; 360 | return this; 361 | } 362 | 363 | /** 364 | * 检测协议是否符合要求 365 | * 366 | * @return 367 | * @throws SimpleJSBridgeException 368 | */ 369 | private void checkProtocol() { 370 | if (TextUtils.isEmpty(mProtocol)) { 371 | throw new SimpleJSBridgeException("必须调用setProtocol(String)设置协议"); 372 | } 373 | Uri uri = Uri.parse(mProtocol); 374 | if (TextUtils.isEmpty(uri.getScheme()) || TextUtils.isEmpty(uri.getHost()) || !mProtocol.endsWith("?")) { 375 | throw new IllegalArgumentException("协议的格式必须是 scheme://host? 这种格式"); 376 | } 377 | } 378 | 379 | private void checkJSMethod() { 380 | if (TextUtils.isEmpty(mJSMethodName4Java)) { 381 | throw new IllegalArgumentException("必须调用 setJSMethodName4Java(String) 方法对给js发送消息的方法进行设置"); 382 | } 383 | 384 | } 385 | 386 | public SimpleJavaJsBridge create() { 387 | /*检查协议是否设置,并设置正确了*/ 388 | checkProtocol(); 389 | checkJSMethod(); 390 | if (mWebView == null) { 391 | throw new IllegalArgumentException("必须调用 setWebView(WebView) 方法设置Webview"); 392 | } 393 | return new SimpleJavaJsBridge(this); 394 | } 395 | } 396 | 397 | 398 | /** 399 | * 生成调用js的命令,在调用js之前必须得调用该方法,该模式是模仿retrofit的 400 | * @param tClass 必须是一个interface 401 | * @param 402 | * @return 403 | */ 404 | public T createInvokJSCommand(Class tClass) { 405 | return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new InvocationHandler() { 406 | @Override 407 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 408 | String jsMethodName = null; 409 | if (method.getAnnotation(InvokeJSInterface.class) != null) { 410 | InvokeJSInterface invokeJSInterface = method.getAnnotation(InvokeJSInterface.class); 411 | jsMethodName = invokeJSInterface.value(); 412 | } 413 | 414 | RequestResponseBuilder requstBuild = new RequestResponseBuilder(true); 415 | requstBuild.setInterfaceName(jsMethodName); 416 | Params params = Params.createParams(method); 417 | params.convertParamValues2Json(requstBuild, args); 418 | 419 | sendData2JS(requstBuild); 420 | return new Object(); 421 | } 422 | }); 423 | } 424 | 425 | /** 426 | * 存储java为js提供的接口们 427 | * 428 | * @param javaMethods4JSes 429 | */ 430 | private void saveJavaMethods4JS(ArrayList javaMethods4JSes) { 431 | if (javaMethods4JSes != null) { 432 | 433 | for (int i = 0; i < javaMethods4JSes.size(); i++) { 434 | Object instance = javaMethods4JSes.get(i); 435 | if (instance != null) { 436 | 437 | //把java提供给js调用的接口放到json中 438 | Class bridgeClass = instance.getClass(); 439 | Method[] allMethod = bridgeClass.getDeclaredMethods(); 440 | for (Method method : allMethod) { 441 | 442 | //说明这是提供给js的接口 443 | if (method.getAnnotation(JavaInterface4JS.class) != null) { 444 | /*既然是提供给js的接口就得用JavascriptInterfaceKey标注,否则报错*/ 445 | JavaInterface4JS jsKey = method.getAnnotation(JavaInterface4JS.class); 446 | MethodHandler methodHandler = MethodHandler.createMethodHandler(instance, method); 447 | mJavaInterfaces4JSCache.put(jsKey.value(), methodHandler); 448 | } 449 | } 450 | 451 | } 452 | } 453 | } 454 | } 455 | 456 | /** 457 | * 生成唯一的回调id 458 | * 459 | * @return 460 | */ 461 | private static String generaUniqueCallbackId() { 462 | return ++sUniqueCallbackId + "_" + System.currentTimeMillis(); 463 | } 464 | 465 | private void sendRequest2JS(RequestResponseBuilder requst) { 466 | if (requst != null) { 467 | String callbackId = generaUniqueCallbackId(); 468 | requst.setCallbackId(callbackId); 469 | 470 | /*处理提供给js的回调方法*/ 471 | if (requst.getCallback() != null) { 472 | Class bridgeClass = requst.getCallback().getClass(); 473 | Method[] allMethod = bridgeClass.getDeclaredMethods(); 474 | JavaCallback4JS javaCallback4JS = null; 475 | for (Method method : allMethod) { 476 | javaCallback4JS = method.getAnnotation(JavaCallback4JS.class); 477 | if (javaCallback4JS != null) { 478 | mJavaCallbackMethods4JSCache.put(callbackId, MethodHandler.createMethodHandler(requst.getCallback(), method)); 479 | break; 480 | } 481 | 482 | } 483 | } 484 | 485 | startSendData2JS(requst.toString()); 486 | 487 | } 488 | } 489 | 490 | private void sendResponse2JS(RequestResponseBuilder response) { 491 | if (response != null) { 492 | startSendData2JS(response.toString()); 493 | } 494 | } 495 | 496 | /** 497 | * 发送数据给js 498 | */ 499 | public void sendData2JS(final RequestResponseBuilder requestResponseBuilder) { 500 | if (requestResponseBuilder == null) { 501 | return; 502 | } 503 | 504 | if (requestResponseBuilder.isBuildRequest()) { 505 | sendRequest2JS(requestResponseBuilder); 506 | } else { 507 | sendResponse2JS(requestResponseBuilder); 508 | } 509 | } 510 | 511 | 512 | /** 513 | * 开始发送数据给js 514 | * 515 | * @param data 516 | */ 517 | private void startSendData2JS(String data) { 518 | if (TextUtils.isEmpty(data)) { 519 | return; 520 | } 521 | 522 | data = String.format(mJSMethod4SendData2JS, data); 523 | 524 | final String finalData = data; 525 | if(mIsDebug){ 526 | 527 | Log.i(TAG, "发送给js的数据:" + data ); 528 | } 529 | mMainHandler.post(new Runnable() { 530 | @Override 531 | public void run() { 532 | mWebView.loadUrl(finalData); 533 | } 534 | }); 535 | 536 | } 537 | 538 | /** 539 | * 解析从js传递过来的json数据 540 | * 541 | * @param json 542 | * @return true 代表可以解析当前数据,否则不可以解析 543 | */ 544 | boolean parseJsonFromJs(String json) { 545 | boolean result = false; 546 | if (!TextUtils.isEmpty(json)) { 547 | if (json.startsWith(mProtocol)) { 548 | result = true; 549 | json = json.substring(json.indexOf(mProtocol, 0) + mProtocol.length()); 550 | if(mIsDebug){ 551 | 552 | Log.i(TAG, "收到js发送过来的数据:" + json ); 553 | } 554 | try { 555 | JSONObject data = new JSONObject(json); 556 | if (data == null) { 557 | return false; 558 | } 559 | /*开始调用java的方法*/ 560 | invokeJavaMethod(RequestResponseBuilder.create(data)); 561 | } catch (Exception e) { 562 | e.printStackTrace(); 563 | } 564 | } 565 | } 566 | return result; 567 | } 568 | 569 | /** 570 | * 开始调用java的方法, 571 | * 572 | * @param requestResponseBuilder 573 | */ 574 | private void invokeJavaMethod(RequestResponseBuilder requestResponseBuilder) { 575 | if (requestResponseBuilder == null) { 576 | return; 577 | } 578 | /*说明这是响应数据*/ 579 | if (!requestResponseBuilder.isBuildRequest()) { 580 | MethodHandler methodHandler = mJavaCallbackMethods4JSCache.remove(requestResponseBuilder.getResponseId()); 581 | if (methodHandler == null) { 582 | Log.e(TAG, "回调方法不存在"); 583 | return; 584 | } 585 | methodHandler.invoke(requestResponseBuilder); 586 | } else { 587 | /*说明是js请求java的请求数据*/ 588 | MethodHandler methodHandler = mJavaInterfaces4JSCache.get(requestResponseBuilder.getInterfaceName()); 589 | if (methodHandler != null) { 590 | methodHandler.invoke(requestResponseBuilder); 591 | } else { 592 | Log.e(TAG, "所调用的接口不存在"); 593 | 594 | RequestResponseBuilder errorResponse = new RequestResponseBuilder(false); 595 | errorResponse.setResponseId(requestResponseBuilder.getCallbackId()); 596 | errorResponse.putResponseStatus("errmsg","所调用的接口不存在"); 597 | sendResponse2JS(errorResponse); 598 | } 599 | 600 | } 601 | 602 | } 603 | 604 | 605 | } 606 | --------------------------------------------------------------------------------