From bb17b74b99308e3f5fd6688b7ac4e52c2ce2bf0a Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Fri, 5 Nov 2021 15:14:32 +0800 Subject: [PATCH] Android:use webview to execute js --- .../java/pub/doric/engine/DoricJSEngine.java | 4 +- .../doric/engine/DoricWebViewJSExecutor.java | 124 +++++++++++++---- doric-js/bundle/doric-web.js | 126 ++++++++++++++++++ doric-js/index.web.ts | 72 +++++++++- doric-js/lib/index.web.d.ts | 18 ++- doric-js/lib/index.web.js | 64 ++++++++- doric-js/rollup.config.js | 1 + 7 files changed, 367 insertions(+), 42 deletions(-) diff --git a/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java b/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java index 143b4878..218b71bd 100644 --- a/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java +++ b/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java @@ -97,6 +97,7 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time protected void initJSEngine() { mDoricJSE = new DoricWebViewJSExecutor(Doric.application()); + loadBuiltinJS("doric-web.js"); } public void setEnvironmentValue(Map values) { @@ -239,9 +240,6 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time private void initDoricRuntime() { try { - if (mDoricJSE instanceof DoricWebViewJSExecutor) { - loadBuiltinJS("doric-web.js"); - } loadBuiltinJS(DoricConstant.DORIC_BUNDLE_SANDBOX); String libName = DoricConstant.DORIC_MODULE_LIB; String libJS = DoricUtils.readAssetFile(DoricConstant.DORIC_BUNDLE_LIB); diff --git a/doric-android/doric/src/main/java/pub/doric/engine/DoricWebViewJSExecutor.java b/doric-android/doric/src/main/java/pub/doric/engine/DoricWebViewJSExecutor.java index 483bc44d..4ae2d1ab 100644 --- a/doric-android/doric/src/main/java/pub/doric/engine/DoricWebViewJSExecutor.java +++ b/doric-android/doric/src/main/java/pub/doric/engine/DoricWebViewJSExecutor.java @@ -17,6 +17,7 @@ package pub.doric.engine; import android.annotation.SuppressLint; import android.content.Context; +import android.text.TextUtils; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.JsResult; @@ -37,6 +38,7 @@ import org.json.JSONObject; import java.util.HashMap; import java.util.Map; +import pub.doric.async.SettableFuture; import pub.doric.utils.DoricLog; @@ -59,22 +61,83 @@ public class DoricWebViewJSExecutor implements IDoricJSE { case "boolean": return jsonObject.optBoolean("value"); case "object": - return jsonObject.optJSONObject("value"); + try { + return new JSONObject(jsonObject.optString("value")); + } catch (JSONException e) { + e.printStackTrace(); + return JSONObject.NULL; + } case "array": - return jsonObject.optJSONArray("value"); + try { + return new JSONArray(jsonObject.optString("value")); + } catch (JSONException e) { + e.printStackTrace(); + return JSONObject.NULL; + } default: return JSONObject.NULL; } } - private static final String WRAPPED_NULL = new JSONBuilder().put("type", "null").toString(); + private static JSONObject wrapJavaValue(JavaValue javaValue) { + if (javaValue == null || javaValue.getType() == 0) { + return WRAPPED_NULL; + } + if (javaValue.getType() == 1) { + Double value = Double.valueOf(javaValue.getValue()); + return new JSONBuilder().put("type", "number").put("value", value).toJSONObject(); + } + if (javaValue.getType() == 2) { + Boolean value = Boolean.valueOf(javaValue.getValue()); + return new JSONBuilder().put("type", "boolean").put("value", value).toJSONObject(); + } + if (javaValue.getType() == 3) { + String value = String.valueOf(javaValue.getValue()); + return new JSONBuilder().put("type", "string").put("value", value).toJSONObject(); + } + if (javaValue.getType() == 4) { + String value = String.valueOf(javaValue.getValue()); + try { + return new JSONBuilder().put("type", "object").put("value", new JSONObject(value)).toJSONObject(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + if (javaValue.getType() == 5) { + String value = String.valueOf(javaValue.getValue()); + try { + return new JSONBuilder().put("type", "array").put("value", new JSONArray(value)).toJSONObject(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return WRAPPED_NULL; + } + + private static final JSONObject WRAPPED_NULL = new JSONBuilder().put("type", "null").toJSONObject(); + + private static final String WRAPPED_NULL_STRING = WRAPPED_NULL.toString(); + private SettableFuture returnFuture = null; public class WebViewCallback { + @JavascriptInterface + public void log(String message) { + DoricLog.d(message); + } + + @JavascriptInterface + public void returnNative(String result) { + if (returnFuture != null) { + returnFuture.set(result); + } + } + @JavascriptInterface public String callNative(String name, String arguments) { JavaFunction javaFunction = globalFunctions.get(name); if (javaFunction == null) { - return WRAPPED_NULL; + DoricLog.e("Cannot find global function %s", name); + return WRAPPED_NULL_STRING; } try { JSONArray jsonArray = new JSONArray(arguments); @@ -86,33 +149,11 @@ public class DoricWebViewJSExecutor implements IDoricJSE { decoders[i] = new JavaJSDecoder(object); } JavaValue javaValue = javaFunction.exec(decoders); - if (javaValue.getType() == 0) { - return WRAPPED_NULL; - } - if (javaValue.getType() == 1) { - Double value = Double.valueOf(javaValue.getValue()); - return new JSONBuilder().put("type", "number").put("value", value).toString(); - } - if (javaValue.getType() == 2) { - Boolean value = Boolean.valueOf(javaValue.getValue()); - return new JSONBuilder().put("type", "boolean").put("value", value).toString(); - } - if (javaValue.getType() == 3) { - String value = String.valueOf(javaValue.getValue()); - return new JSONBuilder().put("type", "string").put("value", value).toString(); - } - if (javaValue.getType() == 4) { - String value = String.valueOf(javaValue.getValue()); - return new JSONBuilder().put("type", "object").put("value", value).toString(); - } - if (javaValue.getType() == 5) { - String value = String.valueOf(javaValue.getValue()); - return new JSONBuilder().put("type", "array").put("value", value).toString(); - } + return wrapJavaValue(javaValue).toString(); } catch (JSONException e) { e.printStackTrace(); } - return WRAPPED_NULL; + return WRAPPED_NULL_STRING; } } @@ -163,15 +204,40 @@ public class DoricWebViewJSExecutor implements IDoricJSE { @Override public void injectGlobalJSFunction(String name, JavaFunction javaFunction) { globalFunctions.put(name, javaFunction); + loadJS(String.format("__injectGlobalFunction('%s')", name), ""); } @Override public void injectGlobalJSObject(String name, JavaValue javaValue) { - + loadJS(String.format("_injectGlobalObject('%s','%s')", name, javaValue.getValue()), ""); } @Override public JSDecoder invokeMethod(String objectName, String functionName, JavaValue[] javaValues, boolean hashKey) throws JSRuntimeException { + JSONArray jsonArray = new JSONArray(); + for (int i = 0; i < javaValues.length; i++) { + JavaValue javaValue = javaValues[i]; + JSONObject jsonObject = wrapJavaValue(javaValue); + jsonArray.put(jsonObject); + } + + returnFuture = new SettableFuture<>(); + String script = String.format( + "__invokeMethod('%s','%s','%s')", + objectName, + functionName, + jsonArray.toString()); + loadJS(script, ""); + String result = returnFuture.get(); + if (!TextUtils.isEmpty(result)) { + try { + JSONObject jsonObject = new JSONObject(result); + Object object = unwrapJSObject(jsonObject); + return new JavaJSDecoder(object); + } catch (JSONException e) { + e.printStackTrace(); + } + } return null; } diff --git a/doric-js/bundle/doric-web.js b/doric-js/bundle/doric-web.js index eb109abb..de735baa 100644 --- a/doric-js/bundle/doric-web.js +++ b/doric-js/bundle/doric-web.js @@ -1,2 +1,128 @@ 'use strict'; +"use strict"; +function _binaryValue(v) { + switch (typeof v) { + case "number": + return { + type: "number", + value: v + }; + case "string": + return { + type: "string", + value: v + }; + case "boolean": + return { + type: "boolean", + value: v + }; + case "object": + if (v instanceof Array) { + return { + type: "array", + value: JSON.stringify(v) + }; + } + else { + return { + type: "object", + value: JSON.stringify(v) + }; + } + default: + return { + type: "null", + value: undefined + }; + } +} +function _wrappedValue(v) { + switch (typeof v) { + case "number": + return { + type: "number", + value: v + }; + case "string": + return { + type: "string", + value: v + }; + case "boolean": + return { + type: "boolean", + value: v + }; + case "object": + if (v instanceof Array) { + return { + type: "array", + value: JSON.stringify(v) + }; + } + else { + return { + type: "object", + value: JSON.stringify(v) + }; + } + default: + return { + type: "null", + value: undefined + }; + } +} +function _rawValue(v) { + switch (v.type) { + case "number": + return v.value; + case "string": + return v.value; + case "boolean": + return v.value; + case "object": + case "array": + return v.value; + default: + return undefined; + } +} +function _injectGlobalObject(name, args) { + Reflect.set(window, name, JSON.parse(args)); +} +function __injectGlobalFunction(name) { + Reflect.set(window, name, function () { + const args = []; + for (let i = 0; i < arguments.length; i++) { + args.push(_wrappedValue(arguments[i])); + } + const ret = NativeClient.callNative(name, JSON.stringify(args)); + return _rawValue(JSON.parse(ret)); + }); +} +function __invokeMethod(objectName, functionName, stringifiedArgs) { + NativeClient.log(`invoke:${objectName}.${functionName}(${stringifiedArgs})`); + try { + const thisObject = Reflect.get(window, objectName); + const thisFunction = Reflect.get(thisObject, functionName); + const args = JSON.parse(stringifiedArgs); + args.forEach(e => { + NativeClient.log(`Arg:${e},${typeof e}`); + }); + const rawArgs = args.map(e => _rawValue(e)); + rawArgs.forEach(e => { + NativeClient.log(`RawArg:${e},${typeof e}`); + }); + const ret = Reflect.apply(thisFunction, thisObject, rawArgs); + const returnVal = JSON.stringify(_wrappedValue(ret)); + NativeClient.log(`return:${returnVal}`); + NativeClient.returnNative(returnVal); + } + catch (e) { + NativeClient.log(`error:${e},${e.stack}`); + NativeClient.returnNative(""); + } +} diff --git a/doric-js/index.web.ts b/doric-js/index.web.ts index ba5c2929..6796edcf 100644 --- a/doric-js/index.web.ts +++ b/doric-js/index.web.ts @@ -1,4 +1,6 @@ declare module NativeClient { + function log(message: string): void + function returnNative(ret: string): void function callNative(name: string, args: string): string } @@ -8,7 +10,43 @@ type WrappedValue = { type: "number" | "string" | "boolean" | "object" | "array" | "null", value: RawValue, } +function _binaryValue(v: RawValue) { + switch (typeof v) { + case "number": + return { + type: "number", + value: v + }; + case "string": + return { + type: "string", + value: v + }; + case "boolean": + return { + type: "boolean", + value: v + }; + case "object": + if (v instanceof Array) { + return { + type: "array", + value: JSON.stringify(v) + }; + } else { + return { + type: "object", + value: JSON.stringify(v) + }; + } + default: + return { + type: "null", + value: undefined + }; + } +} function _wrappedValue(v: RawValue): WrappedValue { switch (typeof v) { case "number": @@ -49,20 +87,23 @@ function _wrappedValue(v: RawValue): WrappedValue { function _rawValue(v: WrappedValue): RawValue { switch (v.type) { case "number": - return v.value + return v.value; case "string": - return v.value + return v.value; case "boolean": - return v.value + return v.value; case "object": case "array": - return JSON.stringify(v.value) + if (typeof v.value === 'string') { + return JSON.parse(v.value) + } + return v.value; default: - return undefined + return undefined; } } -function __injectGlobalObject(name: string, args: string) { +function _injectGlobalObject(name: string, args: string) { Reflect.set(window, name, JSON.parse(args)); } @@ -73,6 +114,23 @@ function __injectGlobalFunction(name: string) { args.push(_wrappedValue(arguments[i])); } const ret = NativeClient.callNative(name, JSON.stringify(args)); - return _rawValue(JSON.parse(ret)) + return _rawValue(JSON.parse(ret)); }); +} + +function __invokeMethod(objectName: string, functionName: string, stringifiedArgs: string) { + NativeClient.log(`invoke:${objectName}.${functionName}(${stringifiedArgs})`) + try { + const thisObject = Reflect.get(window, objectName); + const thisFunction = Reflect.get(thisObject, functionName); + const args = JSON.parse(stringifiedArgs) as WrappedValue[]; + const rawArgs = args.map(e => _rawValue(e)); + const ret = Reflect.apply(thisFunction, thisObject, rawArgs); + const returnVal = JSON.stringify(_wrappedValue(ret)) + NativeClient.log(`return:${returnVal}`) + NativeClient.returnNative(returnVal) + } catch (e) { + NativeClient.log(`error:${e},${(e as any).stack}`) + NativeClient.returnNative("") + } } \ No newline at end of file diff --git a/doric-js/lib/index.web.d.ts b/doric-js/lib/index.web.d.ts index 9f8ea2a5..f5042a09 100644 --- a/doric-js/lib/index.web.d.ts +++ b/doric-js/lib/index.web.d.ts @@ -1,4 +1,6 @@ declare module NativeClient { + function log(message: string): void; + function returnNative(ret: string): void; function callNative(name: string, args: string): string; } declare type RawValue = number | string | boolean | object | undefined; @@ -6,7 +8,21 @@ declare type WrappedValue = { type: "number" | "string" | "boolean" | "object" | "array" | "null"; value: RawValue; }; +declare function _binaryValue(v: RawValue): { + type: string; + value: number; +} | { + type: string; + value: string; +} | { + type: string; + value: boolean; +} | { + type: string; + value: undefined; +}; declare function _wrappedValue(v: RawValue): WrappedValue; declare function _rawValue(v: WrappedValue): RawValue; -declare function __injectGlobalObject(name: string, args: string): void; +declare function _injectGlobalObject(name: string, args: string): void; declare function __injectGlobalFunction(name: string): void; +declare function __invokeMethod(objectName: string, functionName: string, stringifiedArgs: string): void; diff --git a/doric-js/lib/index.web.js b/doric-js/lib/index.web.js index 1af72de8..995ddf16 100644 --- a/doric-js/lib/index.web.js +++ b/doric-js/lib/index.web.js @@ -1,4 +1,41 @@ "use strict"; +function _binaryValue(v) { + switch (typeof v) { + case "number": + return { + type: "number", + value: v + }; + case "string": + return { + type: "string", + value: v + }; + case "boolean": + return { + type: "boolean", + value: v + }; + case "object": + if (v instanceof Array) { + return { + type: "array", + value: JSON.stringify(v) + }; + } + else { + return { + type: "object", + value: JSON.stringify(v) + }; + } + default: + return { + type: "null", + value: undefined + }; + } +} function _wrappedValue(v) { switch (typeof v) { case "number": @@ -46,12 +83,12 @@ function _rawValue(v) { return v.value; case "object": case "array": - return JSON.stringify(v.value); + return v.value; default: return undefined; } } -function __injectGlobalObject(name, args) { +function _injectGlobalObject(name, args) { Reflect.set(window, name, JSON.parse(args)); } function __injectGlobalFunction(name) { @@ -64,3 +101,26 @@ function __injectGlobalFunction(name) { return _rawValue(JSON.parse(ret)); }); } +function __invokeMethod(objectName, functionName, stringifiedArgs) { + NativeClient.log(`invoke:${objectName}.${functionName}(${stringifiedArgs})`); + try { + const thisObject = Reflect.get(window, objectName); + const thisFunction = Reflect.get(thisObject, functionName); + const args = JSON.parse(stringifiedArgs); + args.forEach(e => { + NativeClient.log(`Arg:${e},${typeof e}`); + }); + const rawArgs = args.map(e => _rawValue(e)); + rawArgs.forEach(e => { + NativeClient.log(`RawArg:${e},${typeof e}`); + }); + const ret = Reflect.apply(thisFunction, thisObject, rawArgs); + const returnVal = JSON.stringify(_wrappedValue(ret)); + NativeClient.log(`return:${returnVal}`); + NativeClient.returnNative(returnVal); + } + catch (e) { + NativeClient.log(`error:${e},${e.stack}`); + NativeClient.returnNative(""); + } +} diff --git a/doric-js/rollup.config.js b/doric-js/rollup.config.js index 6a580f4e..17eb7718 100644 --- a/doric-js/rollup.config.js +++ b/doric-js/rollup.config.js @@ -61,6 +61,7 @@ export default [ } console.warn(warning.message); }, + treeshake: false, }, { input: "lib-es5/index.runtime.es5.js",