├── .gitignore
├── README.md
├── WebViewJavascriptBridge
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── assets
│ ├── ExampleApp.html
│ └── WebViewJavascriptBridge.js.txt
├── gen
│ └── com
│ │ └── example
│ │ └── test
│ │ ├── BuildConfig.java
│ │ └── R.java
├── libs
│ └── android-support-v4.jar
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── activity_main.xml
│ └── values
│ │ └── strings.xml
└── src
│ └── com
│ └── example
│ └── test
│ ├── MainActivity.java
│ └── WVJBWebViewClient.java
└── tb408s.h5
/.gitignore:
--------------------------------------------------------------------------------
1 | /WebViewJavascriptBridge/bin
2 | .DS_Store
3 |
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebViewJavascriptBridge
2 | WebViewJavascriptBridge for Android 是 WebViewJavascriptBridge for iOS/OSX的Android 实现, 项目里的WebViewJavascriptBridge.js.txt, 及测试网页ExampleApp.html 直接来自 WebViewJavascriptBridge for iOS/OSX.
3 |
4 | 使用方法:
5 |
6 | 1. 复制WVJBWebViewClient.java 到 Android 项目 src 下的一个子目录中, 按需修改package名称;
7 | 2. 复制WebViewJavascriptBridge.js.txt到assets目录;
8 | 3. 子类化 WVJBWebViewClient, 例如:
9 |
10 | class MyWebViewClient extends WVJBWebViewClient {
11 |
12 | public MyWebViewClient(WebView webView) {
13 |
14 | //需要支持JS send 方法时
15 | super(webView, new WVJBWebViewClient.WVJBHandler() {
16 | @Override
17 | public void request(Object data, WVJBResponseCallback callback) {
18 | //处理代码
19 | }
20 | });
21 |
22 | /*
23 | //不需要支持JS send 方法时
24 | super(webView);
25 | */
26 | }
27 |
28 | @Override
29 | public void onPageFinished(WebView view, String url) {
30 | //处理代码
31 | super.onPageFinished(view, url);
32 | }
33 |
34 | @Override
35 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
36 | //处理代码
37 | return super.shouldOverrideUrlLoading(view, url);
38 | }
39 |
40 | 4. 设置WebView
41 | webView.setWebViewClient(new MyWebViewClient(webView));
42 |
43 | 5. 用 excuteJavascript 方法执行脚本:
44 |
45 | excuteJavascript(script); //不需要返回值, script前不要加javascript:前缀
46 |
47 | 或
48 |
49 | excuteJavascript(script, callback); //需要返回值, script前不要加javascript:前缀
50 |
51 | executeJavascript方法的内部实现机制:
52 |
53 | a. Android 4.4及更高版本下, 使用WebView.evaluateJavascript方法执行脚本;
54 |
55 | b. Android 4.4以下版本若需要返回值则采用addJavascriptInterface机制实现;
56 |
57 | c. Android 4.4以下版本若不需要返回值则使用loadUrl方法执行脚本.
58 |
59 |
60 | 6. WebViewJavascriptBridge for iOS/OSX 的下载地址: https://github.com/marcuswestin/WebViewJavascriptBridge
61 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/.gitignore:
--------------------------------------------------------------------------------
1 | /gen
2 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | WebViewJavascriptBridge
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/assets/ExampleApp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 | WebViewJavascriptBridge Demo
11 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/assets/WebViewJavascriptBridge.js.txt:
--------------------------------------------------------------------------------
1 | ;(function() {
2 | if (window.WebViewJavascriptBridge) { return }
3 | var messagingIframe
4 | var sendMessageQueue = []
5 | var receiveMessageQueue = []
6 | var messageHandlers = {}
7 |
8 | var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
9 | var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'
10 |
11 | var responseCallbacks = {}
12 | var uniqueId = 1
13 |
14 | function _createQueueReadyIframe(doc) {
15 | messagingIframe = doc.createElement('iframe')
16 | messagingIframe.style.display = 'none'
17 | doc.documentElement.appendChild(messagingIframe)
18 | }
19 |
20 | function init(messageHandler) {
21 | if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }
22 | WebViewJavascriptBridge._messageHandler = messageHandler
23 | var receivedMessages = receiveMessageQueue
24 | receiveMessageQueue = null
25 | for (var i=0; i
4 |
5 |
10 |
11 |
17 |
18 |
26 |
27 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 | Hello world!
6 | Send message
7 | Call handler
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/src/com/example/test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.test;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import android.annotation.SuppressLint;
7 | import android.app.Activity;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.view.View.OnClickListener;
11 | import android.webkit.WebChromeClient;
12 | import android.webkit.WebView;
13 | import android.widget.Toast;
14 |
15 |
16 | @SuppressLint("SetJavaScriptEnabled")
17 | public class MainActivity extends Activity {
18 |
19 | private WebView webView;
20 | private WVJBWebViewClient webViewClient;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_main);
26 | webView=(WebView) findViewById(R.id.webview);
27 | webView.getSettings().setJavaScriptEnabled(true);
28 | webView.setWebChromeClient(new WebChromeClient());
29 | webView.loadUrl("file:///android_asset/ExampleApp.html");
30 |
31 | webViewClient = new MyWebViewClient(webView);
32 | webViewClient.enableLogging();
33 | webView.setWebViewClient(webViewClient);
34 |
35 | findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
36 |
37 | @Override
38 | public void onClick(View v) {
39 | webViewClient.send("A string sent from ObjC to JS", new WVJBWebViewClient.WVJBResponseCallback() {
40 |
41 | @Override
42 | public void callback(Object data) {
43 | Toast.makeText(MainActivity.this, "sendMessage got response: " + data, Toast.LENGTH_LONG).show();
44 | }
45 | });
46 | }
47 |
48 | });
49 |
50 | findViewById(R.id.button2).setOnClickListener(new OnClickListener() {
51 |
52 | @Override
53 | public void onClick(View v) {
54 | try {
55 | webViewClient.callHandler("testJavascriptHandler", new JSONObject("{\"greetingFromObjC\": \"Hi there, JS!\" }"), new WVJBWebViewClient.WVJBResponseCallback() {
56 |
57 | @Override
58 | public void callback(Object data) {
59 | Toast.makeText(MainActivity.this, "testJavascriptHandler responded: " + data, Toast.LENGTH_LONG).show();
60 | }
61 | });
62 | } catch (JSONException e) {
63 | e.printStackTrace();
64 | }
65 | }
66 | });
67 |
68 | }
69 |
70 | class MyWebViewClient extends WVJBWebViewClient {
71 | public MyWebViewClient(WebView webView) {
72 |
73 | // support js send
74 | super(webView, new WVJBWebViewClient.WVJBHandler() {
75 |
76 | @Override
77 | public void request(Object data, WVJBResponseCallback callback) {
78 | Toast.makeText(MainActivity.this, "ObjC Received message from JS:" + data, Toast.LENGTH_LONG).show();
79 | callback.callback("Response for message from ObjC!");
80 | }
81 | });
82 |
83 | /*
84 | // not support js send
85 | super(webView);
86 | */
87 |
88 | enableLogging();
89 |
90 | registerHandler("testObjcCallback", new WVJBWebViewClient.WVJBHandler() {
91 |
92 | @Override
93 | public void request(Object data, WVJBResponseCallback callback) {
94 | Toast.makeText(MainActivity.this, "testObjcCallback called:" + data, Toast.LENGTH_LONG).show();
95 | callback.callback("Response from testObjcCallback!");
96 | }
97 | });
98 |
99 | send("A string sent from ObjC before Webview has loaded.", new WVJBResponseCallback() {
100 |
101 | @Override
102 | public void callback(Object data) {
103 | Toast.makeText(MainActivity.this, "ObjC got response! :" + data, Toast.LENGTH_LONG).show();
104 | }
105 | });
106 |
107 | try {
108 | callHandler("testJavascriptHandler", new JSONObject("{\"foo\":\"before ready\" }"),new WVJBResponseCallback() {
109 |
110 | @Override
111 | public void callback(Object data) {
112 | Toast.makeText(MainActivity.this, "ObjC call testJavascriptHandler got response! :" + data, Toast.LENGTH_LONG).show();
113 | }
114 | });
115 | } catch (JSONException e) {
116 | e.printStackTrace();
117 | }
118 |
119 | }
120 |
121 | @Override
122 | public void onPageFinished(WebView view, String url) {
123 | super.onPageFinished(view, url);
124 | }
125 |
126 | @Override
127 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
128 | return super.shouldOverrideUrlLoading(view, url);
129 | }
130 |
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/WebViewJavascriptBridge/src/com/example/test/WVJBWebViewClient.java:
--------------------------------------------------------------------------------
1 | package com.example.test;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.ArrayList;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | import org.json.JSONArray;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import android.annotation.SuppressLint;
14 | import android.os.Build;
15 | import android.util.Log;
16 | import android.webkit.JavascriptInterface;
17 | import android.webkit.ValueCallback;
18 | import android.webkit.WebView;
19 | import android.webkit.WebViewClient;
20 |
21 | @SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
22 | public class WVJBWebViewClient extends WebViewClient {
23 |
24 | private static final String kTag = "WVJB";
25 | private static final String kInterface = kTag + "Interface";
26 | private static final String kCustomProtocolScheme = "wvjbscheme";
27 | private static final String kQueueHasMessage = "__WVJB_QUEUE_MESSAGE__";
28 |
29 | private static boolean logging = false;
30 |
31 | protected WebView webView;
32 |
33 | private ArrayList startupMessageQueue = null;
34 | private Map responseCallbacks = null;
35 | private Map messageHandlers = null;
36 | private long uniqueId = 0;
37 | private WVJBHandler messageHandler;
38 | private MyJavascriptInterface myInterface = new MyJavascriptInterface();
39 |
40 | public interface WVJBResponseCallback {
41 | public void callback(Object data);
42 | }
43 |
44 | public interface WVJBHandler {
45 | public void request(Object data, WVJBResponseCallback callback);
46 | }
47 |
48 | public WVJBWebViewClient(WebView webView) {
49 | this(webView, null);
50 | }
51 |
52 | public WVJBWebViewClient(WebView webView, WVJBHandler messageHandler) {
53 | this.webView = webView;
54 | this.webView.getSettings().setJavaScriptEnabled(true);
55 | this.webView.addJavascriptInterface(myInterface, kInterface);
56 | this.responseCallbacks = new HashMap();
57 | this.messageHandlers = new HashMap();
58 | this.startupMessageQueue = new ArrayList();
59 | this.messageHandler = messageHandler;
60 | }
61 |
62 | public void enableLogging() {
63 | logging = true;
64 | }
65 |
66 | public void send(Object data) {
67 | send(data, null);
68 | }
69 |
70 | public void send(Object data, WVJBResponseCallback responseCallback) {
71 | sendData(data, responseCallback, null);
72 | }
73 |
74 | public void callHandler(String handlerName) {
75 | callHandler(handlerName, null, null);
76 | }
77 |
78 | public void callHandler(String handlerName, Object data) {
79 | callHandler(handlerName, data, null);
80 | }
81 |
82 | public void callHandler(String handlerName, Object data,
83 | WVJBResponseCallback responseCallback) {
84 | sendData(data, responseCallback, handlerName);
85 | }
86 |
87 | public void registerHandler(String handlerName, WVJBHandler handler) {
88 | if (handlerName == null || handlerName.length() == 0 || handler == null)
89 | return;
90 | messageHandlers.put(handlerName, handler);
91 | }
92 |
93 | private void sendData(Object data, WVJBResponseCallback responseCallback,
94 | String handlerName) {
95 | if (data == null && (handlerName == null || handlerName.length() == 0))
96 | return;
97 | WVJBMessage message = new WVJBMessage();
98 | if (data != null) {
99 | message.data = data;
100 | }
101 | if (responseCallback != null) {
102 | String callbackId = "objc_cb_" + (++uniqueId);
103 | responseCallbacks.put(callbackId, responseCallback);
104 | message.callbackId = callbackId;
105 | }
106 | if (handlerName != null) {
107 | message.handlerName = handlerName;
108 | }
109 | queueMessage(message);
110 | }
111 |
112 | private void queueMessage(WVJBMessage message) {
113 | if (startupMessageQueue != null) {
114 | startupMessageQueue.add(message);
115 | } else {
116 | dispatchMessage(message);
117 | }
118 | }
119 |
120 | private void dispatchMessage(WVJBMessage message) {
121 | String messageJSON = message2JSONObject(message).toString()
122 | .replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"")
123 | .replaceAll("\'", "\\\\\'").replaceAll("\n", "\\\\\n")
124 | .replaceAll("\r", "\\\\\r").replaceAll("\f", "\\\\\f");
125 |
126 | log("SEND", messageJSON);
127 |
128 | executeJavascript("WebViewJavascriptBridge._handleMessageFromObjC('"
129 | + messageJSON + "');");
130 | }
131 |
132 | private JSONObject message2JSONObject(WVJBMessage message) {
133 | JSONObject jo = new JSONObject();
134 | try {
135 | if (message.callbackId != null) {
136 | jo.put("callbackId", message.callbackId);
137 | }
138 | if (message.data != null) {
139 | jo.put("data", message.data);
140 | }
141 | if (message.handlerName != null) {
142 | jo.put("handlerName", message.handlerName);
143 | }
144 | if (message.responseId != null) {
145 | jo.put("responseId", message.responseId);
146 | }
147 | if (message.responseData != null) {
148 | jo.put("responseData", message.responseData);
149 | }
150 | } catch (JSONException e) {
151 | e.printStackTrace();
152 | }
153 | return jo;
154 | }
155 |
156 | private WVJBMessage JSONObject2WVJBMessage(JSONObject jo) {
157 | WVJBMessage message = new WVJBMessage();
158 | try {
159 | if (jo.has("callbackId")) {
160 | message.callbackId = jo.getString("callbackId");
161 | }
162 | if (jo.has("data")) {
163 | message.data = jo.get("data");
164 | }
165 | if (jo.has("handlerName")) {
166 | message.handlerName = jo.getString("handlerName");
167 | }
168 | if (jo.has("responseId")) {
169 | message.responseId = jo.getString("responseId");
170 | }
171 | if (jo.has("responseData")) {
172 | message.responseData = jo.get("responseData");
173 | }
174 | } catch (JSONException e) {
175 | e.printStackTrace();
176 | }
177 | return message;
178 | }
179 |
180 | private void flushMessageQueue() {
181 | String script = "WebViewJavascriptBridge._fetchQueue()";
182 | executeJavascript(script, new JavascriptCallback() {
183 | public void onReceiveValue(String messageQueueString) {
184 | if (messageQueueString == null
185 | || messageQueueString.length() == 0)
186 | return;
187 | processQueueMessage(messageQueueString);
188 | }
189 | });
190 | }
191 |
192 | private void processQueueMessage(String messageQueueString) {
193 | try {
194 | JSONArray messages = new JSONArray(messageQueueString);
195 | for (int i = 0; i < messages.length(); i++) {
196 | JSONObject jo = messages.getJSONObject(i);
197 |
198 | log("RCVD", jo);
199 |
200 | WVJBMessage message = JSONObject2WVJBMessage(jo);
201 | if (message.responseId != null) {
202 | WVJBResponseCallback responseCallback = responseCallbacks
203 | .remove(message.responseId);
204 | if (responseCallback != null) {
205 | responseCallback.callback(message.responseData);
206 | }
207 | } else {
208 | WVJBResponseCallback responseCallback = null;
209 | if (message.callbackId != null) {
210 | final String callbackId = message.callbackId;
211 | responseCallback = new WVJBResponseCallback() {
212 | @Override
213 | public void callback(Object data) {
214 | WVJBMessage msg = new WVJBMessage();
215 | msg.responseId = callbackId;
216 | msg.responseData = data;
217 | queueMessage(msg);
218 | }
219 | };
220 | }
221 |
222 | WVJBHandler handler;
223 | if (message.handlerName != null) {
224 | handler = messageHandlers.get(message.handlerName);
225 | } else {
226 | handler = messageHandler;
227 | }
228 | if (handler != null) {
229 | handler.request(message.data, responseCallback);
230 | }
231 | }
232 | }
233 | } catch (JSONException e) {
234 | e.printStackTrace();
235 | }
236 | }
237 |
238 | void log(String action, Object json) {
239 | if (!logging)
240 | return;
241 | String jsonString = String.valueOf(json);
242 | if (jsonString.length() > 500) {
243 | Log.i(kTag, action + ": " + jsonString.substring(0, 500) + " [...]");
244 | } else {
245 | Log.i(kTag, action + ": " + jsonString);
246 | }
247 | }
248 |
249 | public void executeJavascript(String script) {
250 | executeJavascript(script, null);
251 | }
252 |
253 | public void executeJavascript(String script,
254 | final JavascriptCallback callback) {
255 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
256 | webView.evaluateJavascript(script, new ValueCallback() {
257 | @Override
258 | public void onReceiveValue(String value) {
259 | if (callback != null) {
260 | if (value != null && value.startsWith("\"")
261 | && value.endsWith("\"")) {
262 | value = value.substring(1, value.length() - 1)
263 | .replaceAll("\\\\", "");
264 | }
265 | callback.onReceiveValue(value);
266 | }
267 | }
268 | });
269 | } else {
270 | if (callback != null) {
271 | myInterface.addCallback(++uniqueId + "", callback);
272 | webView.loadUrl("javascript:window." + kInterface
273 | + ".onResultForScript(" + uniqueId + "," + script + ")");
274 | } else {
275 | webView.loadUrl("javascript:" + script);
276 | }
277 | }
278 | }
279 |
280 | @Override
281 | public void onPageFinished(WebView view, String url) {
282 | try {
283 | InputStream is = webView.getContext().getAssets()
284 | .open("WebViewJavascriptBridge.js.txt");
285 | int size = is.available();
286 | byte[] buffer = new byte[size];
287 | is.read(buffer);
288 | is.close();
289 | String js = new String(buffer);
290 | executeJavascript(js);
291 | } catch (IOException e) {
292 | e.printStackTrace();
293 | }
294 |
295 | if (startupMessageQueue != null) {
296 | for (int i = 0; i < startupMessageQueue.size(); i++) {
297 | dispatchMessage(startupMessageQueue.get(i));
298 | }
299 | startupMessageQueue = null;
300 | }
301 | super.onPageFinished(view, url);
302 | }
303 |
304 | @Override
305 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
306 | if (url.startsWith(kCustomProtocolScheme)) {
307 | if (url.indexOf(kQueueHasMessage) > 0) {
308 | flushMessageQueue();
309 | }
310 | return true;
311 | }
312 | return super.shouldOverrideUrlLoading(view, url);
313 | }
314 |
315 | private class WVJBMessage {
316 | Object data = null;
317 | String callbackId = null;
318 | String handlerName = null;
319 | String responseId = null;
320 | Object responseData = null;
321 | }
322 |
323 | private class MyJavascriptInterface {
324 | Map map = new HashMap();
325 |
326 | public void addCallback(String key, JavascriptCallback callback) {
327 | map.put(key, callback);
328 | }
329 |
330 | @JavascriptInterface
331 | public void onResultForScript(String key, String value) {
332 | Log.i(kTag, "onResultForScript: " + value);
333 | JavascriptCallback callback = map.remove(key);
334 | if (callback != null)
335 | callback.onReceiveValue(value);
336 | }
337 | }
338 |
339 | public interface JavascriptCallback {
340 | public void onReceiveValue(String value);
341 | };
342 |
343 | }
344 |
--------------------------------------------------------------------------------
/tb408s.h5:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesse01/WebViewJavascriptBridge/cc8c20764b0be2f1578e751c176ae572d390c051/tb408s.h5
--------------------------------------------------------------------------------