handler) {
46 |
47 | new CountDownTimer(11000, 1000) {
48 | int i=10;
49 | @Override
50 | public void onTick(long millisUntilFinished) {
51 | //setProgressData can be called many times util complete be called.
52 | handler.setProgressData((i--));
53 |
54 | }
55 | @Override
56 | public void onFinish() {
57 | //complete the js invocation with data; handler will be invalid when complete is called
58 | handler.complete(0);
59 |
60 | }
61 | }.start();
62 | }
63 | }
--------------------------------------------------------------------------------
/app/src/main/assets/native-call-js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | DSBridge Test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/changelist.md:
--------------------------------------------------------------------------------
1 | # DSBridge v3.0 change list
2 |
3 | DSBridge v3.0 is a milestone, Compared with v2.0.X, we have made a lot of changes. Note that V3.0 is **incompatible** with V2.0, but v2.0 will continue to maintain. If you are a new user, use >=v3.0
4 |
5 | #### In Java
6 |
7 | 1. **Deprecated**:~~setJavascriptInterface~~ , use `addJavascriptObject` instead.
8 |
9 | 2. **Deprecated**:~~setJavascriptContextInitedListener~~ ,`callHandler` can be called at any time.
10 |
11 | 3. **Deprecated**:~~DUIWebView~~ , `UIWebView` will not be supported ever.
12 |
13 | 4. **New**: `addJavascriptObject:(id) object namespace:(NSString *) namespace`
14 |
15 | 5. **New**: `removeJavascriptObject:NSString * namespace`
16 |
17 | 6. **New**: `disableJavascriptDialogBlock:(bool) disable`
18 |
19 | 7. **New**: `hasJavascriptMethod:(NSString *) handlerName methodExistCallback:(void(^ )(bool exist))callback`
20 |
21 | 8. **New**: ` setJavascriptCloseWindowListener:(void(^)(void))callback`
22 |
23 | 9. **New**: `setDebugMode:(bool) debug`
24 |
25 | 10. **New feature**: Support namespace
26 |
27 | 11. **New feature**: Can add multiple API object
28 |
29 | 12. **Changed**: Object-c API signature changed
30 |
31 | 13. **Changed**: `callHandler` can be called at any time.
32 |
33 |
34 |
35 |
36 | #### In Javascript
37 |
38 | 1. **New**: `hasNativeMethod(handlerName,[type])`
39 | 2. **New**: `disableJavascriptDialogBlock(disable)`
40 | 3. **New**: `registerAsyn(methodName|namespace,function|asyApiObject)`
41 | 4. **Changed**: `register(methodName|namespace,function|synApiObject)`
42 | 5. **New feature**: Support namespace
43 |
44 | # Why Only Support WKWebView?
45 |
46 | ### Advantages of WKWebView
47 |
48 | It is well known that **WKWebView loads web pages faster and more efficiently than UIWebView**, and also **doesn't have as much memory overhead** for you.
49 |
50 | Under the current timeline, most iOS apps only support iOS 9.0+.
51 |
52 | ### UIWebView Cross-Domain Access Vulnerability
53 |
54 | The reason for the iOS platform cross-domain access vulnerability is due to UIWebView turning on the WebKitAllowUniversalAccessFromFileURLs and WebKitAllowFileAccessFromFileURLs options.
55 |
56 | **WKWebView default allowFileAccessFromFileURLs and allowUniversalAccessFromFileURLs option is false.**
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
20 |
28 |
35 |
36 |
43 |
44 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/assets/fly.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
38 |
39 |
40 |
Fly.js supports forwarding the http request (ajax) to Native through any Javascript bridge, And fly.js has already provide the dsBridge adapter. Because the Native side has no the same-origin policy restriction, fly.js can request any resource from any domain.
41 |
42 | Get home page source code of baidu.com
43 | Show the logo of github
44 |
45 | Close window
46 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/assets/js-call-native.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | DSBridge Test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
31 | Synchronous call
32 | Asynchronous call
33 | Sync call without argument
34 | Async call without argument
35 | echo.syn
36 | echo.asyn
37 | Stress test,2K times consecutive asynchronous API calls
38 | Never call because without @JavascriptInterface annotation ( This test is
39 | just for Android ,should be ignored in IOS )
40 |
41 | call progress
42 | hasNativeMethod("xx")
43 | hasNativeMethod("testSyn")
44 |
45 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_call_javascript.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
20 |
21 |
27 |
28 |
34 |
35 |
41 |
42 |
48 |
49 |
55 |
56 |
62 |
63 |
69 |
70 |
76 |
77 |
83 |
84 |
90 |
96 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/app/src/main/java/wendu/jsbdemo/NetUtils.java:
--------------------------------------------------------------------------------
1 | package wendu.jsbdemo;
2 |
3 | import java.net.CookieManager;
4 | import org.json.JSONObject;
5 | import java.io.BufferedReader;
6 | import java.io.InputStream;
7 | import java.io.InputStreamReader;
8 | import java.io.PrintWriter;
9 | import java.net.HttpCookie;
10 | import java.net.HttpURLConnection;
11 | import java.net.URL;
12 | import java.util.HashMap;
13 | import java.util.Iterator;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 |
18 | import javax.net.ssl.HostnameVerifier;
19 | import javax.net.ssl.HttpsURLConnection;
20 | import javax.net.ssl.SSLSession;
21 |
22 | /**
23 | * Created by du on 2017/9/16.
24 | */
25 |
26 | public class NetUtils {
27 | public static CookieManager cookieManager=new CookieManager();
28 | public static Map request(String method, String url, String param, JSONObject headers) throws Exception {
29 | URL uri = new URL(url);
30 | method = method.toUpperCase();
31 | HttpURLConnection urlCon = (HttpURLConnection) uri.openConnection();
32 | urlCon.setRequestMethod(method);
33 | urlCon.setConnectTimeout(10000);
34 | handleRequestHeaders(urlCon,headers);
35 | if (urlCon instanceof HttpsURLConnection) {
36 | addCertVerifier((HttpsURLConnection) urlCon);
37 | }
38 | if (method.equals("POST")) {
39 | urlCon.setDoOutput(true);
40 | urlCon.setDoInput(true);
41 | if (!param.trim().isEmpty()) {
42 | PrintWriter pw = new PrintWriter(urlCon.getOutputStream());
43 | pw.print(param);
44 | pw.flush();
45 | pw.close();
46 | }
47 | }
48 | Map response=new HashMap<>();
49 | response.put("responseText",inputStream2String(urlCon.getInputStream()));
50 | response.put("statusCode",urlCon.getResponseCode());
51 | Map> responseHeaders= new HashMap<>(urlCon.getHeaderFields());
52 | responseHeaders.remove(null);
53 | responseHeaders=handleResponseHeaders(urlCon,responseHeaders);
54 | response.put("headers",responseHeaders);
55 | return response;
56 |
57 | }
58 |
59 | //对于https请求,进行证书校验
60 | private static void addCertVerifier(HttpsURLConnection urlCon) throws Exception {
61 | // 在此做证书校验
62 | // urlCon.setSSLSocketFactory(getSSLSocketFactory());
63 | urlCon.setHostnameVerifier(new HostnameVerifier() {
64 | @Override
65 | public boolean verify(String hostname, SSLSession session) {
66 | //return "api.dtworkroom.com".equals(hostname);
67 | HostnameVerifier hv=HttpsURLConnection.getDefaultHostnameVerifier();
68 | return hv.verify("*.dtworkroom.com",session);
69 | }
70 | });
71 | }
72 |
73 | //预处理请求头
74 | private static void handleRequestHeaders(HttpURLConnection connection,JSONObject headers) throws Exception {
75 |
76 | Iterator iterator = headers.keys();
77 | while(iterator.hasNext()){
78 | String key = (String) iterator.next();
79 | String value = headers.getString(key);
80 | if(!key.toLowerCase().equals("cookie")){
81 | //请求cookie
82 | connection.setRequestProperty(key, value);
83 | }
84 | }
85 | List cookies= cookieManager.getCookieStore().get(connection.getURL().toURI());
86 | cookies.toString();
87 | }
88 |
89 | private static Map> handleResponseHeaders(HttpURLConnection connection, Map> responseHeaders) throws Exception {
90 | //获取响应头中的cookies,端上统一管理cookie
91 | cookieManager.put(connection.getURL().toURI(),responseHeaders);
92 | responseHeaders.remove("set-cookie");
93 | return responseHeaders;
94 | }
95 |
96 |
97 | private static String inputStream2String(InputStream is) {
98 | String result = "";
99 | String line;
100 | InputStreamReader inputReader = new InputStreamReader(is);
101 | BufferedReader bufReader = new BufferedReader(inputReader);
102 | try {
103 | while ((line = bufReader.readLine()) != null)
104 | result += line + "\r\n";
105 | bufReader.close();
106 | } catch (Exception e) {
107 | e.printStackTrace();
108 | }
109 | return result;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/dsbridge/npm/index.js:
--------------------------------------------------------------------------------
1 | var bridge = {
2 | default:this,// for typescript
3 | call: function (method, args, cb) {
4 | var ret = '';
5 | if (typeof args == 'function') {
6 | cb = args;
7 | args = {};
8 | }
9 | var arg={data:args===undefined?null:args}
10 | if (typeof cb == 'function') {
11 | var cbName = 'dscb' + window.dscb++;
12 | window[cbName] = cb;
13 | arg['_dscbstub'] = cbName;
14 | }
15 | arg = JSON.stringify(arg)
16 |
17 | //if in webview that dsBridge provided, call!
18 | if(window._dsbridge){
19 | ret= _dsbridge.call(method, arg)
20 | }else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){
21 | ret = prompt("_dsbridge=" + method, arg);
22 | }
23 |
24 | return JSON.parse(ret||'{}').data
25 | },
26 | register: function (name, fun, asyn) {
27 | var q = asyn ? window._dsaf : window._dsf
28 | if (!window._dsInit) {
29 | window._dsInit = true;
30 | //notify native that js apis register successfully on next event loop
31 | setTimeout(function () {
32 | bridge.call("_dsb.dsinit");
33 | }, 0)
34 | }
35 | if (typeof fun == "object") {
36 | q._obs[name] = fun;
37 | } else {
38 | q[name] = fun
39 | }
40 | },
41 | registerAsyn: function (name, fun) {
42 | this.register(name, fun, true);
43 | },
44 | hasNativeMethod: function (name, type) {
45 | return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
46 | },
47 | disableJavascriptDialogBlock: function (disable) {
48 | this.call("_dsb.disableJavascriptDialogBlock", {
49 | disable: disable !== false
50 | })
51 | }
52 | };
53 |
54 | !function () {
55 | if (window._dsf) return;
56 | var ob = {
57 | _dsf: {
58 | _obs: {}
59 | },
60 | _dsaf: {
61 | _obs: {}
62 | },
63 | dscb: 0,
64 | dsBridge: bridge,
65 | close: function () {
66 | bridge.call("_dsb.closePage")
67 | },
68 | _handleMessageFromNative: function (info) {
69 | var arg = JSON.parse(info.data);
70 | var ret = {
71 | id: info.callbackId,
72 | complete: true
73 | }
74 | var f = this._dsf[info.method];
75 | var af = this._dsaf[info.method]
76 | var callSyn = function (f, ob) {
77 | ret.data = f.apply(ob, arg)
78 | bridge.call("_dsb.returnValue", ret)
79 | }
80 | var callAsyn = function (f, ob) {
81 | arg.push(function (data, complete) {
82 | ret.data = data;
83 | ret.complete = complete!==false;
84 | bridge.call("_dsb.returnValue", ret)
85 | })
86 | f.apply(ob, arg)
87 | }
88 | if (f) {
89 | callSyn(f, this._dsf);
90 | } else if (af) {
91 | callAsyn(af, this._dsaf);
92 | } else {
93 | //with namespace
94 | var name = info.method.split('.');
95 | if (name.length<2) return;
96 | var method=name.pop();
97 | var namespace=name.join('.')
98 | var obs = this._dsf._obs;
99 | var ob = obs[namespace] || {};
100 | var m = ob[method];
101 | if (m && typeof m == "function") {
102 | callSyn(m, ob);
103 | return;
104 | }
105 | obs = this._dsaf._obs;
106 | ob = obs[namespace] || {};
107 | m = ob[method];
108 | if (m && typeof m == "function") {
109 | callAsyn(m, ob);
110 | return;
111 | }
112 | }
113 | }
114 | }
115 | for (var attr in ob) {
116 | window[attr] = ob[attr]
117 | }
118 | bridge.register("_hasJavascriptMethod", function (method, tag) {
119 | var name = method.split('.')
120 | if(name.length<2) {
121 | return !!(_dsf[name]||_dsaf[name])
122 | }else{
123 | // with namespace
124 | var method=name.pop()
125 | var namespace=name.join('.')
126 | var ob=_dsf._obs[namespace]||_dsaf._obs[namespace]
127 | return ob&&!!ob[method]
128 | }
129 | })
130 | }();
131 |
132 | module.exports = bridge;
--------------------------------------------------------------------------------
/app/src/main/java/wendu/jsbdemo/AjaxHandler.java:
--------------------------------------------------------------------------------
1 | package wendu.jsbdemo;
2 |
3 | import android.util.Base64;
4 |
5 | import org.json.JSONObject;
6 |
7 | import java.io.IOException;
8 | import java.util.HashMap;
9 | import java.util.Iterator;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.concurrent.TimeUnit;
13 |
14 | import okhttp3.Call;
15 | import okhttp3.Callback;
16 | import okhttp3.MediaType;
17 | import okhttp3.OkHttpClient;
18 | import okhttp3.Request;
19 | import okhttp3.RequestBody;
20 | import okhttp3.Response;
21 | import wendu.dsbridge.CompletionHandler;
22 |
23 | /**
24 | * Created by du on 2017/10/31.
25 | *
26 | * This class handles the Ajax requests forwarded by fly.js in DWebView
27 | * More about fly.js see https://github.com/wendux/fly
28 | */
29 |
30 | public class AjaxHandler {
31 | public static void onAjaxRequest(final JSONObject requestData, final CompletionHandler handler){
32 |
33 | // Define response structure
34 | final Map responseData=new HashMap<>();
35 | responseData.put("statusCode",0);
36 |
37 | try {
38 | int timeout =requestData.getInt("timeout");
39 | // Create a okhttp instance and set timeout
40 | final OkHttpClient okHttpClient = new OkHttpClient
41 | .Builder()
42 | .connectTimeout(timeout, TimeUnit.MILLISECONDS)
43 | .build();
44 |
45 | // Determine whether you need to encode the response result.
46 | // And encode when responseType is stream.
47 | String contentType="";
48 | boolean encode=false;
49 | String responseType=requestData.optString("responseType",null);
50 | if(responseType!=null&&responseType.equals("stream")){
51 | encode=true;
52 | }
53 |
54 | Request.Builder rb= new Request.Builder();
55 | rb.url(requestData.getString("url"));
56 | JSONObject headers=requestData.getJSONObject("headers");
57 |
58 | // Set request headers
59 | Iterator iterator = headers.keys();
60 | while(iterator.hasNext()){
61 | String key = (String) iterator.next();
62 | String value = headers.getString(key);
63 | String lKey=key.toLowerCase();
64 | if(lKey.equals("cookie")){
65 | // Here you can use CookieJar to manage cookie in a unified way with you native code.
66 | continue;
67 | }
68 | if(lKey.toLowerCase().equals("content-type")){
69 | contentType=value;
70 | }
71 | rb.header(key,value);
72 | }
73 |
74 | // Create request body
75 | if(requestData.getString("method").equals("POST")){
76 | RequestBody requestBody=RequestBody
77 | .create(MediaType.parse(contentType),requestData.getString("data"));
78 | rb.post(requestBody) ;
79 | }
80 | // Create and send HTTP requests
81 | Call call=okHttpClient.newCall(rb.build());
82 | final boolean finalEncode = encode;
83 | call.enqueue(new Callback() {
84 | @Override
85 | public void onFailure(Call call, IOException e) {
86 | responseData.put("responseText",e.getMessage());
87 | handler.complete(new JSONObject(responseData).toString());
88 | }
89 |
90 | @Override
91 | public void onResponse(Call call, Response response) throws IOException {
92 | String data;
93 | // If encoding is needed, the result is encoded by Base64 and returned
94 | if(finalEncode){
95 | data= Base64.encodeToString(response.body().bytes(),Base64.DEFAULT);
96 | }else{
97 | data=response.body().string();
98 | }
99 | responseData.put("responseText",data);
100 | responseData.put("statusCode",response.code());
101 | responseData.put("statusMessage",response.message());
102 | Map> responseHeaders= response.headers().toMultimap();
103 | responseHeaders.remove(null);
104 | responseData.put("headers",responseHeaders);
105 | handler.complete(new JSONObject(responseData).toString());
106 | }
107 | });
108 |
109 | }catch (Exception e){
110 | responseData.put("responseText",e.getMessage());
111 | handler.complete(new JSONObject(responseData).toString());
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/assets/dsbridge.js:
--------------------------------------------------------------------------------
1 | var bridge = {
2 | default:this,// for typescript
3 | call: function (method, args, cb) {
4 | var ret = '';
5 | if (typeof args == 'function') {
6 | cb = args;
7 | args = {};
8 | }
9 | var arg={data:args===undefined?null:args}
10 | if (typeof cb == 'function') {
11 | var cbName = 'dscb' + window.dscb++;
12 | window[cbName] = cb;
13 | arg['_dscbstub'] = cbName;
14 | }
15 | arg = JSON.stringify(arg)
16 |
17 | //if in webview that dsBridge provided, call!
18 | if(window._dsbridge){
19 | ret= _dsbridge.call(method, arg)
20 | }else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){
21 | ret = prompt("_dsbridge=" + method, arg);
22 | }
23 |
24 | return JSON.parse(ret||'{}').data
25 | },
26 | register: function (name, fun, asyn) {
27 | var q = asyn ? window._dsaf : window._dsf
28 | if (!window._dsInit) {
29 | window._dsInit = true;
30 | //notify native that js apis register successfully on next event loop
31 | setTimeout(function () {
32 | bridge.call("_dsb.dsinit");
33 | }, 0)
34 | }
35 | if (typeof fun == "object") {
36 | q._obs[name] = fun;
37 | } else {
38 | q[name] = fun
39 | }
40 | },
41 | registerAsyn: function (name, fun) {
42 | this.register(name, fun, true);
43 | },
44 | hasNativeMethod: function (name, type) {
45 | return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
46 | },
47 | disableJavascriptDialogBlock: function (disable) {
48 | this.call("_dsb.disableJavascriptDialogBlock", {
49 | disable: disable !== false
50 | })
51 | }
52 | };
53 |
54 | !function () {
55 | if (window._dsf) return;
56 | var _close=window.close;
57 | var ob = {
58 | //保存JS同步方法
59 | _dsf: {
60 | _obs: {}
61 | },
62 | //保存JS异步方法
63 | _dsaf: {
64 | _obs: {}
65 | },
66 | dscb: 0,
67 | dsBridge: bridge,
68 | close: function () {
69 | if(bridge.hasNativeMethod('_dsb.closePage')){
70 | bridge.call("_dsb.closePage")
71 | }else{
72 | _close.call(window)
73 | }
74 | },
75 | _handleMessageFromNative: function (info) {
76 | var arg = JSON.parse(info.data);
77 | var ret = {
78 | id: info.callbackId,
79 | complete: true
80 | }
81 | var f = this._dsf[info.method];
82 | var af = this._dsaf[info.method]
83 | var callSyn = function (f, ob) {
84 | ret.data = f.apply(ob, arg)
85 | bridge.call("_dsb.returnValue", ret)
86 | }
87 | var callAsyn = function (f, ob) {
88 | arg.push(function (data, complete) {
89 | ret.data = data;
90 | ret.complete = complete!==false;
91 | bridge.call("_dsb.returnValue", ret)
92 | })
93 | f.apply(ob, arg)
94 | }
95 | if (f) {
96 | callSyn(f, this._dsf);
97 | } else if (af) {
98 | callAsyn(af, this._dsaf);
99 | } else {
100 | //with namespace
101 | var name = info.method.split('.');
102 | if (name.length<2) return;
103 | var method=name.pop();
104 | var namespace=name.join('.')
105 | var obs = this._dsf._obs;
106 | var ob = obs[namespace] || {};
107 | var m = ob[method];
108 | if (m && typeof m == "function") {
109 | callSyn(m, ob);
110 | return;
111 | }
112 | obs = this._dsaf._obs;
113 | ob = obs[namespace] || {};
114 | m = ob[method];
115 | if (m && typeof m == "function") {
116 | callAsyn(m, ob);
117 | return;
118 | }
119 | }
120 | }
121 | }
122 | for (var attr in ob) {
123 | window[attr] = ob[attr]
124 | }
125 | bridge.register("_hasJavascriptMethod", function (method, tag) {
126 | var name = method.split('.')
127 | if(name.length<2) {
128 | return !!(_dsf[name]||_dsaf[name])
129 | }else{
130 | // with namespace
131 | var method=name.pop()
132 | var namespace=name.join('.')
133 | var ob=_dsf._obs[namespace]||_dsaf._obs[namespace]
134 | return ob&&!!ob[method]
135 | }
136 | })
137 | }();
138 |
139 | module.exports = bridge;
--------------------------------------------------------------------------------
/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 d 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/java/wendu/jsbdemo/CallJavascriptActivity.java:
--------------------------------------------------------------------------------
1 | package wendu.jsbdemo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 | import android.widget.Toast;
7 |
8 | import org.json.JSONObject;
9 |
10 | import wendu.dsbridge.DWebView;
11 | import wendu.dsbridge.OnReturnValue;
12 |
13 | public class CallJavascriptActivity extends AppCompatActivity implements View.OnClickListener {
14 |
15 | DWebView dWebView;
16 |
17 | public T getView(int viewId) {
18 | View view = findViewById(viewId);
19 | return (T) view;
20 | }
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_call_javascript);
26 | getView(R.id.addValue).setOnClickListener(this);
27 | getView(R.id.append).setOnClickListener(this);
28 | getView(R.id.startTimer).setOnClickListener(this);
29 | getView(R.id.synAddValue).setOnClickListener(this);
30 | getView(R.id.synGetInfo).setOnClickListener(this);
31 | getView(R.id.asynAddValue).setOnClickListener(this);
32 | getView(R.id.asynGetInfo).setOnClickListener(this);
33 | getView(R.id.hasMethodAddValue).setOnClickListener(this);
34 | getView(R.id.hasMethodXX).setOnClickListener(this);
35 | getView(R.id.hasMethodAsynAddValue).setOnClickListener(this);
36 | getView(R.id.hasMethodAsynXX).setOnClickListener(this);
37 | DWebView.setWebContentsDebuggingEnabled(true);
38 | dWebView= getView(R.id.webview);
39 | dWebView.loadUrl("file:///android_asset/native-call-js.html");
40 |
41 |
42 |
43 | }
44 |
45 |
46 | void showToast(Object o) {
47 | Toast.makeText(this, o.toString(), Toast.LENGTH_SHORT).show();
48 | }
49 |
50 | @Override
51 | public void onClick(View v) {
52 | switch (v.getId()) {
53 | case R.id.addValue:
54 | dWebView.callHandler("addValue", new Object[]{3, 4}, new OnReturnValue() {
55 | @Override
56 | public void onValue(Integer retValue) {
57 | showToast(retValue);
58 | }
59 | });
60 | break;
61 | case R.id.append:
62 | dWebView.callHandler("append", new Object[]{"I", "love", "you"}, new OnReturnValue() {
63 | @Override
64 | public void onValue(String retValue) {
65 | showToast(retValue);
66 | }
67 | });
68 | break;
69 | case R.id.startTimer:
70 | dWebView.callHandler("startTimer", new OnReturnValue() {
71 | @Override
72 | public void onValue(Integer retValue) {
73 | showToast(retValue);
74 | }
75 | });
76 | break;
77 | case R.id.synAddValue:
78 | dWebView.callHandler("syn.addValue", new Object[]{5, 6}, new OnReturnValue() {
79 | @Override
80 | public void onValue(Integer retValue) {
81 | showToast(retValue);
82 | }
83 | });
84 | break;
85 | case R.id.synGetInfo:
86 | dWebView.callHandler("syn.getInfo", new OnReturnValue() {
87 | @Override
88 | public void onValue(JSONObject retValue) {
89 | showToast(retValue);
90 | }
91 | });
92 | break;
93 | case R.id.asynAddValue:
94 | dWebView.callHandler("asyn.addValue", new Object[]{5, 6}, new OnReturnValue() {
95 | @Override
96 | public void onValue(Integer retValue) {
97 | showToast(retValue);
98 | }
99 | });
100 | break;
101 | case R.id.asynGetInfo:
102 | dWebView.callHandler("asyn.getInfo", new OnReturnValue() {
103 | @Override
104 | public void onValue(JSONObject retValue) {
105 | showToast(retValue);
106 | }
107 | });
108 | break;
109 | case R.id.hasMethodAddValue:
110 | dWebView.hasJavascriptMethod("addValue", new OnReturnValue() {
111 | @Override
112 | public void onValue(Boolean retValue) {
113 | showToast(retValue);
114 | }
115 | });
116 | break;
117 | case R.id.hasMethodXX:
118 | dWebView.hasJavascriptMethod("XX", new OnReturnValue() {
119 | @Override
120 | public void onValue(Boolean retValue) {
121 | showToast(retValue);
122 | }
123 | });
124 | break;
125 | case R.id.hasMethodAsynAddValue:
126 | dWebView.hasJavascriptMethod("asyn.addValue", new OnReturnValue() {
127 | @Override
128 | public void onValue(Boolean retValue) {
129 | showToast(retValue);
130 | }
131 | });
132 | break;
133 | case R.id.hasMethodAsynXX:
134 | dWebView.hasJavascriptMethod("asyn.XX", new OnReturnValue() {
135 | @Override
136 | public void onValue(Boolean retValue) {
137 | showToast(retValue);
138 | }
139 | });
140 | break;
141 | }
142 |
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/readme-chs.md:
--------------------------------------------------------------------------------
1 | # DSBridge for Android
2 |
3 | 
4 |
5 | [](https://jitpack.io/#wendux/DSBridge-Android)
6 | 
7 | [](https://opensource.org/licenses/mit-license.php)
8 | [](https://travis-ci.org/wendux/DSBridge-Android)
9 | [](https://github.com/wendux/DSBridge-Android/tree/master)
10 | 
11 | [](https://github.com/wendux/DSBridge-Android/tree/x5-3.0)
12 |
13 | > 三端易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数.
14 |
15 |
16 | ### 注意
17 |
18 | DSBridge v3.0 是一个里程碑版本,和v2.0相比,有许多变化,需要注意的是v3.0**不兼容**之前版本,但是我们也会继续维护v2.0分支,所以,如果你是v2.0的使用者,请放心继续使用v2.0,如果你是新用户,请使用>=v3.0.
19 |
20 | [DSBridge v3.0.0 更新列表](https://github.com/wendux/DSBridge-Android/issues/31)
21 | 腾讯X5内核支持:https://github.com/wendux/DSBridge-Android/tree/x5-3.0
22 |
23 |
24 | ## 特性
25 |
26 | 1. Android、IOS、Javascript 三端易用,轻量且强大、安全且健壮。
27 |
28 | 2. 同时支持同步调用和异步调用
29 |
30 | 3. 支持以类的方式集中统一管理API
31 |
32 | 4. 支持API命名空间
33 |
34 | 5. 支持调试模式
35 |
36 | 6. 支持API存在性检测
37 |
38 | 7. 支持进度回调:一次调用,多次返回
39 |
40 | 8. 支持Javascript关闭页面事件回调
41 |
42 | 9. 支持Javascript 模态/非模态对话框
43 |
44 | 10. 支持腾讯X5内核
45 |
46 |
47 |
48 | ## 安装
49 |
50 | 1. 添加 JitPack repository 到gradle脚本中
51 |
52 | ```groovy
53 | allprojects {
54 | repositories {
55 | ...
56 | maven { url 'https://jitpack.io' }
57 | }
58 | }
59 | ```
60 |
61 | 2. 添加依赖
62 |
63 | ```groovy
64 | dependencies {
65 | //compile 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'
66 | //support the x5 browser core of tencent
67 | //compile 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
68 | }
69 | ```
70 |
71 | ## 示例
72 |
73 | 请参考工程目录下的 `wendu.jsbdemo/` 包。运行 `app` 工程并查看示例交互。
74 |
75 | 如果要在你自己的项目中使用 dsBridge :
76 |
77 | ## 使用
78 |
79 | 1. 新建一个Java类,实现API
80 |
81 | ```java
82 | public class JsApi{
83 | //同步API
84 | @JavascriptInterface
85 | public String testSyn(Object msg) {
86 | return msg + "[syn call]";
87 | }
88 |
89 | //异步API
90 | @JavascriptInterface
91 | public void testAsyn(Object msg, CompletionHandler handler) {
92 | handler.complete(msg+" [ asyn call]");
93 | }
94 | }
95 | ```
96 |
97 | 可以看到,DSBridge正式通过类的方式集中、统一地管理API。由于安全原因,所有Java API 必须有"@JavascriptInterface" 标注。
98 |
99 | 2. 添加API类实例到 DWebView .
100 |
101 | ```javascript
102 | import wendu.dsbridge.DWebView
103 | ...
104 | DWebView dwebView= (DWebView) findViewById(R.id.dwebview);
105 | dwebView.addJavascriptObject(new JsApi(), null);
106 | ```
107 |
108 | 3. 在Javascript中调用原生 (Java/Object-c/swift) API ,并注册一个 javascript API供原生调用.
109 |
110 | - 初始化 dsBridge
111 |
112 | ```javascript
113 | //cdn方式引入初始化代码(中国地区慢,建议下载到本地工程)
114 | //
115 | //npm方式安装初始化代码
116 | //npm install dsbridge@3.1.3
117 | var dsBridge=require("dsbridge")
118 | ```
119 |
120 | - 调用原生API ,并注册一个 javascript API供原生调用.
121 |
122 | ```javascript
123 |
124 | //同步调用
125 | var str=dsBridge.call("testSyn","testSyn");
126 |
127 | //异步调用
128 | dsBridge.call("testAsyn","testAsyn", function (v) {
129 | alert(v);
130 | })
131 |
132 | //注册 javascript API
133 | dsBridge.register('addValue',function(l,r){
134 | return l+r;
135 | })
136 | ```
137 |
138 | 4. 在Java中调用 Javascript API
139 |
140 | ```java
141 | dwebView.callHandler("addValue",new Object[]{3,4},new OnReturnValue(){
142 | @Override
143 | public void onValue(Integer retValue) {
144 | Log.d("jsbridge","call succeed,return value is "+retValue);
145 | }
146 | });
147 | ```
148 |
149 |
150 |
151 |
152 | ## Java API 签名
153 |
154 | 为了兼容IOS,我们约定 Java API 签名,**注意,如果API签名不合法,则不会被调用**!签名如下:
155 |
156 | 1. 同步API.
157 |
158 | **` public any handler(Object msg) `**
159 |
160 | 参数必须是 `Object` 类型,**并且必须申明**(如果不需要参数,申明后不适用即可)。返回值类型没有限制,可以是任意类型。
161 |
162 | 2. 异步 API.
163 |
164 | **`public void handler(Object arg, CompletionHandler handler)`**
165 |
166 |
167 |
168 | ## 命名空间
169 |
170 | 命名空间可以帮助你更好的管理API,这在API数量多的时候非常实用,比如在混合应用中。DSBridge (>= v3.0.0) 支持你通过命名空间将API分类管理,并且命名空间支持多级的,不同级之间只需用'.' 分隔即可。
171 |
172 |
173 |
174 | ## 调试模式
175 |
176 | 在调试模式时,发生一些错误时,将会以弹窗形式提示,并且原生API如果触发异常将不会被自动捕获,因为在调试阶段应该将问题暴露出来。如果调试模式关闭,错误将不会弹窗,并且会自动捕获API触发的异常,防止crash。强烈建议在开发阶段开启调试模式,可以通过如下代码开启调试模式
177 |
178 | ```java
179 | DWebView.setWebContentsDebuggingEnabled(true)
180 | ```
181 |
182 |
183 |
184 | ## 进度回调
185 |
186 | 通常情况下,调用一个方法结束后会返回一个结果,是一一对应的。但是有时会遇到一次调用需要多次返回的场景,比如在javascript钟调用端上的一个下载文件功能,端上在下载过程中会多次通知javascript进度, 然后javascript将进度信息展示在h5页面上,这是一个典型的一次调用,多次返回的场景,如果使用其它Javascript bridge, 你将会发现要实现这个功能会比较麻烦,而DSBridge本省支持进度回调,你可以非常简单方便的实现一次调用需要多次返回的场景,下面我们实现一个倒计时的例子:
187 |
188 | In Java
189 |
190 | ```java
191 | @JavascriptInterface
192 | public void callProgress(Object args, final CompletionHandler handler) {
193 | new CountDownTimer(11000, 1000) {
194 | int i=10;
195 | @Override
196 | public void onTick(long millisUntilFinished) {
197 | //setProgressData can be called many times util complete be called.
198 | handler.setProgressData((i--));
199 | }
200 | @Override
201 | public void onFinish() {
202 | //complete the js invocation with data;
203 | //handler will invalid when complete is called
204 | handler.complete(0);
205 | }
206 | }.start();
207 | }
208 | ```
209 |
210 | In Javascript
211 |
212 | ```javascript
213 | dsBridge.call("callProgress", function (value) {
214 | document.getElementById("progress").innerText = value
215 | })
216 | ```
217 |
218 | 完整的示例代码请参考demo工程。
219 |
220 |
221 |
222 | ## Javascript 弹出框
223 |
224 | DSBridge已经实现了 Javascript的弹出框函数(alert/confirm/prompt),如果你想自定义它们,通过`WebChromeClient`重写相关函数即可。DSBridge实现的对话框默认设置是模态的,这会挂起UI线程,如果你需要非模态对话框,请参考`dwebview.disableJavascriptDialogBlock(bool disable)` 。
225 |
226 |
227 |
228 | ## 安全
229 |
230 | 在Android 4.2(API17)之前 `webview.addJavascriptInterface` 存在安全漏洞,DSBridge内部在4.2以下的设备上不会使用` webview.addJavascriptInterface`,而是通过其它方式通信,在4.2之后会使用 `webview.addJavascriptInterface` 。同时,为了防止Javascript调用未授权的原生函数,所有Java API 必须有"@JavascriptInterface" 标注,所以在任何版本的Android系统下,您可以放心使用DSBridge!
231 |
232 |
233 |
234 | ## DWebView
235 |
236 | DWebView中,如果在非主线程调用下列方法时,它们内部会自动分发到主线程中执行,你再也无需手动切换。
237 |
238 | ```java
239 | void loadUrl( String url)
240 | void loadUrl( String url, Map additionalHttpHeaders)
241 | void evaluateJavascript(String script)
242 | ```
243 |
244 |
245 |
246 | ## API 列表
247 |
248 | ### Java API
249 |
250 | 在Java中我们把实现了供 javascript调用的 API类的实例 成为 **Java API object**.
251 |
252 | ##### `dwebview.addJavascriptObject(Object object, String namespace)`
253 |
254 | 添加一个Java API object到DWebView ,并为它指定一个命名空间。然后,在 javascript 中就可以通过`bridge.call("namespace.api",...)`来调用Java API object中的原生API了。
255 |
256 | 如果命名空间是空(null或空字符串), 那么这个添加的 Java API object就没有命名空间。在 javascript 通过 `bridge.call("api",...)`调用。
257 |
258 | **示例**:
259 |
260 | In Java
261 |
262 | ```javascript
263 | public class JsEchoApi {
264 | @JavascriptInterface
265 | public Object syn(Object args) throws JSONException {
266 | return args;
267 | }
268 |
269 | @JavascriptInterface
270 | public void asyn(Object args,CompletionHandler handler){
271 | handler.complete(args);
272 | }
273 | }
274 | //namespace is "echo"
275 | dwebView.addJavascriptObject(new JsEchoApi(),"echo");
276 | ```
277 |
278 | In Javascript
279 |
280 | ```javascript
281 | // call echo.syn
282 | var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1})
283 | alert(JSON.stringify(ret))
284 | // call echo.asyn
285 | dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},function (ret) {
286 | alert(JSON.stringify(ret));
287 | })
288 | ```
289 |
290 |
291 |
292 | ##### `dwebview.removeJavascriptObject(String namespace)`
293 |
294 | 通过命名空间名称移除相应的Java API object 。
295 |
296 |
297 |
298 | ##### `dwebview.callHandler(String handlerName, Object[] args)`
299 |
300 | ##### `dwebview.callHandler(String handlerName, OnReturnValue handler)`
301 |
302 | ##### `dwebview.callHandler(String handlerName, Object[] args,OnReturnValue handler)`
303 |
304 | 调用 javascript API。`handlerName` 为javascript API 的名称,可以包含命名空间;参数以数组传递,`args`数组中的元素依次对应javascript API的形参; `handler` 用于接收javascript API的返回值,**注意:handler将在主线程中被执行**。
305 |
306 | 示例:
307 |
308 | ```java
309 |
310 | dWebView.callHandler("append",new Object[]{"I","love","you"},new OnReturnValue((){
311 | @Override
312 | public void onValue(String retValue) {
313 | Log.d("jsbridge","call succeed, append string is: "+retValue);
314 | }
315 | });
316 | // call with namespace 'syn', More details to see the Demo project
317 | dWebView.callHandler("syn.getInfo", new OnReturnValue() {
318 | @Override
319 | public void onValue(JSONObject retValue) {
320 | showToast(retValue);
321 | }
322 | });
323 | ```
324 |
325 |
326 |
327 | ##### `dwebview.disableJavascriptDialogBlock(bool disable)`
328 |
329 | **小心使用**. 如果你在javascript中调用弹窗函数(`alert`,` confirm`, 或 `prompt`), 那么APP将会挂起,因为这些弹窗都是**模态**的,会阻塞APP主线程,此时javascript执行流也会阻塞。如果你想避免阻塞,可以通过此API禁止,禁止后,一旦 javascript中调用了这些弹窗函数,APP将弹出**非模态**对话框,并立即返回,( `confirm` 会返回 `true`, `prompt` 返回空字符串)。
330 |
331 | 禁止Javascript对话框阻塞:
332 |
333 | ```javascript
334 | dwebview.disableJavascriptDialogBlock(true);
335 | ```
336 |
337 | 如果你想恢复**模态**对话框,传 `false` 调用即可.
338 |
339 |
340 |
341 | ##### `dwebview.setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener)`
342 |
343 | 当 Javascript中调用`window.close`时,DWebView会触发此监听器,如果未设置,DWebView默认会关闭掉当前Activity.
344 |
345 | Example:
346 |
347 | ```java
348 | dwebview.setJavascriptCloseWindowListener(new DWebView.JavascriptCloseWindowListener() {
349 | @Override
350 | public boolean onClose() {
351 | Log.d("jsbridge","window.close is called in Javascript");
352 | //如果返回false,则阻止DWebView默认处理.
353 | return false;
354 | }
355 | });
356 | ```
357 |
358 |
359 |
360 | ##### `dwebview.hasJavascriptMethod(String handlerName, OnReturnValue existCallback)`
361 |
362 | 检测是否存在指定的 javascript API,`handlerName`可以包含命名空间.
363 |
364 | 示例:
365 |
366 | ```java
367 | dWebView.hasJavascriptMethod("addValue", new OnReturnValue() {
368 | @Override
369 | public void onValue(Boolean retValue) {
370 | showToast(retValue);
371 | }
372 | });
373 | ```
374 |
375 |
376 |
377 | ##### `DWebView.setWebContentsDebuggingEnabled(boolean enabled)`
378 |
379 | 设置调试模式。在调试模式时,发生一些错误时,将会以弹窗形式提示,并且原生API如果触发异常将不会被自动捕获,因为在调试阶段应该将问题暴露出来。如果调试模式关闭,错误将不会弹窗,并且会自动捕获API触发的异常,防止crash。强烈建议在开发阶段开启调试模式。
380 |
381 |
382 |
383 | ### Javascript API
384 |
385 | ##### dsBridge
386 |
387 | "dsBridge" 在初始化之后可用 .
388 |
389 | ##### `dsBridge.call(method,[arg,callback])`
390 |
391 | 同步或异步的调用Java API。
392 |
393 | `method`: Java API 名称, 可以包含命名空间。
394 |
395 | `arg`:传递给Java API 的参数。只能传一个,如果需要多个参数时,可以合并成一个json对象参数。
396 |
397 | `callback(String returnValue)`: 处理Java API的返回结果. 可选参数,**只有异步调用时才需要提供**.
398 |
399 |
400 |
401 | ##### `dsBridge.register(methodName|namespace,function|synApiObject)`
402 |
403 | ##### `dsBridge.registerAsyn(methodName|namespace,function|asynApiObject)`
404 |
405 | 注册同步/异步的Javascript API. 这两个方法都有两种调用形式:
406 |
407 | 1. 注册一个普通的方法,如:
408 |
409 | In Javascript
410 |
411 | ```javascript
412 | dsBridge.register('addValue',function(l,r){
413 | return l+r;
414 | })
415 | dsBridge.registerAsyn('append',function(arg1,arg2,arg3,responseCallback){
416 | responseCallback(arg1+" "+arg2+" "+arg3);
417 | })
418 | ```
419 |
420 | In Java
421 |
422 | ```java
423 | webView.callHandler("addValue",new Object[]{1,6},new OnReturnValue(){
424 | @Override
425 | public void onValue(String retValue) {
426 | Log.d("jsbridge","call succeed,return value is: "+retValue);
427 | }
428 | });
429 |
430 | webView.callHandler("append",new Object[]{"I","love","you"},new OnReturnValue(){
431 | @Override
432 | public void onValue(String retValue) {
433 | Log.d("jsbridge","call succeed, append string is: "+retValue);
434 | }
435 | });
436 | ```
437 |
438 |
439 |
440 | 2. 注册一个对象,指定一个命名空间:
441 |
442 | **In Javascript**
443 |
444 | ```javascript
445 | //namespace test for synchronous calls
446 | dsBridge.register("test",{
447 | tag:"test",
448 | test1:function(){
449 | return this.tag+"1"
450 | },
451 | test2:function(){
452 | return this.tag+"2"
453 | }
454 | })
455 |
456 | //namespace test1 for asynchronous calls
457 | dsBridge.registerAsyn("test1",{
458 | tag:"test1",
459 | test1:function(responseCallback){
460 | return responseCallback(this.tag+"1")
461 | },
462 | test2:function(responseCallback){
463 | return responseCallback(this.tag+"2")
464 | }
465 | })
466 | ```
467 |
468 | > 因为Javascript并不支持函数重载,所以不能在同一个Javascript对象中定义同名的同步函数和异步函数
469 |
470 | **In Java**
471 |
472 | ```java
473 | webView.callHandler("test.test1",new OnReturnValue(){
474 | @Override
475 | public void onValue(String retValue) {
476 | Log.d("jsbridge","Namespace test.test1: "+retValue);
477 | }
478 | });
479 |
480 | webView.callHandler("test1.test1",new OnReturnValue(){
481 | @Override
482 | public void onValue(String retValue) {
483 | Log.d("jsbridge","Namespace test.test1: "+retValue);
484 | }
485 | });
486 | ```
487 |
488 |
489 |
490 |
491 | ##### `dsBridge.hasNativeMethod(handlerName,[type])`
492 |
493 | 检测Java中是否存在名为`handlerName`的API, `handlerName` 可以包含命名空间.
494 |
495 | `type`: 可选参数,`["all"|"syn"|"asyn" ]`, 默认是 "all".
496 |
497 | ```javascript
498 | //检测是否存在一个名为'testAsyn'的API(无论同步还是异步)
499 | dsBridge.hasNativeMethod('testAsyn')
500 | //检测test命名空间下是否存在一个’testAsyn’的API
501 | dsBridge.hasNativeMethod('test.testAsyn')
502 | // 检测是否存在一个名为"testSyn"的异步API
503 | dsBridge.hasNativeMethod('testSyn','asyn') //false
504 | ```
505 |
506 |
507 |
508 | ##### `dsBridge.disableJavascriptDialogBlock(disable)`
509 |
510 | 调用 `dsBridge.disableJavascriptDialogBlock(...)` 和在Java中调用 `dwebview.disableJavascriptDialogBlock(...)` 作用一样.
511 |
512 | 示例:
513 |
514 | ```javascript
515 | //disable
516 | dsBridge.disableJavascriptDialogBlock()
517 | //enable
518 | dsBridge.disableJavascriptDialogBlock(false)
519 | ```
520 |
521 |
522 |
523 | ## 和 fly.js一起使用
524 |
525 | 当dsBridge遇见 [Fly.js](https://github.com/wendux/fly) 时,将会打开一个新的世界。[fly.js传送门](https://github.com/wendux/fly)
526 |
527 | 正如我们所知,在浏览器中,ajax请求受同源策略限制,不能跨域请求资源。然而, [Fly.js](https://github.com/wendux/fly) 有一个强大的功能就是支持请求重定向:将ajax请求通过任何Javascript bridge重定向到端上,并且 [Fly.js](https://github.com/wendux/fly) 官方已经提供的 dsBridge 的 adapter, 可以非常方便的协同dsBridge一起使用。由于端上没有同源策略的限制,所以 fly.js可以请求任何域的资源。
528 |
529 | 另一个典型的使用场景是在混合APP中,由于[Fly.js](https://github.com/wendux/fly) 可以将所有ajax请求转发到端上,所以,开发者就可以在端上进行统一的请求管理、证书校验、cookie管理、访问控制等。
530 |
531 | 具体的示例请查看demo.
532 |
533 | ## 最后
534 |
535 | 如果你喜欢DSBridge, 欢迎star,以便更多的人知道它, 谢谢 !
536 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 | # DSBridge for Android
3 |
4 | 
5 |
6 | [](https://jitpack.io/#wendux/DSBridge-Android)
7 | 
8 | [](https://opensource.org/licenses/mit-license.php)
9 | [](https://travis-ci.org/wendux/DSBridge-Android)
10 | [](https://github.com/wendux/DSBridge-Android/tree/master)
11 | 
12 | [](https://github.com/wendux/DSBridge-Android/tree/x5-3.0)
13 |
14 | >Modern cross-platform JavaScript bridge, through which you can invoke each other's functions synchronously or asynchronously between JavaScript and native applications.
15 |
16 | Chinese documentation [中文文档](https://github.com/wendux/DSBridge-Android/blob/master/readme-chs.md)
17 | DSBridge-IOS:https://github.com/wendux/DSBridge-IOS
18 | [Tencent x5 webcore support](https://github.com/wendux/DSBridge-Android/tree/x5-3.0)
19 |
20 | ### Notice
21 |
22 | DSBridge v3.0 is a milestone version. Compared with v2.0, we have made a lot of changes. Note that v3.0 is **incompatible** with v2.0, but v2.0 will continue to maintain. If you are a new user, use >=v3.0.
23 |
24 | [DSBridge v3.0.0 change list](https://github.com/wendux/DSBridge-Android/issues/31)
25 |
26 | ## Features
27 |
28 | 1. The three ends of Android, IOS and Javascript are easy to use, light and powerful, secure and strong
29 | 2. Both synchronous and asynchronous calls are supported
30 | 3. Support **API Object**, which centrally implements APIs in a Java Class or a Javascript object
31 | 4. Support API namespace
32 | 5. Support debug mode
33 | 6. Support the test of whether API exists
34 | 7. Support **Progress Callback**: one call, multiple returns
35 | 8. Support event listener for Javascript to close the page
36 | 9. Support Modal and Modeless popup box for javascript
37 | 10. Support the X5 webcore of Tencent
38 |
39 | ## Installation
40 |
41 | 1. Add the JitPack repository to your build file
42 |
43 | ```groovy
44 | allprojects {
45 | repositories {
46 | ...
47 | maven { url 'https://jitpack.io' }
48 | }
49 | }
50 | ```
51 |
52 | 2. Add the dependency
53 |
54 | ```groovy
55 | dependencies {
56 | //compile 'com.github.wendux:DSBridge-Android:3.0-SNAPSHOT'
57 | //support the x5 browser core of Tencent
58 | //compile 'com.github.wendux:DSBridge-Android:x5-3.0-SNAPSHOT'
59 | }
60 | ```
61 |
62 | ## Examples
63 |
64 | See the `wendu.jsbdemo/` package. run the `app` project and to see it in action.
65 |
66 | To use dsBridge in your own project:
67 |
68 | ## Usage
69 |
70 | 1. Implement APIs in a Java class
71 |
72 | ```java
73 | public class JsApi{
74 | //for synchronous invocation
75 | @JavascriptInterface
76 | public String testSyn(Object msg) {
77 | return msg + "[syn call]";
78 | }
79 |
80 | //for asynchronous invocation
81 | @JavascriptInterface
82 | public void testAsyn(Object msg, CompletionHandler handler) {
83 | handler.complete(msg+" [ asyn call]");
84 | }
85 | }
86 | ```
87 |
88 | For security reason, Java APIs must be with "@JavascriptInterface" annotation .
89 |
90 | 2. Add API object to DWebView .
91 |
92 | ```javascript
93 | import wendu.dsbridge.DWebView
94 | ...
95 | DWebView dwebView= (DWebView) findViewById(R.id.dwebview);
96 | dwebView.addJavascriptObject(new JsApi(), null);
97 | ```
98 |
99 | 3. Call Native (Java/Object-c/swift) API in Javascript, and register javascript API.
100 |
101 | - Init dsBridge
102 |
103 | ```javascript
104 | //cdn
105 | //
106 | //npm
107 | //npm install dsbridge@3.1.3
108 | var dsBridge=require("dsbridge")
109 | ```
110 |
111 | - Call Native API and register a javascript API for Native invocation.
112 |
113 | ```javascript
114 |
115 | //Call synchronously
116 | var str=dsBridge.call("testSyn","testSyn");
117 |
118 | //Call asynchronously
119 | dsBridge.call("testAsyn","testAsyn", function (v) {
120 | alert(v);
121 | })
122 |
123 | //Register javascript API for Native
124 | dsBridge.register('addValue',function(l,r){
125 | return l+r;
126 | })
127 | ```
128 |
129 | 4. Call Javascript API in java
130 |
131 | ```java
132 | dwebView.callHandler("addValue",new Object[]{3,4},new OnReturnValue(){
133 | @Override
134 | public void onValue(Integer retValue) {
135 | Log.d("jsbridge","call succeed,return value is "+retValue);
136 | }
137 | });
138 | ```
139 |
140 |
141 |
142 | ## Java API signature
143 |
144 | In order to be compatible with IOS , we make the following convention on Java API signature:
145 |
146 | 1. For synchronous API.
147 |
148 | **` public any handler(Object msg) `**
149 |
150 | The argument type must be Object and must be declared even if not need),and the type of return value is not limited.
151 |
152 | 2. For asynchronous API.
153 |
154 | **`public void handler(Object arg, CompletionHandler handler)`**
155 |
156 | ## Namespace
157 |
158 | Namespaces can help you better manage your APIs, which is very useful in hybrid applications, because these applications have a large number of APIs. DSBridge (>= v3.0.0) allows you to classify API with namespace. And the namespace can be multilevel, between different levels with '.' division.
159 |
160 | ## Debug mode
161 |
162 | In debug mode, some errors will be prompted by a popup dialog , and the exception caused by the native APIs will not be captured to expose problems. We recommend that the debug mode be opened at the development stage. You can open debug mode :
163 |
164 | ```java
165 | DWebView.setWebContentsDebuggingEnabled(true)
166 | ```
167 |
168 |
169 |
170 | ## Progress Callback
171 |
172 | Normally, when a API is called to end, it returns a result, which corresponds one by one. But sometimes a call need to repeatedly return multipule times, Suppose that on the Native side, there is a API to download the file, in the process of downloading, it will send the progress information to Javascript many times, then Javascript will display the progress information on the H5 page. Oh...You will find it is difficult to achieve this function. Fortunately, DSBridge supports **Progress Callback**. You can be very simple and convenient to implement a call that needs to be returned many times. Here's an example of a countdown:
173 |
174 | In Java
175 |
176 | ```java
177 | @JavascriptInterface
178 | public void callProgress(Object args, final CompletionHandler handler) {
179 | new CountDownTimer(11000, 1000) {
180 | int i=10;
181 | @Override
182 | public void onTick(long millisUntilFinished) {
183 | //setProgressData can be called many times util complete be called.
184 | handler.setProgressData((i--));
185 | }
186 | @Override
187 | public void onFinish() {
188 | //complete the js invocation with data;
189 | //handler will be invalid when complete is called
190 | handler.complete(0);
191 | }
192 | }.start();
193 | }
194 | ```
195 |
196 | In Javascript
197 |
198 | ```javascript
199 | dsBridge.call("callProgress", function (value) {
200 | document.getElementById("progress").innerText = value
201 | })
202 | ```
203 |
204 | For the complete sample code, please refer to the demo project.
205 |
206 |
207 |
208 | ## Javascript popup box
209 |
210 | For Javascript popup box functions (alert/confirm/prompt), DSBridge has implemented them all by default, if you want to custom them, override the corresponding callback in WebChromeClient . The default dialog box implemented by DSBridge is modal. This will block the UI thread. If you need modeless, please refer to `dwebview.disableJavascriptDialogBlock (bool disable)`.
211 |
212 |
213 |
214 | ## Security
215 |
216 | Before Android 4.2 (API 17), `webview.addJavascriptInterface` has security vulnerabilities, and DSBridge doesn't use it under 4.2 of the devices. Meanwhile, in order to prevent Javascript from calling unauthorized native functions, all Java APIs must be annotated with "@JavascriptInterface" , so you can use DSBridge safely.
217 |
218 | ## DWebView
219 |
220 | In DWebview, the following functions will execute in main thread automatically, you need not to switch thread by yourself.
221 |
222 | ```java
223 | void loadUrl( String url)
224 | void loadUrl(final String url, Map additionalHttpHeaders)
225 | void evaluateJavascript(String script)
226 | ```
227 |
228 |
229 |
230 | ## API Reference
231 |
232 | ### Java API
233 |
234 | In Java, the object that implements the javascript interfaces is called **Java API object**.
235 |
236 | ##### `dwebview.addJavascriptObject(Object object, String namespace)`
237 |
238 | Add the Java API object with supplied namespace into DWebView. The javascript can then call Java APIs with `bridge.call("namespace.api",...)`.
239 |
240 | If the namespace is empty, the Java API object have no namespace. The javascript can call Java APIs with `bridge.call("api",...)`.
241 |
242 | Example:
243 |
244 | In Java
245 |
246 | ```javascript
247 | public class JsEchoApi {
248 | @JavascriptInterface
249 | public Object syn(Object args) throws JSONException {
250 | return args;
251 | }
252 |
253 | @JavascriptInterface
254 | public void asyn(Object args,CompletionHandler handler){
255 | handler.complete(args);
256 | }
257 | }
258 | //namespace is "echo"
259 | dwebView.addJavascriptObject(new JsEchoApi(),"echo");
260 | ```
261 |
262 | In Javascript
263 |
264 | ```javascript
265 | // call echo.syn
266 | var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1})
267 | alert(JSON.stringify(ret))
268 | // call echo.asyn
269 | dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},function (ret) {
270 | alert(JSON.stringify(ret));
271 | })
272 | ```
273 |
274 |
275 |
276 | ##### `dwebview.removeJavascriptObject(String namespace)`
277 |
278 | Remove the Java API object with supplied namespace.
279 |
280 |
281 |
282 | ##### `dwebview.callHandler(String handlerName, Object[] args)`
283 |
284 | ##### `dwebview.callHandler(String handlerName, OnReturnValue handler)`
285 |
286 | ##### `dwebview.callHandler(String handlerName, Object[] args,OnReturnValue handler)`
287 |
288 | Call the javascript API. If a `handler` is given, the javascript handler can respond. the `handlerName` can contain the namespace. **The handler will be called in main thread**.
289 |
290 | Example:
291 |
292 | ```java
293 |
294 | dWebView.callHandler("append",new Object[]{"I","love","you"},new OnReturnValue((){
295 | @Override
296 | public void onValue(String retValue) {
297 | Log.d("jsbridge","call succeed, append string is: "+retValue);
298 | }
299 | });
300 | // call with namespace 'syn', More details to see the Demo project
301 | dWebView.callHandler("syn.getInfo", new OnReturnValue() {
302 | @Override
303 | public void onValue(JSONObject retValue) {
304 | showToast(retValue);
305 | }
306 | });
307 | ```
308 |
309 |
310 |
311 | ##### `dwebview.disableJavascriptDialogBlock(bool disable)`
312 |
313 | BE CAREFUL to use. if you call any of the javascript popup box functions (`alert`,` confirm`, and `prompt`), the app will hang, and the javascript execution flow will be blocked. if you don't want to block the javascript execution flow, call this method, the popup box functions will return immediately( `confirm` return `true`, and the `prompt` return empty string).
314 |
315 | Example:
316 |
317 | ```javascript
318 | dwebview.disableJavascriptDialogBlock(true);
319 | ```
320 |
321 | if you want to enable the block, just calling this method with the argument value `false` .
322 |
323 |
324 |
325 | ##### `dwebview.setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener)`
326 |
327 | DWebView calls `listener.onClose` when Javascript calls `window.close`. the default handler is closing the current active activity. you can provide a listener to add your hanlder .
328 |
329 | Example:
330 |
331 | ```java
332 | dwebview.setJavascriptCloseWindowListener(new DWebView.JavascriptCloseWindowListener() {
333 | @Override
334 | public boolean onClose() {
335 | Log.d("jsbridge","window.close is called in Javascript");
336 | //If return false,the default handler will be prevented.
337 | return false;
338 | }
339 | });
340 | ```
341 |
342 |
343 |
344 | ##### `dwebview.hasJavascriptMethod(String handlerName, OnReturnValue existCallback)`
345 |
346 | Test whether the handler exist in javascript.
347 |
348 | Example:
349 |
350 | ```java
351 | dWebView.hasJavascriptMethod("addValue", new OnReturnValue() {
352 | @Override
353 | public void onValue(Boolean retValue) {
354 | showToast(retValue);
355 | }
356 | });
357 | ```
358 |
359 |
360 |
361 | ##### `DWebView.setWebContentsDebuggingEnabled(boolean enabled)`
362 |
363 | Set debug mode. if in debug mode, some errors will be prompted by a popup dialog , and the exception caused by the native APIs will not be captured to expose problems. We recommend that the debug mode be opened at the development stage.
364 |
365 |
366 |
367 | ### Javascript API
368 |
369 | ##### dsBridge
370 |
371 | "dsBridge" is accessed after dsBridge Initialization .
372 |
373 |
374 |
375 | ##### `dsBridge.call(method,[arg,callback])`
376 |
377 | Call Java api synchronously and asynchronously。
378 |
379 | `method`: Java API name, can contain the namespace。
380 |
381 | `arg`: argument, Only one allowed, if you expect multiple parameters, you can pass them with a json object.
382 |
383 | `callback(String returnValue)`: callback to handle the result. **only asynchronous invocation required**.
384 |
385 |
386 |
387 | ##### `dsBridge.register(methodName|namespace,function|synApiObject)`
388 |
389 | ##### `dsBridge.registerAsyn(methodName|namespace,function|asyApiObject)`
390 |
391 | Register javascript synchronous and asynchronous API for Native invocation. There are two types of invocation
392 |
393 | 1. Just register a method. For example:
394 |
395 | In Javascript
396 |
397 | ```javascript
398 | dsBridge.register('addValue',function(l,r){
399 | return l+r;
400 | })
401 | dsBridge.registerAsyn('append',function(arg1,arg2,arg3,responseCallback){
402 | responseCallback(arg1+" "+arg2+" "+arg3);
403 | })
404 | ```
405 |
406 | In Java
407 |
408 | ```java
409 | webView.callHandler("addValue",new Object[]{1,6},new OnReturnValue(){
410 | @Override
411 | public void onValue(String retValue) {
412 | Log.d("jsbridge","call succeed,return value is: "+retValue);
413 | }
414 | });
415 |
416 | webView.callHandler("append",new Object[]{"I","love","you"},new OnReturnValue(){
417 | @Override
418 | public void onValue(String retValue) {
419 | Log.d("jsbridge","call succeed, append string is: "+retValue);
420 | }
421 | });
422 | ```
423 |
424 |
425 |
426 | 2. Register a Javascript API object with supplied namespace. For example:
427 |
428 | **In Javascript**
429 |
430 | ```java
431 | //namespace test for synchronous
432 | dsBridge.register("test",{
433 | tag:"test",
434 | test1:function(){
435 | return this.tag+"1"
436 | },
437 | test2:function(){
438 | return this.tag+"2"
439 | }
440 | })
441 |
442 | //namespace test1 for asynchronous calls
443 | dsBridge.registerAsyn("test1",{
444 | tag:"test1",
445 | test1:function(responseCallback){
446 | return responseCallback(this.tag+"1")
447 | },
448 | test2:function(responseCallback){
449 | return responseCallback(this.tag+"2")
450 | }
451 | })
452 | ```
453 |
454 | > Because JavaScript does not support function overloading, it is not possible to define asynchronous function and sync function of the same name。
455 | >
456 |
457 | **In Java**
458 |
459 | ```java
460 | webView.callHandler("test.test1",new OnReturnValue(){
461 | @Override
462 | public void onValue(String retValue) {
463 | Log.d("jsbridge","Namespace test.test1: "+retValue);
464 | }
465 | });
466 |
467 | webView.callHandler("test1.test1",new OnReturnValue(){
468 | @Override
469 | public void onValue(String retValue) {
470 | Log.d("jsbridge","Namespace test.test1: "+retValue);
471 | }
472 | });
473 | ```
474 |
475 |
476 |
477 |
478 | ##### `dsBridge.hasNativeMethod(handlerName,[type])`
479 |
480 | Test whether the handler exist in Java, the `handlerName` can contain the namespace.
481 |
482 | `type`: optional`["all"|"syn"|"asyn" ]`, default is "all".
483 |
484 | ```javascript
485 | dsBridge.hasNativeMethod('testAsyn')
486 | //test namespace method
487 | dsBridge.hasNativeMethod('test.testAsyn')
488 | // test if exist a asynchronous function that named "testSyn"
489 | dsBridge.hasNativeMethod('testSyn','asyn') //false
490 | ```
491 |
492 |
493 |
494 | ##### `dsBridge.disableJavascriptDialogBlock(disable)`
495 |
496 | Calling `dsBridge.disableJavascriptDialogBlock(...)` has the same effect as calling `dwebview.disableJavascriptDialogBlock(...)` in Java.
497 |
498 | Example:
499 |
500 | ```javascript
501 | //disable
502 | dsBridge.disableJavascriptDialogBlock()
503 | //enable
504 | dsBridge.disableJavascriptDialogBlock(false)
505 | ```
506 |
507 |
508 |
509 | ## Work with fly.js
510 |
511 | As we all know, In browser, AJax request are restricted by same-origin policy, so the request cannot be initiated across the domain. However, [Fly.js](https://github.com/wendux/fly) supports forwarding the http request to Native through any Javascript bridge, And fly.js has already provide the dsBridge adapter.Because the Native side has no the same-origin policy restriction, fly.js can request any resource from any domain.
512 |
513 | Another typical scene is in the hybrid App, [Fly.js](https://github.com/wendux/fly) will forward all requests to Native, then, the unified request management, cookie management, certificate verification, request filtering and so on are carried out on Native.
514 |
515 | For the complete sample code, please refer to the demo project.
516 |
517 | ## Finally
518 |
519 | If you like DSBridge, please star to let more people know it , Thank you !
520 |
--------------------------------------------------------------------------------
/dsbridge/src/main/java/wendu/dsbridge/DWebView.java:
--------------------------------------------------------------------------------
1 | package wendu.dsbridge;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.annotation.TargetApi;
5 | import android.app.Activity;
6 | //import android.app.AlertDialog;
7 | import android.app.Dialog;
8 | import android.content.Context;
9 | import android.content.DialogInterface;
10 | import android.graphics.Bitmap;
11 | import android.net.Uri;
12 | import android.os.Build;
13 | import android.os.Handler;
14 | import android.os.Looper;
15 | import android.os.Message;
16 | import android.support.annotation.Keep;
17 | import android.support.v7.app.AlertDialog;
18 | import android.util.AttributeSet;
19 | import android.util.Log;
20 | import android.view.Gravity;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.webkit.ConsoleMessage;
24 | import android.webkit.CookieManager;
25 | import android.webkit.GeolocationPermissions;
26 | import android.webkit.JavascriptInterface;
27 | import android.webkit.JsPromptResult;
28 | import android.webkit.JsResult;
29 | import android.webkit.PermissionRequest;
30 | import android.webkit.ValueCallback;
31 | import android.webkit.WebChromeClient;
32 | import android.webkit.WebSettings;
33 | import android.webkit.WebStorage;
34 | import android.webkit.WebView;
35 | import android.widget.EditText;
36 | import android.widget.FrameLayout;
37 |
38 | import org.json.JSONArray;
39 | import org.json.JSONException;
40 | import org.json.JSONObject;
41 |
42 | import java.io.File;
43 | import java.lang.reflect.Method;
44 | import java.util.ArrayList;
45 | import java.util.Arrays;
46 | import java.util.HashMap;
47 | import java.util.Map;
48 |
49 |
50 | /**
51 | * Created by du on 16/12/29.
52 | */
53 |
54 | public class DWebView extends WebView {
55 | private static final String BRIDGE_NAME = "_dsbridge";
56 | private static final String LOG_TAG = "dsBridge";
57 | private static boolean isDebug = false;
58 | private Map javaScriptNamespaceInterfaces = new HashMap();
59 | private String APP_CACHE_DIRNAME;
60 | private int callID = 0;
61 | private WebChromeClient webChromeClient;
62 |
63 | private volatile boolean alertBoxBlock = true;
64 | private JavascriptCloseWindowListener javascriptCloseWindowListener = null;
65 | private ArrayList callInfoList;
66 | private InnerJavascriptInterface innerJavascriptInterface = new InnerJavascriptInterface();
67 | private Handler mainHandler = new Handler(Looper.getMainLooper());
68 |
69 | private class InnerJavascriptInterface {
70 |
71 | private void PrintDebugInfo(String error) {
72 | Log.d(LOG_TAG, error);
73 | if (isDebug) {
74 | evaluateJavascript(String.format("alert('%s')", "DEBUG ERR MSG:\\n" + error.replaceAll("\\'", "\\\\'")));
75 | }
76 | }
77 |
78 | @Keep
79 | @JavascriptInterface
80 | public String call(String methodName, String argStr) {
81 | String error = "Js bridge called, but can't find a corresponded " +
82 | "JavascriptInterface object , please check your code!";
83 | String[] nameStr = parseNamespace(methodName.trim());
84 | methodName = nameStr[1];
85 | Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]);
86 | JSONObject ret = new JSONObject();
87 | try {
88 | ret.put("code", -1);
89 | } catch (JSONException e) {
90 | e.printStackTrace();
91 | }
92 | if (jsb == null) {
93 | PrintDebugInfo(error);
94 | return ret.toString();
95 | }
96 | Object arg=null;
97 | Method method = null;
98 | String callback = null;
99 |
100 | try {
101 | JSONObject args = new JSONObject(argStr);
102 | if (args.has("_dscbstub")) {
103 | callback = args.getString("_dscbstub");
104 | }
105 | if(args.has("data")) {
106 | arg = args.get("data");
107 | }
108 | } catch (JSONException e) {
109 | error = String.format("The argument of \"%s\" must be a JSON object string!", methodName);
110 | PrintDebugInfo(error);
111 | e.printStackTrace();
112 | return ret.toString();
113 | }
114 |
115 |
116 | Class> cls = jsb.getClass();
117 | boolean asyn = false;
118 | try {
119 | method = cls.getMethod(methodName,
120 | new Class[]{Object.class, CompletionHandler.class});
121 | asyn = true;
122 | } catch (Exception e) {
123 | try {
124 | method = cls.getMethod(methodName, new Class[]{Object.class});
125 | } catch (Exception ex) {
126 |
127 | }
128 | }
129 |
130 | if (method == null) {
131 | error = "Not find method \"" + methodName + "\" implementation! please check if the signature or namespace of the method is right ";
132 | PrintDebugInfo(error);
133 | return ret.toString();
134 | }
135 |
136 |
137 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
138 | JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class);
139 | if (annotation == null) {
140 | error = "Method " + methodName + " is not invoked, since " +
141 | "it is not declared with JavascriptInterface annotation! ";
142 | PrintDebugInfo(error);
143 | return ret.toString();
144 | }
145 | }
146 |
147 | Object retData;
148 | method.setAccessible(true);
149 | try {
150 | if (asyn) {
151 | final String cb = callback;
152 | method.invoke(jsb, arg, new CompletionHandler() {
153 |
154 | @Override
155 | public void complete(Object retValue) {
156 | complete(retValue, true);
157 | }
158 |
159 | @Override
160 | public void complete() {
161 | complete(null, true);
162 | }
163 |
164 | @Override
165 | public void setProgressData(Object value) {
166 | complete(value, false);
167 | }
168 |
169 | private void complete(Object retValue, boolean complete) {
170 | try {
171 | JSONObject ret = new JSONObject();
172 | ret.put("code", 0);
173 | ret.put("data", retValue);
174 | //retValue = URLEncoder.encode(ret.toString(), "UTF-8").replaceAll("\\+", "%20");
175 | if (cb != null) {
176 | //String script = String.format("%s(JSON.parse(decodeURIComponent(\"%s\")).data);", cb, retValue);
177 | String script = String.format("%s(%s.data);", cb, ret.toString());
178 | if (complete) {
179 | script += "delete window." + cb;
180 | }
181 | //Log.d(LOG_TAG, "complete " + script);
182 | evaluateJavascript(script);
183 | }
184 | } catch (Exception e) {
185 | e.printStackTrace();
186 | }
187 | }
188 | });
189 | } else {
190 | retData = method.invoke(jsb, arg);
191 | ret.put("code", 0);
192 | ret.put("data", retData);
193 | return ret.toString();
194 | }
195 | } catch (Exception e) {
196 | e.printStackTrace();
197 | error = String.format("Call failed:The parameter of \"%s\" in Java is invalid.", methodName);
198 | PrintDebugInfo(error);
199 | return ret.toString();
200 | }
201 | return ret.toString();
202 | }
203 |
204 | }
205 |
206 | Map handlerMap = new HashMap<>();
207 |
208 | public interface JavascriptCloseWindowListener {
209 | /**
210 | * @return If true, close the current activity, otherwise, do nothing.
211 | */
212 | boolean onClose();
213 | }
214 |
215 |
216 | @Deprecated
217 | public interface FileChooser {
218 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
219 | void openFileChooser(ValueCallback valueCallback, String acceptType);
220 |
221 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
222 | void openFileChooser(ValueCallback valueCallback,
223 | String acceptType, String capture);
224 | }
225 |
226 | public DWebView(Context context, AttributeSet attrs) {
227 | super(context, attrs);
228 | init();
229 | }
230 |
231 | public DWebView(Context context) {
232 | super(context);
233 | init();
234 | }
235 |
236 | /**
237 | * Set debug mode. if in debug mode, some errors will be prompted by a dialog
238 | * and the exception caused by the native handlers will not be captured.
239 | *
240 | * @param enabled
241 | */
242 | public static void setWebContentsDebuggingEnabled(boolean enabled) {
243 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
244 | WebView.setWebContentsDebuggingEnabled(enabled);
245 | }
246 | isDebug = enabled;
247 | }
248 |
249 | @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
250 | private void init() {
251 | APP_CACHE_DIRNAME = getContext().getFilesDir().getAbsolutePath() + "/webcache";
252 | WebSettings settings = getSettings();
253 | settings.setDomStorageEnabled(true);
254 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
255 | CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);
256 | settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
257 | }
258 | settings.setAllowFileAccess(false);
259 | settings.setAppCacheEnabled(false);
260 | settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
261 | settings.setJavaScriptEnabled(true);
262 | settings.setLoadWithOverviewMode(true);
263 | settings.setAppCachePath(APP_CACHE_DIRNAME);
264 | settings.setUseWideViewPort(true);
265 | super.setWebChromeClient(mWebChromeClient);
266 | addInternalJavascriptObject();
267 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
268 | super.addJavascriptInterface(innerJavascriptInterface, BRIDGE_NAME);
269 | } else {
270 | // add dsbridge tag in lower android version
271 | settings.setUserAgentString(settings.getUserAgentString() + " _dsbridge");
272 | }
273 | }
274 |
275 |
276 | private String[] parseNamespace(String method) {
277 | int pos = method.lastIndexOf('.');
278 | String namespace = "";
279 | if (pos != -1) {
280 | namespace = method.substring(0, pos);
281 | method = method.substring(pos + 1);
282 | }
283 | return new String[]{namespace, method};
284 | }
285 |
286 | @Keep
287 | private void addInternalJavascriptObject() {
288 | addJavascriptObject(new Object() {
289 |
290 | @Keep
291 | @JavascriptInterface
292 | public boolean hasNativeMethod(Object args) throws JSONException {
293 | JSONObject jsonObject = (JSONObject) args;
294 | String methodName = jsonObject.getString("name").trim();
295 | String type = jsonObject.getString("type").trim();
296 | String[] nameStr = parseNamespace(methodName);
297 | Object jsb = javaScriptNamespaceInterfaces.get(nameStr[0]);
298 | if (jsb != null) {
299 | Class> cls = jsb.getClass();
300 | boolean asyn = false;
301 | Method method = null;
302 | try {
303 | method = cls.getMethod(nameStr[1],
304 | new Class[]{Object.class, CompletionHandler.class});
305 | asyn = true;
306 | } catch (Exception e) {
307 | try {
308 | method = cls.getMethod(nameStr[1], new Class[]{Object.class});
309 | } catch (Exception ex) {
310 |
311 | }
312 | }
313 | if (method != null) {
314 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
315 | JavascriptInterface annotation = method.getAnnotation(JavascriptInterface.class);
316 | if (annotation == null) {
317 | return false;
318 | }
319 | }
320 | if ("all".equals(type) || (asyn && "asyn".equals(type) || (!asyn && "syn".equals(type)))) {
321 | return true;
322 | }
323 |
324 | }
325 | }
326 | return false;
327 | }
328 |
329 | @Keep
330 | @JavascriptInterface
331 | public String closePage(Object object) throws JSONException {
332 | runOnMainThread(new Runnable() {
333 | @Override
334 | public void run() {
335 | if (javascriptCloseWindowListener == null
336 | || javascriptCloseWindowListener.onClose()) {
337 | Context context = getContext();
338 | if (context instanceof Activity) {
339 | ((Activity)context).onBackPressed();
340 | }
341 | }
342 | }
343 | });
344 | return null;
345 | }
346 |
347 | @Keep
348 | @JavascriptInterface
349 | public void disableJavascriptDialogBlock(Object object) throws JSONException {
350 | JSONObject jsonObject = (JSONObject) object;
351 | alertBoxBlock = !jsonObject.getBoolean("disable");
352 | }
353 |
354 | @Keep
355 | @JavascriptInterface
356 | public void dsinit(Object jsonObject) {
357 | DWebView.this.dispatchStartupQueue();
358 | }
359 |
360 | @Keep
361 | @JavascriptInterface
362 | public void returnValue(final Object obj){
363 | runOnMainThread(new Runnable() {
364 | @Override
365 | public void run() {
366 | JSONObject jsonObject = (JSONObject) obj;
367 | Object data = null;
368 | try {
369 | int id = jsonObject.getInt("id");
370 | boolean isCompleted = jsonObject.getBoolean("complete");
371 | OnReturnValue handler = handlerMap.get(id);
372 | if (jsonObject.has("data")) {
373 | data = jsonObject.get("data");
374 | }
375 | if (handler != null) {
376 | handler.onValue(data);
377 | if (isCompleted) {
378 | handlerMap.remove(id);
379 | }
380 | }
381 | } catch (JSONException e) {
382 | e.printStackTrace();
383 | }
384 | }
385 | });
386 | }
387 |
388 | }, "_dsb");
389 | }
390 |
391 | private void _evaluateJavascript(String script) {
392 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
393 | DWebView.super.evaluateJavascript(script, null);
394 | } else {
395 | super.loadUrl("javascript:" + script);
396 | }
397 | }
398 |
399 | /**
400 | * This method can be called in any thread, and if it is not called in the main thread,
401 | * it will be automatically distributed to the main thread.
402 | *
403 | * @param script
404 | */
405 | public void evaluateJavascript(final String script) {
406 | runOnMainThread(new Runnable() {
407 | @Override
408 | public void run() {
409 | _evaluateJavascript(script);
410 | }
411 | });
412 | }
413 |
414 | /**
415 | * This method can be called in any thread, and if it is not called in the main thread,
416 | * it will be automatically distributed to the main thread.
417 | *
418 | * @param url
419 | */
420 | @Override
421 | public void loadUrl(final String url) {
422 | runOnMainThread(new Runnable() {
423 | @Override
424 | public void run() {
425 | if (url != null && url.startsWith("javascript:")){
426 | DWebView.super.loadUrl(url);
427 | }else{
428 | callInfoList = new ArrayList<>();
429 | DWebView.super.loadUrl(url);
430 | }
431 | }
432 | });
433 | }
434 |
435 | /**
436 | * This method can be called in any thread, and if it is not called in the main thread,
437 | * it will be automatically distributed to the main thread.
438 | *
439 | * @param url
440 | * @param additionalHttpHeaders
441 | */
442 | @Override
443 | public void loadUrl(final String url, final Map additionalHttpHeaders) {
444 | runOnMainThread(new Runnable() {
445 | @Override
446 | public void run() {
447 | if (url != null && url.startsWith("javascript:")){
448 | DWebView.super.loadUrl(url, additionalHttpHeaders);
449 | }else{
450 | callInfoList = new ArrayList<>();
451 | DWebView.super.loadUrl(url, additionalHttpHeaders);
452 | }
453 | }
454 | });
455 | }
456 |
457 | @Override
458 | public void reload() {
459 | runOnMainThread(new Runnable() {
460 | @Override
461 | public void run() {
462 | callInfoList = new ArrayList<>();
463 | DWebView.super.reload();
464 | }
465 | });
466 | }
467 |
468 | /**
469 | * set a listener for javascript closing the current activity.
470 | */
471 | public void setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener) {
472 | javascriptCloseWindowListener = listener;
473 | }
474 |
475 |
476 | private static class CallInfo {
477 | private String data;
478 | private int callbackId;
479 | private String method;
480 |
481 | CallInfo(String handlerName, int id, Object[] args) {
482 | if (args == null) args = new Object[0];
483 | data = new JSONArray(Arrays.asList(args)).toString();
484 | callbackId = id;
485 | method = handlerName;
486 | }
487 |
488 | @Override
489 | public String toString() {
490 | JSONObject jo = new JSONObject();
491 | try {
492 | jo.put("method", method);
493 | jo.put("callbackId", callbackId);
494 | jo.put("data", data);
495 | } catch (JSONException e) {
496 | e.printStackTrace();
497 | }
498 | return jo.toString();
499 | }
500 | }
501 |
502 | private synchronized void dispatchStartupQueue() {
503 | if (callInfoList != null) {
504 | for (CallInfo info : callInfoList) {
505 | dispatchJavascriptCall(info);
506 | }
507 | callInfoList = null;
508 | }
509 | }
510 |
511 | private void dispatchJavascriptCall(CallInfo info) {
512 | evaluateJavascript(String.format("window._handleMessageFromNative(%s)", info.toString()));
513 | }
514 |
515 | public synchronized void callHandler(String method, Object[] args, final OnReturnValue handler) {
516 |
517 | CallInfo callInfo = new CallInfo(method, ++callID, args);
518 | if (handler != null) {
519 | handlerMap.put(callInfo.callbackId, handler);
520 | }
521 |
522 | if (callInfoList != null) {
523 | callInfoList.add(callInfo);
524 | } else {
525 | dispatchJavascriptCall(callInfo);
526 | }
527 |
528 | }
529 |
530 | public void callHandler(String method, Object[] args) {
531 | callHandler(method, args, null);
532 | }
533 |
534 | public void callHandler(String method, OnReturnValue handler) {
535 | callHandler(method, null, handler);
536 | }
537 |
538 |
539 | /**
540 | * Test whether the handler exist in javascript
541 | *
542 | * @param handlerName
543 | * @param existCallback
544 | */
545 | public void hasJavascriptMethod(String handlerName, OnReturnValue existCallback) {
546 | callHandler("_hasJavascriptMethod", new Object[]{handlerName}, existCallback);
547 | }
548 |
549 | /**
550 | * Add a java object which implemented the javascript interfaces to dsBridge with namespace.
551 | * Remove the object using {@link #removeJavascriptObject(String) removeJavascriptObject(String)}
552 | *
553 | * @param object
554 | * @param namespace if empty, the object have no namespace.
555 | */
556 | public void addJavascriptObject(Object object, String namespace) {
557 | if (namespace == null) {
558 | namespace = "";
559 | }
560 | if (object != null) {
561 | javaScriptNamespaceInterfaces.put(namespace, object);
562 | }
563 | }
564 |
565 | /**
566 | * remove the javascript object with supplied namespace.
567 | *
568 | * @param namespace
569 | */
570 | public void removeJavascriptObject(String namespace) {
571 | if (namespace == null) {
572 | namespace = "";
573 | }
574 | javaScriptNamespaceInterfaces.remove(namespace);
575 |
576 | }
577 |
578 |
579 | public void disableJavascriptDialogBlock(boolean disable) {
580 | alertBoxBlock = !disable;
581 | }
582 |
583 | @Override
584 | public void setWebChromeClient(WebChromeClient client) {
585 | webChromeClient = client;
586 | }
587 |
588 | private WebChromeClient mWebChromeClient = new WebChromeClient() {
589 |
590 | @Override
591 | public void onProgressChanged(WebView view, int newProgress) {
592 | if (webChromeClient != null) {
593 | webChromeClient.onProgressChanged(view, newProgress);
594 | } else {
595 | super.onProgressChanged(view, newProgress);
596 | }
597 | }
598 |
599 | @Override
600 | public void onReceivedTitle(WebView view, String title) {
601 | if (webChromeClient != null) {
602 | webChromeClient.onReceivedTitle(view, title);
603 | } else {
604 | super.onReceivedTitle(view, title);
605 | }
606 | }
607 |
608 | @Override
609 | public void onReceivedIcon(WebView view, Bitmap icon) {
610 | if (webChromeClient != null) {
611 | webChromeClient.onReceivedIcon(view, icon);
612 | } else {
613 | super.onReceivedIcon(view, icon);
614 | }
615 | }
616 |
617 | @Override
618 | public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
619 | if (webChromeClient != null) {
620 | webChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
621 | } else {
622 | super.onReceivedTouchIconUrl(view, url, precomposed);
623 | }
624 | }
625 |
626 | @Override
627 | public void onShowCustomView(View view, CustomViewCallback callback) {
628 | if (webChromeClient != null) {
629 | webChromeClient.onShowCustomView(view, callback);
630 | } else {
631 | super.onShowCustomView(view, callback);
632 | }
633 | }
634 |
635 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
636 | public void onShowCustomView(View view, int requestedOrientation,
637 | CustomViewCallback callback) {
638 | if (webChromeClient != null) {
639 | webChromeClient.onShowCustomView(view, requestedOrientation, callback);
640 | } else {
641 | super.onShowCustomView(view, requestedOrientation, callback);
642 | }
643 | }
644 |
645 | @Override
646 | public void onHideCustomView() {
647 | if (webChromeClient != null) {
648 | webChromeClient.onHideCustomView();
649 | } else {
650 | super.onHideCustomView();
651 | }
652 | }
653 |
654 | @Override
655 | public boolean onCreateWindow(WebView view, boolean isDialog,
656 | boolean isUserGesture, Message resultMsg) {
657 | if (webChromeClient != null) {
658 | return webChromeClient.onCreateWindow(view, isDialog,
659 | isUserGesture, resultMsg);
660 | }
661 | return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
662 | }
663 |
664 | @Override
665 | public void onRequestFocus(WebView view) {
666 | if (webChromeClient != null) {
667 | webChromeClient.onRequestFocus(view);
668 | } else {
669 | super.onRequestFocus(view);
670 | }
671 | }
672 |
673 | @Override
674 | public void onCloseWindow(WebView window) {
675 | if (webChromeClient != null) {
676 | webChromeClient.onCloseWindow(window);
677 | } else {
678 | super.onCloseWindow(window);
679 | }
680 | }
681 |
682 | @Override
683 | public boolean onJsAlert(WebView view, String url, final String message, final JsResult result) {
684 | if (!alertBoxBlock) {
685 | result.confirm();
686 | }
687 | if (webChromeClient != null) {
688 | if (webChromeClient.onJsAlert(view, url, message, result)) {
689 | return true;
690 | }
691 | }
692 | Dialog alertDialog = new AlertDialog.Builder(getContext()).
693 | setMessage(message).
694 | setCancelable(false).
695 | setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
696 | @Override
697 | public void onClick(DialogInterface dialog, int which) {
698 | dialog.dismiss();
699 | if (alertBoxBlock) {
700 | result.confirm();
701 | }
702 | }
703 | })
704 | .create();
705 | alertDialog.show();
706 | return true;
707 | }
708 |
709 | @Override
710 | public boolean onJsConfirm(WebView view, String url, String message,
711 | final JsResult result) {
712 | if (!alertBoxBlock) {
713 | result.confirm();
714 | }
715 | if (webChromeClient != null && webChromeClient.onJsConfirm(view, url, message, result)) {
716 | return true;
717 | } else {
718 | DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
719 | @Override
720 | public void onClick(DialogInterface dialog, int which) {
721 | if (alertBoxBlock) {
722 | if (which == Dialog.BUTTON_POSITIVE) {
723 | result.confirm();
724 | } else {
725 | result.cancel();
726 | }
727 | }
728 | }
729 | };
730 | new AlertDialog.Builder(getContext())
731 | .setMessage(message)
732 | .setCancelable(false)
733 | .setPositiveButton(android.R.string.ok, listener)
734 | .setNegativeButton(android.R.string.cancel, listener).show();
735 | return true;
736 |
737 | }
738 |
739 | }
740 |
741 | @Override
742 | public boolean onJsPrompt(WebView view, String url, final String message,
743 | String defaultValue, final JsPromptResult result) {
744 |
745 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
746 | String prefix = "_dsbridge=";
747 | if (message.startsWith(prefix)) {
748 | result.confirm(innerJavascriptInterface.call(message.substring(prefix.length()), defaultValue));
749 | return true;
750 | }
751 | }
752 |
753 | if (!alertBoxBlock) {
754 | result.confirm();
755 | }
756 |
757 | if (webChromeClient != null && webChromeClient.onJsPrompt(view, url, message, defaultValue, result)) {
758 | return true;
759 | } else {
760 | final EditText editText = new EditText(getContext());
761 | editText.setText(defaultValue);
762 | if (defaultValue != null) {
763 | editText.setSelection(defaultValue.length());
764 | }
765 | float dpi = getContext().getResources().getDisplayMetrics().density;
766 | DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
767 | @Override
768 | public void onClick(DialogInterface dialog, int which) {
769 | if (alertBoxBlock) {
770 | if (which == Dialog.BUTTON_POSITIVE) {
771 | result.confirm(editText.getText().toString());
772 | } else {
773 | result.cancel();
774 | }
775 | }
776 | }
777 | };
778 | new AlertDialog.Builder(getContext())
779 | .setTitle(message)
780 | .setView(editText)
781 | .setCancelable(false)
782 | .setPositiveButton(android.R.string.ok, listener)
783 | .setNegativeButton(android.R.string.cancel, listener)
784 | .show();
785 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
786 | ViewGroup.LayoutParams.MATCH_PARENT,
787 | ViewGroup.LayoutParams.WRAP_CONTENT);
788 | int t = (int) (dpi * 16);
789 | layoutParams.setMargins(t, 0, t, 0);
790 | layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
791 | editText.setLayoutParams(layoutParams);
792 | int padding = (int) (15 * dpi);
793 | editText.setPadding(padding - (int) (5 * dpi), padding, padding, padding);
794 | return true;
795 | }
796 |
797 | }
798 |
799 | @Override
800 | public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
801 | if (webChromeClient != null) {
802 | return webChromeClient.onJsBeforeUnload(view, url, message, result);
803 | }
804 | return super.onJsBeforeUnload(view, url, message, result);
805 | }
806 |
807 | @Override
808 | public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
809 | long estimatedDatabaseSize,
810 | long totalQuota,
811 | WebStorage.QuotaUpdater quotaUpdater) {
812 | if (webChromeClient != null) {
813 | webChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota,
814 | estimatedDatabaseSize, totalQuota, quotaUpdater);
815 | } else {
816 | super.onExceededDatabaseQuota(url, databaseIdentifier, quota,
817 | estimatedDatabaseSize, totalQuota, quotaUpdater);
818 | }
819 | }
820 |
821 | @Override
822 | public void onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater) {
823 | if (webChromeClient != null) {
824 | webChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
825 | }
826 | super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
827 | }
828 |
829 | @Override
830 | public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
831 | if (webChromeClient != null) {
832 | webChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
833 | } else {
834 | super.onGeolocationPermissionsShowPrompt(origin, callback);
835 | }
836 | }
837 |
838 | @Override
839 | public void onGeolocationPermissionsHidePrompt() {
840 | if (webChromeClient != null) {
841 | webChromeClient.onGeolocationPermissionsHidePrompt();
842 | } else {
843 | super.onGeolocationPermissionsHidePrompt();
844 | }
845 | }
846 |
847 |
848 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
849 | public void onPermissionRequest(PermissionRequest request) {
850 | if (webChromeClient != null) {
851 | webChromeClient.onPermissionRequest(request);
852 | } else {
853 | super.onPermissionRequest(request);
854 | }
855 | }
856 |
857 |
858 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
859 | @Override
860 | public void onPermissionRequestCanceled(PermissionRequest request) {
861 | if (webChromeClient != null) {
862 | webChromeClient.onPermissionRequestCanceled(request);
863 | } else {
864 | super.onPermissionRequestCanceled(request);
865 | }
866 | }
867 |
868 | @Override
869 | public boolean onJsTimeout() {
870 | if (webChromeClient != null) {
871 | return webChromeClient.onJsTimeout();
872 | }
873 | return super.onJsTimeout();
874 | }
875 |
876 | @Override
877 | public void onConsoleMessage(String message, int lineNumber, String sourceID) {
878 | if (webChromeClient != null) {
879 | webChromeClient.onConsoleMessage(message, lineNumber, sourceID);
880 | } else {
881 | super.onConsoleMessage(message, lineNumber, sourceID);
882 | }
883 | }
884 |
885 | @Override
886 | public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
887 | if (webChromeClient != null) {
888 | return webChromeClient.onConsoleMessage(consoleMessage);
889 | }
890 | return super.onConsoleMessage(consoleMessage);
891 | }
892 |
893 | @Override
894 | public Bitmap getDefaultVideoPoster() {
895 |
896 | if (webChromeClient != null) {
897 | return webChromeClient.getDefaultVideoPoster();
898 | }
899 | return super.getDefaultVideoPoster();
900 | }
901 |
902 | @Override
903 | public View getVideoLoadingProgressView() {
904 | if (webChromeClient != null) {
905 | return webChromeClient.getVideoLoadingProgressView();
906 | }
907 | return super.getVideoLoadingProgressView();
908 | }
909 |
910 | @Override
911 | public void getVisitedHistory(ValueCallback callback) {
912 | if (webChromeClient != null) {
913 | webChromeClient.getVisitedHistory(callback);
914 | } else {
915 | super.getVisitedHistory(callback);
916 | }
917 | }
918 |
919 |
920 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
921 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback,
922 | FileChooserParams fileChooserParams) {
923 | if (webChromeClient != null) {
924 | return webChromeClient.onShowFileChooser(webView, filePathCallback, fileChooserParams);
925 | }
926 | return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
927 | }
928 |
929 |
930 | @Keep
931 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
932 | public void openFileChooser(ValueCallback valueCallback, String acceptType) {
933 | if (webChromeClient instanceof FileChooser) {
934 | ((FileChooser) webChromeClient).openFileChooser(valueCallback, acceptType);
935 | }
936 | }
937 |
938 |
939 | @Keep
940 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
941 | public void openFileChooser(ValueCallback valueCallback,
942 | String acceptType, String capture) {
943 | if (webChromeClient instanceof FileChooser) {
944 | ((FileChooser) webChromeClient).openFileChooser(valueCallback, acceptType, capture);
945 | }
946 | }
947 |
948 | };
949 |
950 | @Override
951 | public void clearCache(boolean includeDiskFiles) {
952 | super.clearCache(includeDiskFiles);
953 | CookieManager.getInstance().removeAllCookie();
954 | Context context = getContext();
955 | try {
956 | context.deleteDatabase("webview.db");
957 | context.deleteDatabase("webviewCache.db");
958 | } catch (Exception e) {
959 | e.printStackTrace();
960 | }
961 |
962 | File appCacheDir = new File(APP_CACHE_DIRNAME);
963 | File webviewCacheDir = new File(context.getCacheDir()
964 | .getAbsolutePath() + "/webviewCache");
965 |
966 | if (webviewCacheDir.exists()) {
967 | deleteFile(webviewCacheDir);
968 | }
969 |
970 | if (appCacheDir.exists()) {
971 | deleteFile(appCacheDir);
972 | }
973 | }
974 |
975 | public void deleteFile(File file) {
976 | if (file.exists()) {
977 | if (file.isFile()) {
978 | file.delete();
979 | } else if (file.isDirectory()) {
980 | File files[] = file.listFiles();
981 | for (int i = 0; i < files.length; i++) {
982 | deleteFile(files[i]);
983 | }
984 | }
985 | file.delete();
986 | } else {
987 | Log.e("Webview", "delete file no exists " + file.getAbsolutePath());
988 | }
989 | }
990 |
991 | private void runOnMainThread(Runnable runnable) {
992 | if (Looper.getMainLooper() == Looper.myLooper()) {
993 | runnable.run();
994 | return;
995 | }
996 | mainHandler.post(runnable);
997 | }
998 |
999 |
1000 |
1001 |
1002 | }
1003 |
--------------------------------------------------------------------------------