diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..2884129 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,17 @@ +name: Android CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: ./gradlew build diff --git a/JsBridge.zip b/JsBridge.zip new file mode 100644 index 0000000..d2c837b Binary files /dev/null and b/JsBridge.zip differ diff --git a/README.md b/README.md index 8d57fa8..d58fde3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#JsBridge +# JsBridge ----- @@ -129,8 +129,7 @@ will print 'JS got a message hello' and 'JS responding with' in webview console. ## Notice This lib will inject a WebViewJavascriptBridge Object to window object. -So in your js, before use WebViewJavascriptBridge, you must detect if WebViewJavascriptBridge exist. -If WebViewJavascriptBridge does not exit, you can listen to WebViewJavascriptBridgeReady event, as the blow code shows: +You can listen to `WebViewJavascriptBridgeReady` event to ensure `window.WebViewJavascriptBridge` is exist, as the blow code shows: ```javascript @@ -148,6 +147,38 @@ If WebViewJavascriptBridge does not exit, you can listen to WebViewJavascriptBri ``` +Or put all JsBridge function call into `window.WVJBCallbacks` array if `window.WebViewJavascriptBridge` is undefined, this taks queue will be flushed when `WebViewJavascriptBridgeReady` event triggered. + +Copy and paste setupWebViewJavascriptBridge into your JS: + +```javascript +function setupWebViewJavascriptBridge(callback) { + if (window.WebViewJavascriptBridge) { + return callback(WebViewJavascriptBridge); + } + if (window.WVJBCallbacks) { + return window.WVJBCallbacks.push(callback); + } + window.WVJBCallbacks = [callback]; +} +``` + +Call `setupWebViewJavascriptBridge` and then use the bridge to register handlers or call Java handlers: + +```javascript +setupWebViewJavascriptBridge(function(bridge) { + bridge.registerHandler('JS Echo', function(data, responseCallback) { + console.log("JS Echo called with:", data); + responseCallback(data); + }); + bridge.callHandler('ObjC Echo', {'key':'value'}, function(responseData) { + console.log("JS received response:", responseData); + }); +}); +``` + +It same with https://github.com/marcuswestin/WebViewJavascriptBridge, that would be easier for you to define same behavior in different platform between Android and iOS. Meanwhile, writing concise code. + ## License This project is licensed under the terms of the MIT license. diff --git a/build.gradle b/build.gradle index a4648d2..ba3db91 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -23,7 +23,7 @@ allprojects { } google() } - tasks.withType(Javadoc) { // 这一段是为了消除gbk的错误 + tasks.withType(Javadoc) { // 这一段是为了消除gbk的错�? options{ encoding "UTF-8" charSet 'UTF-8' diff --git a/example/build.gradle b/example/build.gradle index 8d69880..c6950fb 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 + compileSdkVersion 28 // buildToolsVersion "25.0.3" defaultConfig { applicationId "com.github.lzyzsd.jsbridge.example" - minSdkVersion 9 - targetSdkVersion 25 + minSdkVersion 17 + targetSdkVersion 28 versionCode 1 versionName "1.0" } @@ -20,8 +20,8 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':library') - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'com.google.code.gson:gson:2.3.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':library') + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.google.code.gson:gson:2.8.5' } diff --git a/example/src/main/assets/demo.html b/example/src/main/assets/demo.html index dc358fc..38d106b 100644 --- a/example/src/main/assets/demo.html +++ b/example/src/main/assets/demo.html @@ -1,42 +1,42 @@ - - - - js调用java - - - - -

-

- -

-

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

- - + + diff --git a/example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java b/example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java new file mode 100644 index 0000000..1b5935c --- /dev/null +++ b/example/src/main/java/com/github/lzyzsd/jsbridge/example/CustomWebView.java @@ -0,0 +1,115 @@ +package com.github.lzyzsd.jsbridge.example; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.github.lzyzsd.jsbridge.BridgeHandler; +import com.github.lzyzsd.jsbridge.BridgeHelper; +import com.github.lzyzsd.jsbridge.CallBackFunction; +import com.github.lzyzsd.jsbridge.IWebView; +import com.github.lzyzsd.jsbridge.WebViewJavascriptBridge; + +/** + * 采用BridgeHelper集成JsBridge功能示例.定制WebView,可只添加实际需要的JsBridge接口. + * + * @author ZhengAn + * @date 2019-07-07 + */ +@SuppressLint("SetJavaScriptEnabled") +public class CustomWebView extends WebView implements WebViewJavascriptBridge, IWebView { + + private BridgeHelper bridgeHelper; + + public CustomWebView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CustomWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public CustomWebView(Context context) { + super(context); + init(); + } + + private void init() { + this.setVerticalScrollBarEnabled(false); + this.setHorizontalScrollBarEnabled(false); + this.getSettings().setJavaScriptEnabled(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + WebView.setWebContentsDebuggingEnabled(true); + } + + bridgeHelper = new BridgeHelper(this); + this.setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView webView, String s) { + bridgeHelper.onPageFinished(); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView webView, String s) { + return bridgeHelper.shouldOverrideUrlLoading(s); + } + }); + } + + /** + * @param handler default handler,handle messages send by js without assigned handler name, + * if js message has handler name, it will be handled by named handlers registered by native + */ + public void setDefaultHandler(BridgeHandler handler) { + bridgeHelper.setDefaultHandler(handler); + } + + @Override + public void send(String data) { + send(data, null); + } + + @Override + public void send(String data, CallBackFunction responseCallback) { + bridgeHelper.send(data, responseCallback); + } + + + /** + * register handler,so that javascript can call it + * 注册处理程序,以便javascript调用它 + * + * @param handlerName handlerName + * @param handler BridgeHandler + */ + public void registerHandler(String handlerName, BridgeHandler handler) { + bridgeHelper.registerHandler(handlerName, handler); + } + + /** + * unregister handler + * + * @param handlerName + */ + public void unregisterHandler(String handlerName) { + bridgeHelper.unregisterHandler(handlerName); + } + + /** + * call javascript registered handler + * 调用javascript处理程序注册 + * + * @param handlerName handlerName + * @param data data + * @param callBack CallBackFunction + */ + public void callHandler(String handlerName, String data, CallBackFunction callBack) { + bridgeHelper.callHandler(handlerName, data, callBack); + } + +} diff --git a/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java b/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java index aedb2b4..dced3b6 100644 --- a/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java +++ b/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainActivity.java @@ -12,10 +12,8 @@ import android.webkit.WebView; import android.widget.Button; -import com.github.lzyzsd.jsbridge.BridgeHandler; import com.github.lzyzsd.jsbridge.BridgeWebView; -import com.github.lzyzsd.jsbridge.CallBackFunction; -import com.github.lzyzsd.jsbridge.DefaultHandler; +import com.github.lzyzsd.jsbridge.OnBridgeCallback; import com.google.gson.Gson; public class MainActivity extends Activity implements OnClickListener { @@ -53,7 +51,6 @@ protected void onCreate(Bundle savedInstanceState) { button.setOnClickListener(this); - webView.setDefaultHandler(new DefaultHandler()); webView.setWebChromeClient(new WebChromeClient() { @@ -80,32 +77,24 @@ public boolean onShowFileChooser(WebView webView, ValueCallback filePathC } }); + webView.addJavascriptInterface(new MainJavascrotInterface(webView.getCallbacks(), webView), "android"); + webView.setGson(new Gson()); webView.loadUrl("file:///android_asset/demo.html"); - webView.registerHandler("submitFromWeb", new BridgeHandler() { - - @Override - public void handler(String data, CallBackFunction function) { - Log.i(TAG, "handler = submitFromWeb, data from web = " + data); - function.onCallBack("submitFromWeb exe, response data 中文 from Java"); - } - - }); - User user = new User(); Location location = new Location(); location.address = "SDU"; user.location = location; user.name = "大头鬼"; - webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { + webView.callHandler("functionInJs", new Gson().toJson(user), new OnBridgeCallback() { @Override public void onCallBack(String data) { - + Log.d(TAG, "onCallBack: " + data); } }); - webView.send("hello"); + webView.sendToWeb("hello"); } @@ -129,7 +118,9 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent) if(null == mUploadMessage && null != mUploadMessageArray){ Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); - mUploadMessageArray.onReceiveValue(new Uri[]{result}); + if (result != null) { + mUploadMessageArray.onReceiveValue(new Uri[]{result}); + } mUploadMessageArray = null; } @@ -139,7 +130,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent) @Override public void onClick(View v) { if (button.equals(v)) { - webView.callHandler("functionInJs", "data from Java", new CallBackFunction() { + webView.callHandler("functionInJs", "data from Java", new OnBridgeCallback() { @Override public void onCallBack(String data) { diff --git a/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascrotInterface.java b/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascrotInterface.java new file mode 100644 index 0000000..293a7e5 --- /dev/null +++ b/example/src/main/java/com/github/lzyzsd/jsbridge/example/MainJavascrotInterface.java @@ -0,0 +1,36 @@ +package com.github.lzyzsd.jsbridge.example; + +import android.util.Log; +import android.webkit.JavascriptInterface; + +import com.github.lzyzsd.jsbridge.BridgeWebView; +import com.github.lzyzsd.jsbridge.OnBridgeCallback; + +import java.util.Map; + +/** + * Created on 2019/7/10. + * Author: bigwang + * Description: + */ +public class MainJavascrotInterface extends BridgeWebView.BaseJavascriptInterface { + + private BridgeWebView mWebView; + + public MainJavascrotInterface(Map callbacks, BridgeWebView webView) { + super(callbacks); + mWebView = webView; + } + + @Override + public String send(String data) { + return "it is default response"; + } + + + @JavascriptInterface + public void submitFromWeb(String data, String callbackId) { + Log.d("chromium data", data + ", callbackId: " + callbackId + " " + Thread.currentThread().getName()); + mWebView.sendResponse("submitFromWeb response", callbackId); + } +} diff --git a/gradle.properties b/gradle.properties index 4e669b2..7293145 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,3 +17,5 @@ #systemProp.http.proxyHost=127.0.0.1 #systemProp.https.proxyHost=127.0.0.1 #systemProp.http.proxyPort=1080 +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 34495fa..85cbcc2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 31 22:44:06 CST 2018 +#Wed Mar 24 16:24:00 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/library/build.gradle b/library/build.gradle index 1eba597..500ecf2 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -3,12 +3,12 @@ apply plugin: 'com.android.library' version = "1.0.0" android { - compileSdkVersion 25 + compileSdkVersion 28 // buildToolsVersion "25.0.3" defaultConfig { - minSdkVersion 9 - targetSdkVersion 25 + minSdkVersion 17 + targetSdkVersion 28 versionCode 1 versionName version } @@ -25,28 +25,30 @@ android { } dependencies { + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'com.google.code.gson:gson:2.8.5' } -//def siteUrl = 'https://github.com/lzyzsd/JsBridge' -//def gitUrl = 'https://github.com/lzyzsd/JsBridge.git' -//apply plugin: 'com.github.dcendents.android-maven' -//group = "com.github.lzyzsd.jsbridge" -//task sourcesJar(type: Jar) { -// from android.sourceSets.main.java.srcDirs -// classifier = 'sources' -//} -//task javadoc(type: Javadoc) { -// source = android.sourceSets.main.java.srcDirs -// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) -//} -//task javadocJar(type: Jar, dependsOn: javadoc) { -// classifier = 'javadoc' -// from javadoc.destinationDir -//} -//artifacts { -// archives javadocJar -// archives sourcesJar -//} -//Properties properties = new Properties() -//properties.load(project.rootProject.file('local.properties').newDataInputStream()) +def siteUrl = 'https://github.com/lzyzsd/JsBridge' +def gitUrl = 'https://github.com/lzyzsd/JsBridge.git' +// apply plugin: 'com.github.dcendents.android-maven' +group = "com.github.lzyzsd.jsbridge" +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} +artifacts { + archives javadocJar + archives sourcesJar +} +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) diff --git a/library/src/main/assets/WebViewJavascriptBridge.js b/library/src/main/assets/WebViewJavascriptBridge.js index 27743b9..bf43554 100644 --- a/library/src/main/assets/WebViewJavascriptBridge.js +++ b/library/src/main/assets/WebViewJavascriptBridge.js @@ -6,15 +6,9 @@ return; } - var messagingIframe; - var bizMessagingIframe; - var sendMessageQueue = []; var receiveMessageQueue = []; var messageHandlers = {}; - var CUSTOM_PROTOCOL_SCHEME = 'yy'; - var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; - var responseCallbacks = {}; var uniqueId = 1; @@ -49,9 +43,7 @@ // 发送 function send(data, responseCallback) { - _doSend({ - data: data - }, responseCallback); + _doSend('send', data, responseCallback); } // 注册线程 往数组里面添加值 @@ -60,19 +52,43 @@ } // 调用线程 function callHandler(handlerName, data, responseCallback) { - _doSend({ - handlerName: handlerName, - data: data - }, responseCallback); + // 如果方法不需要参数,只有回调函数,简化JS中的调用 + if (arguments.length == 2 && typeof data == 'function') { + responseCallback = data; + data = null; + } + _doSend(handlerName, data, responseCallback); } //sendMessage add message, 触发native处理 sendMessage - function _doSend(message, responseCallback) { - if (responseCallback) { - var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); + function _doSend(handlerName, message, responseCallback) { + var callbackId; + if(typeof responseCallback === 'string'){ + callbackId = responseCallback; + } else if (responseCallback) { + callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; + }else{ + callbackId = ''; } + try { + var fn = eval('window.android.' + handlerName); + } catch(e) { + console.log(e); + } + if (typeof fn === 'function'){ + var responseData = fn.call(this, JSON.stringify(message), callbackId); + if(responseData){ + console.log('response message: '+ responseData); + responseCallback = responseCallbacks[callbackId]; + if (!responseCallback) { + return; + } + responseCallback(responseData); + delete responseCallbacks[callbackId]; + } + } sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; @@ -99,7 +115,6 @@ sendMessageQueue = []; //android can't read directly the return data, so we can reload iframe src to communicate with java bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); - } //提供给native使用, @@ -120,10 +135,7 @@ if (message.callbackId) { var callbackResponseId = message.callbackId; responseCallback = function(responseData) { - _doSend({ - responseId: callbackResponseId, - responseData: responseData - }); + _doSend('response', responseData, callbackResponseId); }; } @@ -145,12 +157,12 @@ //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 function _handleMessageFromNative(messageJSON) { - console.log(messageJSON); + console.log('handle message: '+ messageJSON); if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON); } _dispatchMessageFromNative(messageJSON); - + } var WebViewJavascriptBridge = window.WebViewJavascriptBridge = { @@ -158,15 +170,17 @@ send: send, registerHandler: registerHandler, callHandler: callHandler, - _fetchQueue: _fetchQueue, _handleMessageFromNative: _handleMessageFromNative }; var doc = document; - _createQueueReadyIframe(doc); - _createQueueReadyIframe4biz(doc); var readyEvent = doc.createEvent('Events'); + var jobs = window.WVJBCallbacks || []; readyEvent.initEvent('WebViewJavascriptBridgeReady'); readyEvent.bridge = WebViewJavascriptBridge; + window.WVJBCallbacks = [] + jobs.forEach(function (job) { + job(WebViewJavascriptBridge) + }) doc.dispatchEvent(readyEvent); })(); diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java deleted file mode 100644 index 2d745c1..0000000 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.lzyzsd.jsbridge; - -public interface BridgeHandler { - - void handler(String data, CallBackFunction function); - -} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java new file mode 100644 index 0000000..d4e4bfe --- /dev/null +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeHelper.java @@ -0,0 +1,288 @@ +package com.github.lzyzsd.jsbridge; + +import android.os.Looper; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JsBridge辅助类,帮助集成JsBridge功能. + * + * @author ZhengAn + * @date 2019-06-30 + */ +public class BridgeHelper implements WebViewJavascriptBridge { + + private static final String TAG = "BridgeHelper"; + + private static final String BRIDGE_JS = "WebViewJavascriptBridge.js"; + private Map responseCallbacks = new HashMap<>(); + private Map messageHandlers = new HashMap<>(); + private BridgeHandler defaultHandler = new DefaultHandler(); + + private List startupMessage = new ArrayList<>(); + + private List getStartupMessage() { + return startupMessage; + } + + private void setStartupMessage(List startupMessage) { + this.startupMessage = startupMessage; + } + + private long uniqueId = 0; + + private IWebView webView; + + public BridgeHelper(IWebView webView) { + this.webView = webView; + } + + /** + * @param handler default handler,handle messages send by js without assigned handler name, + * if js message has handler name, it will be handled by named handlers registered by native + */ + public void setDefaultHandler(BridgeHandler handler) { + this.defaultHandler = handler; + } + + /** + * 获取到CallBackFunction data执行调用并且从数据集移除 + * + * @param url + */ + private void handlerReturnData(String url) { + String functionName = BridgeUtil.getFunctionFromReturnUrl(url); + CallBackFunction f = responseCallbacks.get(functionName); + String data = BridgeUtil.getDataFromReturnUrl(url); + if (f != null) { + f.onCallBack(data); + responseCallbacks.remove(functionName); + } + } + + @Override + public void send(String data) { + send(data, null); + } + + @Override + public void send(String data, CallBackFunction responseCallback) { + doSend(null, data, responseCallback); + } + + /** + * 保存message到消息队列 + * + * @param handlerName handlerName + * @param data data + * @param responseCallback CallBackFunction + */ + private void doSend(String handlerName, String data, CallBackFunction responseCallback) { + Message m = new Message(); + if (!TextUtils.isEmpty(data)) { + m.setData(data); + } + if (responseCallback != null) { + String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); + responseCallbacks.put(callbackStr, responseCallback); + m.setCallbackId(callbackStr); + } + if (!TextUtils.isEmpty(handlerName)) { + m.setHandlerName(handlerName); + } + queueMessage(m); + } + + /** + * list != null 添加到消息集合否则分发消息 + * + * @param m Message + */ + private void queueMessage(Message m) { + if (startupMessage != null) { + startupMessage.add(m); + } else { + dispatchMessage(m); + } + } + + /** + * 分发message 必须在主线程才分发成功 + * + * @param m Message + */ + private void dispatchMessage(Message m) { + String messageJson = m.toJson(); + //escape special characters for json string 为json字符串转义特殊字符 + messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); + messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); + messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'"); + messageJson = messageJson.replaceAll("%7B", URLEncoder.encode("%7B")); + messageJson = messageJson.replaceAll("%7D", URLEncoder.encode("%7D")); + messageJson = messageJson.replaceAll("%22", URLEncoder.encode("%22")); + String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); + // 必须要找主线程才会将数据传递出去 --- 划重点 + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { + this.loadUrl(javascriptCommand); + } + } + + /** + * 刷新消息队列 + */ + private void flushMessageQueue() { + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { + loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { + + @Override + public void onCallBack(String data) { + // deserializeMessage 反序列化消息 + List list = null; + try { + list = Message.toArrayList(data); + } catch (Exception e) { + Log.w(TAG, e); + return; + } + if (list == null || list.isEmpty()) { + return; + } + for (int i = 0; i < list.size(); i++) { + Message m = list.get(i); + String responseId = m.getResponseId(); + // 是否是response CallBackFunction + if (!TextUtils.isEmpty(responseId)) { + CallBackFunction function = responseCallbacks.get(responseId); + String responseData = m.getResponseData(); + function.onCallBack(responseData); + responseCallbacks.remove(responseId); + } else { + CallBackFunction responseFunction = null; + // if had callbackId 如果有回调Id + final String callbackId = m.getCallbackId(); + if (!TextUtils.isEmpty(callbackId)) { + responseFunction = new CallBackFunction() { + @Override + public void onCallBack(String data) { + Message responseMsg = new Message(); + responseMsg.setResponseId(callbackId); + responseMsg.setResponseData(data); + queueMessage(responseMsg); + } + }; + } else { + responseFunction = new CallBackFunction() { + @Override + public void onCallBack(String data) { + // do nothing + } + }; + } + // BridgeHandler执行 + BridgeHandler handler; + if (!TextUtils.isEmpty(m.getHandlerName())) { + handler = messageHandlers.get(m.getHandlerName()); + } else { + handler = defaultHandler; + } + if (handler != null) { + handler.handler(m.getData(), responseFunction); + } + } + } + } + }); + } + } + + private void loadUrl(String jsUrl, CallBackFunction returnCallback) { + this.loadUrl(jsUrl); + // 添加至 Map + responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); + } + + private void loadUrl(String jsUrl) { + webView.loadUrl(jsUrl); + } + + /** + * register handler,so that javascript can call it + * 注册处理程序,以便javascript调用它 + * + * @param handlerName handlerName + * @param handler BridgeHandler + */ + public void registerHandler(String handlerName, BridgeHandler handler) { + if (handler != null) { + // 添加至 Map + messageHandlers.put(handlerName, handler); + } + } + + /** + * unregister handler + * + * @param handlerName + */ + public void unregisterHandler(String handlerName) { + if (handlerName != null) { + messageHandlers.remove(handlerName); + } + } + + /** + * call javascript registered handler + * 调用javascript处理程序注册 + * + * @param handlerName handlerName + * @param data data + * @param callBack CallBackFunction + */ + public void callHandler(String handlerName, String data, CallBackFunction callBack) { + doSend(handlerName, data, callBack); + } + + public void onPageFinished() { + webViewLoadLocalJs(); + + if (getStartupMessage() != null) { + for (Message m : getStartupMessage()) { + dispatchMessage(m); + } + setStartupMessage(null); + } + } + + private void webViewLoadLocalJs() { + String jsContent = BridgeUtil.assetFile2Str(webView.getContext(), BridgeHelper.BRIDGE_JS); + loadUrl("javascript:" + jsContent); + } + + public boolean shouldOverrideUrlLoading(String url) { + try { + // decode 之前,处理 % 和 + + String replacedUrl = url.replaceAll("%(?![0-9a-fA-F]{2})", "%25").replaceAll("\\+", "%2B"); + url = URLDecoder.decode(replacedUrl, "UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.w(TAG, e); + } + + if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据 + handlerReturnData(url); + return true; + } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // + flushMessageQueue(); + return true; + } + return false; + } +} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java index ae9e55c..dee8584 100644 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeUtil.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.io.InputStreamReader; + public class BridgeUtil { final static String YY_OVERRIDE_SCHEMA = "yy://"; final static String YY_RETURN_DATA = YY_OVERRIDE_SCHEMA + "return/";//格式为 yy://return/{function}/returncontent @@ -17,53 +18,17 @@ public class BridgeUtil { final static String SPLIT_MARK = "/"; final static String CALLBACK_ID_FORMAT = "JAVA_CB_%s"; - final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"; + final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative(%s);"; final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();"; public final static String JAVASCRIPT_STR = "javascript:"; - // 例子 javascript:WebViewJavascriptBridge._fetchQueue(); --> _fetchQueue - public static String parseFunctionName(String jsUrl){ - return jsUrl.replace("javascript:WebViewJavascriptBridge.", "").replaceAll("\\(.*\\);", ""); - } - - // 获取到传递信息的body值 - // url = yy://return/_fetchQueue/[{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] - public static String getDataFromReturnUrl(String url) { - if(url.startsWith(YY_FETCH_QUEUE)) { - // return = [{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] - return url.replace(YY_FETCH_QUEUE, EMPTY_STR); - } - // temp = _fetchQueue/[{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] - String temp = url.replace(YY_RETURN_DATA, EMPTY_STR); - String[] functionAndData = temp.split(SPLIT_MARK); + public static final String JAVA_SCRIPT = "WebViewJavascriptBridge.js"; + public final static String UNDERLINE_STR = "_"; + public final static String CALLBACK_ID_FORMAT = "JAVA_CB_%s"; + public final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"; + public final static String JAVASCRIPT_STR = "javascript:%s"; - if(functionAndData.length >= 2) { - StringBuilder sb = new StringBuilder(); - for (int i = 1; i < functionAndData.length; i++) { - sb.append(functionAndData[i]); - } - // return = [{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] - return sb.toString(); - } - return null; - } - - // 获取到传递信息的方法 - // url = yy://return/_fetchQueue/[{"responseId":"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}] - public static String getFunctionFromReturnUrl(String url) { - // temp = _fetchQueue/[{"responseId":"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}] - String temp = url.replace(YY_RETURN_DATA, EMPTY_STR); - String[] functionAndData = temp.split(SPLIT_MARK); - if(functionAndData.length >= 1){ - // functionAndData[0] = _fetchQueue - return functionAndData[0]; - } - return null; - } - - - /** * js 文件将注入为第一个script引用 * @param view WebView @@ -117,6 +82,7 @@ public static String assetFile2Str(Context c, String urlStr){ try { in.close(); } catch (IOException e) { + e.printStackTrace(); } } } diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java index 5728681..4ec4f77 100644 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebView.java @@ -5,265 +5,263 @@ import android.os.Build; import android.os.Looper; import android.os.SystemClock; +import androidx.collection.ArrayMap; import android.text.TextUtils; import android.util.AttributeSet; -import android.webkit.SslErrorHandler; import android.webkit.WebView; +import android.util.Log; +import android.webkit.JavascriptInterface; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import com.github.lzyzsd.library.BuildConfig; +import com.google.gson.Gson; + + +import org.json.JSONObject; + import java.net.URLEncoder; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @SuppressLint("SetJavaScriptEnabled") -public class BridgeWebView extends WebView implements WebViewJavascriptBridge{ - - private final String TAG = "BridgeWebView"; +public class BridgeWebView extends WebView implements WebViewJavascriptBridge, BridgeWebViewClient.OnLoadJSListener { + private final int URL_MAX_CHARACTER_NUM=2097152; public static final String toLoadJs = "WebViewJavascriptBridge.js"; Map responseCallbacks = new HashMap(); Map messageHandlers = new HashMap(); BridgeHandler defaultHandler = new DefaultHandler(); + private Map mCallbacks = new ArrayMap<>(); - private List startupMessage = new ArrayList(); + private List mMessages = new ArrayList<>(); - public List getStartupMessage() { - return startupMessage; - } + private BridgeWebViewClient mClient; - public void setStartupMessage(List startupMessage) { - this.startupMessage = startupMessage; - } + private long mUniqueId = 0; - private long uniqueId = 0; + private boolean mJSLoaded = false; - public BridgeWebView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } + private Gson mGson; - public BridgeWebView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } + public BridgeWebView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } - public BridgeWebView(Context context) { - super(context); - init(); - } + public BridgeWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } - /** - * - * @param handler - * default handler,handle messages send by js without assigned handler name, - * if js message has handler name, it will be handled by named handlers registered by native - */ - public void setDefaultHandler(BridgeHandler handler) { - this.defaultHandler = handler; - } + public BridgeWebView(Context context) { + super(context); + init(); + } private void init() { - this.setVerticalScrollBarEnabled(false); - this.setHorizontalScrollBarEnabled(false); - this.getSettings().setJavaScriptEnabled(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + clearCache(true); + getSettings().setUseWideViewPort(true); +// webView.getSettings().setLoadWithOverviewMode(true); + getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); + getSettings().setJavaScriptEnabled(true); +// mContent.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + getSettings().setJavaScriptCanOpenWindowsAutomatically(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && BuildConfig.DEBUG) { WebView.setWebContentsDebuggingEnabled(true); } - this.setWebViewClient(generateBridgeWebViewClient()); - } + mClient = new BridgeWebViewClient(this); + super.setWebViewClient(mClient); + } + + public void setGson(Gson gson) { + mGson = gson; + } + + public boolean isJSLoaded() { + return mJSLoaded; + } + + public Map getCallbacks() { + return mCallbacks; + } - protected BridgeWebViewClient generateBridgeWebViewClient() { - return new BridgeWebViewClient(this); + @Override + public void setWebViewClient(WebViewClient client) { + mClient.setWebViewClient(client); + } + + @Override + public void onLoadStart() { + mJSLoaded = false; + } + + @Override + public void onLoadFinished() { + mJSLoaded = true; + if (mMessages != null) { + for (Object message : mMessages) { + dispatchMessage(message); + } + mMessages = null; + } + } + + @Override + public void sendToWeb(Object data) { + sendToWeb(data, (OnBridgeCallback) null); + } + + @Override + public void sendToWeb(Object data, OnBridgeCallback responseCallback) { + doSend(null, data, responseCallback); } /** - * 获取到CallBackFunction data执行调用并且从数据集移除 - * @param url + * call javascript registered handler + * 调用javascript处理程序注册 + * + * @param handlerName handlerName + * @param data data + * @param callBack OnBridgeCallback */ - void handlerReturnData(String url) { - String functionName = BridgeUtil.getFunctionFromReturnUrl(url); - CallBackFunction f = responseCallbacks.get(functionName); - String data = BridgeUtil.getDataFromReturnUrl(url); - if (f != null) { - f.onCallBack(data); - responseCallbacks.remove(functionName); - return; - } - } - - @Override - public void send(String data) { - send(data, null); - } - - @Override - public void send(String data, CallBackFunction responseCallback) { - doSend(null, data, responseCallback); - } + public void callHandler(String handlerName, String data, OnBridgeCallback callBack) { + doSend(handlerName, data, callBack); + } + + + @Override + public void sendToWeb(String function, Object... values) { + // 必须要找主线程才会将数据传递出去 --- 划重点 + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { + String jsCommand = String.format(function, values); + jsCommand = String.format(BridgeUtil.JAVASCRIPT_STR, jsCommand); + loadUrl(jsCommand); + } + } /** * 保存message到消息队列 - * @param handlerName handlerName - * @param data data - * @param responseCallback CallBackFunction + * + * @param handlerName handlerName + * @param data data + * @param responseCallback OnBridgeCallback */ - private void doSend(String handlerName, String data, CallBackFunction responseCallback) { - Message m = new Message(); - if (!TextUtils.isEmpty(data)) { - m.setData(data); - } - if (responseCallback != null) { - String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); - responseCallbacks.put(callbackStr, responseCallback); - m.setCallbackId(callbackStr); - } - if (!TextUtils.isEmpty(handlerName)) { - m.setHandlerName(handlerName); - } - queueMessage(m); - } + private void doSend(String handlerName, Object data, OnBridgeCallback responseCallback) { + if (!(data instanceof String) && mGson == null){ + return; + } + JSRequest request = new JSRequest(); + if (data != null) { + request.data = data instanceof String ? (String) data : mGson.toJson(data); + } + if (responseCallback != null) { + String callbackId = String.format(BridgeUtil.CALLBACK_ID_FORMAT, (++mUniqueId) + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); + mCallbacks.put(callbackId, responseCallback); + request.callbackId = callbackId; + } + if (!TextUtils.isEmpty(handlerName)) { + request.handlerName = handlerName; + } + queueMessage(request); + } /** * list != null 添加到消息集合否则分发消息 - * @param m Message + * + * @param message Message */ - private void queueMessage(Message m) { - if (startupMessage != null) { - startupMessage.add(m); - } else { - dispatchMessage(m); - } - } + private void queueMessage(Object message) { + if (mMessages != null) { + mMessages.add(message); + } else { + dispatchMessage(message); + } + } /** * 分发message 必须在主线程才分发成功 - * @param m Message + * + * @param message Message */ - void dispatchMessage(Message m) { - String messageJson = m.toJson(); + private void dispatchMessage(Object message) { + if (mGson == null){ + return; + } + String messageJson = mGson.toJson(message); //escape special characters for json string 为json字符串转义特殊字符 - messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); - messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); - messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'"); - messageJson = messageJson.replaceAll("%7B", URLEncoder.encode("%7B")); - messageJson = messageJson.replaceAll("%7D", URLEncoder.encode("%7D")); - messageJson = messageJson.replaceAll("%22", URLEncoder.encode("%22")); + + // 系统原生 API 做 Json转义,没必要自己正则替换,而且替换不一定完整 + messageJson = JSONObject.quote(messageJson); + String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); // 必须要找主线程才会将数据传递出去 --- 划重点 if (Thread.currentThread() == Looper.getMainLooper().getThread()) { - this.loadUrl(javascriptCommand); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT&&javascriptCommand.length()>=URL_MAX_CHARACTER_NUM) { + this.evaluateJavascript(javascriptCommand,null); + }else { + this.loadUrl(javascriptCommand); + } } } - /** - * 刷新消息队列 - */ - void flushMessageQueue() { - if (Thread.currentThread() == Looper.getMainLooper().getThread()) { - loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { - - @Override - public void onCallBack(String data) { - // deserializeMessage 反序列化消息 - List list = null; - try { - list = Message.toArrayList(data); - } catch (Exception e) { - e.printStackTrace(); - return; - } - if (list == null || list.size() == 0) { - return; - } - for (int i = 0; i < list.size(); i++) { - Message m = list.get(i); - String responseId = m.getResponseId(); - // 是否是response CallBackFunction - if (!TextUtils.isEmpty(responseId)) { - CallBackFunction function = responseCallbacks.get(responseId); - String responseData = m.getResponseData(); - function.onCallBack(responseData); - responseCallbacks.remove(responseId); - } else { - CallBackFunction responseFunction = null; - // if had callbackId 如果有回调Id - final String callbackId = m.getCallbackId(); - if (!TextUtils.isEmpty(callbackId)) { - responseFunction = new CallBackFunction() { - @Override - public void onCallBack(String data) { - Message responseMsg = new Message(); - responseMsg.setResponseId(callbackId); - responseMsg.setResponseData(data); - queueMessage(responseMsg); - } - }; - } else { - responseFunction = new CallBackFunction() { - @Override - public void onCallBack(String data) { - // do nothing - } - }; - } - // BridgeHandler执行 - BridgeHandler handler; - if (!TextUtils.isEmpty(m.getHandlerName())) { - handler = messageHandlers.get(m.getHandlerName()); - } else { - handler = defaultHandler; - } - if (handler != null){ - handler.handler(m.getData(), responseFunction); - } - } - } - } - }); - } - } - - - public void loadUrl(String jsUrl, CallBackFunction returnCallback) { - this.loadUrl(jsUrl); - // 添加至 Map - responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); - } - - /** - * register handler,so that javascript can call it - * 注册处理程序,以便javascript调用它 - * @param handlerName handlerName - * @param handler BridgeHandler - */ - public void registerHandler(String handlerName, BridgeHandler handler) { - if (handler != null) { - // 添加至 Map - messageHandlers.put(handlerName, handler); - } - } - - /** - * unregister handler - * - * @param handlerName - */ - public void unregisterHandler(String handlerName) { - if (handlerName != null) { - messageHandlers.remove(handlerName); - } - } - - /** - * call javascript registered handler - * 调用javascript处理程序注册 - * @param handlerName handlerName - * @param data data - * @param callBack CallBackFunction - */ - public void callHandler(String handlerName, String data, CallBackFunction callBack) { - doSend(handlerName, data, callBack); - } + public void sendResponse(Object data, String callbackId) { + if (!(data instanceof String) && mGson == null){ + return; + } + if (!TextUtils.isEmpty(callbackId)) { + final JSResponse response = new JSResponse(); + response.responseId = callbackId; + response.responseData = data instanceof String ? (String) data : mGson.toJson(data); + if (Thread.currentThread() == Looper.getMainLooper().getThread()){ + dispatchMessage(response); + }else { + post(new Runnable() { + @Override + public void run() { + dispatchMessage(response); + } + }); + } + + } + } + + @Override + public void destroy() { + super.destroy(); + mCallbacks.clear(); + } + + public static abstract class BaseJavascriptInterface { + + private Map mCallbacks; + + public BaseJavascriptInterface(Map callbacks) { + mCallbacks = callbacks; + } + + @JavascriptInterface + public String send(String data, String callbackId) { + Log.d("chromium", data + ", callbackId: " + callbackId + " " + Thread.currentThread().getName()); + return send(data); + } + + @JavascriptInterface + public void response(String data, String responseId) { + Log.d("chromium", data + ", responseId: " + responseId + " " + Thread.currentThread().getName()); + if (!TextUtils.isEmpty(responseId)) { + OnBridgeCallback function = mCallbacks.remove(responseId); + if (function != null) { + function.onCallBack(data); + } + } + } + + public abstract String send(String data); + } } diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java index 460f9cd..cff0ab4 100644 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/BridgeWebViewClient.java @@ -1,109 +1,258 @@ package com.github.lzyzsd.jsbridge; import android.graphics.Bitmap; - import android.net.http.SslError; import android.os.Build; +import androidx.annotation.Nullable; +import android.view.KeyEvent; +import android.webkit.ClientCertRequest; +import android.webkit.HttpAuthHandler; +import android.webkit.RenderProcessGoneDetail; +import android.webkit.SafeBrowsingResponse; import android.webkit.SslErrorHandler; +import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; - /** * 如果要自定义WebViewClient必须要集成此类 * Created by bruce on 10/28/15. */ -public class BridgeWebViewClient extends WebViewClient { - private BridgeWebView webView; +class BridgeWebViewClient extends WebViewClient { + + private OnLoadJSListener mListener; + + private WebViewClient mClient; - public BridgeWebViewClient(BridgeWebView webView) { - this.webView = webView; + public BridgeWebViewClient(OnLoadJSListener listener) { + mListener = listener; + } + + public void setWebViewClient(WebViewClient client) { + mClient = client; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - try { - url = URLDecoder.decode(url, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + view.loadUrl(url); + if (mClient != null) { + return mClient.shouldOverrideUrlLoading(view, url); } + return super.shouldOverrideUrlLoading(view, url); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + view.loadUrl(request.getUrl().getAuthority()); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && mClient != null) { + return mClient.shouldOverrideUrlLoading(view, request); + } + return super.shouldOverrideUrlLoading(view, request); + } - if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据 - webView.handlerReturnData(url); - return true; - } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // - webView.flushMessageQueue(); - return true; + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (mClient != null) { + mClient.onPageStarted(view, url, favicon); } else { - return this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, url); + super.onPageStarted(view, url, favicon); } + } - // 增加shouldOverrideUrlLoading在api》=24时 @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + public void onPageFinished(WebView view, String url) { + if (mClient != null) { + mClient.onPageFinished(view, url); + } else { + super.onPageFinished(view, url); + } + mListener.onLoadStart(); + BridgeUtil.webViewLoadLocalJs(view, BridgeUtil.JAVA_SCRIPT); + mListener.onLoadFinished(); + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - String url = request.getUrl().toString(); - try { - url = URLDecoder.decode(url, "UTF-8"); - } catch (UnsupportedEncodingException ex) { - ex.printStackTrace(); - } - if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据 - webView.handlerReturnData(url); - return true; - } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { // - webView.flushMessageQueue(); - return true; - } else { - return this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, request); - } - }else { - return super.shouldOverrideUrlLoading(view, request); + @Override + public void onLoadResource(WebView view, String url) { + if (mClient != null) { + mClient.onLoadResource(view, url); + } else { + super.onLoadResource(view, url); } } @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); + public void onPageCommitVisible(WebView view, String url) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) { + mClient.onPageCommitVisible(view, url); + } else { + super.onPageCommitVisible(view, url); + } } + @Nullable @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + if (mClient != null) { + return mClient.shouldInterceptRequest(view, url); + } + return super.shouldInterceptRequest(view, url); + } - if (BridgeWebView.toLoadJs != null) { - BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs); + @Nullable + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mClient != null) { + return mClient.shouldInterceptRequest(view, request); } + return super.shouldInterceptRequest(view, request); + } - // - if (webView.getStartupMessage() != null) { - for (Message m : webView.getStartupMessage()) { - webView.dispatchMessage(m); - } - webView.setStartupMessage(null); + @Override + public void onTooManyRedirects(WebView view, android.os.Message cancelMsg, android.os.Message continueMsg) { + if (mClient != null) { + mClient.onTooManyRedirects(view, cancelMsg, continueMsg); + } else { + super.onTooManyRedirects(view, cancelMsg, continueMsg); } + } - // - onCustomPageFinishd(view,url); + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + if (mClient != null) { + mClient.onReceivedError(view, errorCode, description, failingUrl); + } else { + super.onReceivedError(view, errorCode, description, failingUrl); + } + } + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) { + mClient.onReceivedError(view, request, error); + } else { + super.onReceivedError(view, request, error); + } } + @Override + public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mClient != null) { + mClient.onReceivedHttpError(view, request, errorResponse); + } else { + super.onReceivedHttpError(view, request, errorResponse); + } - protected boolean onCustomShouldOverrideUrlLoading(String url) { - return false; } + @Override + public void onFormResubmission(WebView view, android.os.Message dontResend, android.os.Message resend) { + if (mClient != null) { + mClient.onFormResubmission(view, dontResend, resend); + } else { + super.onFormResubmission(view, dontResend, resend); + } - protected void onCustomPageFinishd(WebView view, String url){ + } + @Override + public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { + if (mClient != null) { + mClient.doUpdateVisitedHistory(view, url, isReload); + } else { + super.doUpdateVisitedHistory(view, url, isReload); + } + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + if (mClient != null) { + mClient.onReceivedSslError(view, handler, error); + } else { + super.onReceivedSslError(view, handler, error); + } + } + + @Override + public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mClient != null) { + mClient.onReceivedClientCertRequest(view, request); + } else { + super.onReceivedClientCertRequest(view, request); + } + } + + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { + if (mClient != null) { + mClient.onReceivedHttpAuthRequest(view, handler, host, realm); + } else { + super.onReceivedHttpAuthRequest(view, handler, host, realm); + } + } + + @Override + public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { + if (mClient != null) { + return mClient.shouldOverrideKeyEvent(view, event); + } + return super.shouldOverrideKeyEvent(view, event); + } + + @Override + public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + if (mClient != null) { + mClient.onUnhandledKeyEvent(view, event); + } else { + super.onUnhandledKeyEvent(view, event); + } + } + + @Override + public void onScaleChanged(WebView view, float oldScale, float newScale) { + if (mClient != null) { + mClient.onScaleChanged(view, oldScale, newScale); + } else { + super.onScaleChanged(view, oldScale, newScale); + } + } + + @Override + public void onReceivedLoginRequest(WebView view, String realm, @Nullable String account, String args) { + if (mClient != null) { + mClient.onReceivedLoginRequest(view, realm, account, args); + } else { + super.onReceivedLoginRequest(view, realm, account, args); + } + } + + @Override + public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mClient != null){ + return mClient.onRenderProcessGone(view, detail); + } + return super.onRenderProcessGone(view, detail); + } + + @Override + public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, SafeBrowsingResponse callback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 && mClient != null){ + mClient.onSafeBrowsingHit(view, request, threatType, callback); + }else { + super.onSafeBrowsingHit(view, request, threatType, callback); + } } + public interface OnLoadJSListener { + + void onLoadStart(); + void onLoadFinished(); + } } \ No newline at end of file diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/CallBackFunction.java b/library/src/main/java/com/github/lzyzsd/jsbridge/CallBackFunction.java deleted file mode 100644 index f41df5b..0000000 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/CallBackFunction.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.lzyzsd.jsbridge; - -public interface CallBackFunction { - - public void onCallBack(String data); - -} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java b/library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java deleted file mode 100644 index 22e5890..0000000 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/DefaultHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.lzyzsd.jsbridge; - -public class DefaultHandler implements BridgeHandler{ - - String TAG = "DefaultHandler"; - - @Override - public void handler(String data, CallBackFunction function) { - if(function != null){ - function.onCallBack("DefaultHandler response data"); - } - } - -} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java b/library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java new file mode 100644 index 0000000..8de9867 --- /dev/null +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/IWebView.java @@ -0,0 +1,16 @@ +package com.github.lzyzsd.jsbridge; + +import android.content.Context; + +/** + * WebView功能接口. + * + * @author ZhengAn + * @date 2019-07-01 + */ +public interface IWebView { + + Context getContext(); + + void loadUrl(String url); +} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java b/library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java new file mode 100644 index 0000000..b4c479d --- /dev/null +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/JSRequest.java @@ -0,0 +1,15 @@ +package com.github.lzyzsd.jsbridge; + +/** + * Created on 2019/7/10. + * Author: bigwang + * Description: + */ +class JSRequest { + + public String callbackId; + + public String data; + + public String handlerName; +} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java b/library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java new file mode 100644 index 0000000..bf5e2f1 --- /dev/null +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/JSResponse.java @@ -0,0 +1,13 @@ +package com.github.lzyzsd.jsbridge; + +/** + * Created on 2019/7/10. + * Author: bigwang + * Description: + */ +class JSResponse { + + public String responseId; + + public String responseData; +} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/Message.java b/library/src/main/java/com/github/lzyzsd/jsbridge/Message.java deleted file mode 100644 index 00d25db..0000000 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/Message.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.lzyzsd.jsbridge; - -import android.text.TextUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.List; -import org.json.JSONTokener; - -/** - * data of bridge - * @author haoqing - * - */ -public class Message { - - private String callbackId; //callbackId - private String responseId; //responseId - private String responseData; //responseData - private String data; //data of message - private String handlerName; //name of handler - - private final static String CALLBACK_ID_STR = "callbackId"; - private final static String RESPONSE_ID_STR = "responseId"; - private final static String RESPONSE_DATA_STR = "responseData"; - private final static String DATA_STR = "data"; - private final static String HANDLER_NAME_STR = "handlerName"; - - public String getResponseId() { - return responseId; - } - public void setResponseId(String responseId) { - this.responseId = responseId; - } - public String getResponseData() { - return responseData; - } - public void setResponseData(String responseData) { - this.responseData = responseData; - } - public String getCallbackId() { - return callbackId; - } - public void setCallbackId(String callbackId) { - this.callbackId = callbackId; - } - public String getData() { - return data; - } - public void setData(String data) { - this.data = data; - } - public String getHandlerName() { - return handlerName; - } - public void setHandlerName(String handlerName) { - this.handlerName = handlerName; - } - - public String toJson() { - JSONObject jsonObject= new JSONObject(); - try { - jsonObject.put(CALLBACK_ID_STR, getCallbackId()); - jsonObject.put(DATA_STR, getData()); - jsonObject.put(HANDLER_NAME_STR, getHandlerName()); - String data = getResponseData(); - if (TextUtils.isEmpty(data)) { - jsonObject.put(RESPONSE_DATA_STR, data); - } else { - jsonObject.put(RESPONSE_DATA_STR, new JSONTokener(data).nextValue()); - } - jsonObject.put(RESPONSE_DATA_STR, getResponseData()); - jsonObject.put(RESPONSE_ID_STR, getResponseId()); - return jsonObject.toString(); - } catch (JSONException e) { - e.printStackTrace(); - } - return null; - } - - public static Message toObject(String jsonStr) { - Message m = new Message(); - try { - JSONObject jsonObject = new JSONObject(jsonStr); - m.setHandlerName(jsonObject.has(HANDLER_NAME_STR) ? jsonObject.getString(HANDLER_NAME_STR):null); - m.setCallbackId(jsonObject.has(CALLBACK_ID_STR) ? jsonObject.getString(CALLBACK_ID_STR):null); - m.setResponseData(jsonObject.has(RESPONSE_DATA_STR) ? jsonObject.getString(RESPONSE_DATA_STR):null); - m.setResponseId(jsonObject.has(RESPONSE_ID_STR) ? jsonObject.getString(RESPONSE_ID_STR):null); - m.setData(jsonObject.has(DATA_STR) ? jsonObject.getString(DATA_STR):null); - return m; - } catch (JSONException e) { - e.printStackTrace(); - } - return m; - } - - public static List toArrayList(String jsonStr){ - List list = new ArrayList(); - try { - JSONArray jsonArray = new JSONArray(jsonStr); - for(int i = 0; i < jsonArray.length(); i++){ - Message m = new Message(); - JSONObject jsonObject = jsonArray.getJSONObject(i); - m.setHandlerName(jsonObject.has(HANDLER_NAME_STR) ? jsonObject.getString(HANDLER_NAME_STR):null); - m.setCallbackId(jsonObject.has(CALLBACK_ID_STR) ? jsonObject.getString(CALLBACK_ID_STR):null); - m.setResponseData(jsonObject.has(RESPONSE_DATA_STR) ? jsonObject.getString(RESPONSE_DATA_STR):null); - m.setResponseId(jsonObject.has(RESPONSE_ID_STR) ? jsonObject.getString(RESPONSE_ID_STR):null); - m.setData(jsonObject.has(DATA_STR) ? jsonObject.getString(DATA_STR):null); - list.add(m); - } - } catch (JSONException e) { - e.printStackTrace(); - } - return list; - } -} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java b/library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java new file mode 100644 index 0000000..43b6d0e --- /dev/null +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/OnBridgeCallback.java @@ -0,0 +1,7 @@ +package com.github.lzyzsd.jsbridge; + +public interface OnBridgeCallback { + + void onCallBack(String data); + +} diff --git a/library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java b/library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java index 594b81f..69c4d3d 100644 --- a/library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java +++ b/library/src/main/java/com/github/lzyzsd/jsbridge/WebViewJavascriptBridge.java @@ -1,11 +1,12 @@ package com.github.lzyzsd.jsbridge; -public interface WebViewJavascriptBridge { - - public void send(String data); - public void send(String data, CallBackFunction responseCallback); - +interface WebViewJavascriptBridge { + void sendToWeb(Object data); + + void sendToWeb(Object data, OnBridgeCallback responseCallback); + + void sendToWeb(String function, Object... values); }