Android JS解析引擎 Rhino 使用笔记(不借助webview)
来源:互联网 发布:python游戏开发pdf 编辑:程序博客网 时间:2024/05/22 09:02
在使用过程中有个需求是在不大改动移动端现有处理逻辑的基础上,通过后期配置来灵活更改本地的逻辑联系。最终选定的方案是借助Js,一开始想到用webview,但webview开销大。经查找,最终使用了 Rhino。
注:本文主要参考自【Android】不使用WebView来执行Javascript脚本(Rhino)
Rhino 简介
(摘自:https://www.ibm.com/developerworks/cn/java/j-lo-rhino/)
Rhino 是开源的 JavaScript 引擎,是完全基于 Java 实现,几乎可以使用 JavaScript 完成 Java 所有的工作。它可以提供强大的计算能力,没有 I/O 的限制,可以将 JavaScript 编译成 Java 字节码,具有良好的速度和性能。在 Rhino 环境中既可以使用 JavaScript 脚本语言,同时也可以非常简单的使用 Java 语言的某些工具。Rhino 为我们提供了如下功能:
- 对 JavaScript 1.5 的完全支持
- 直接在 Java 中使用 JavaScript 的功能
- 一个 JavaScript shell 用于运行 JavaScript 脚本
- 一个 JavaScript 的编译器,用于将 JavaScript 编译成 Java 二进制文件
github 地址:https://github.com/mozilla/rhino
Rhino 官网地址 : https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino
AndroidStudo 中导入Rhino
- 下载Rhino jar 包。 Rhino jar 包下载
- 将jar包放入libs文件夹下。右击该jar包,选择 add as library,选择Model导入
基本使用
基本使用参考 【Android】不使用WebView来执行Javascript脚本(Rhino)
或者官方的示例
封装
在参考的博客中,Rhino嵌在Activity中。由于项目中多个地方需要使用到,所以需要将其封装起来。
另外,由于主要功能是运行js语句来调用本地的java方法,故封装也主要是实现js调用java
/** * JS解析封装 */public class JSEngine{ private Class clazz; private String allFunctions ="";//js方法语句 public JSEngine(){ this.clazz = JSEngine.class; initJSStr();//初始化js语句 } private void initJSStr(){ /** * 在此处可以看到 javaContext、javaLoader的应用, * 基本使用原理应该是利用类名、类加载器和上下文去获取JSEngine的类和方法 * 注意method的输入参数类型与本地方法的对应 */ allFunctions = " var ScriptAPI = java.lang.Class.forName(\"" + JSEngine.class.getName() + "\", true, javaLoader);\n" + " var methodGetValue= ScriptAPI.getMethod(\"getValue\", [java.lang.String]);\n" + " function getValue(key) {\n" + " return methodGetValue.invoke(javaContext,key);\n" + " }\n" + " var methodSetValue=ScriptAPI.getMethod(\"setValue\",[java.lang.Object,java.lang.Object]);\n" + " function setValue(key,value) {\n" + " methodSetValue.invoke(javaContext,key,value);\n" + " }\n"; } //本地java方法 public void setValue(Object keyStr, Object o) { System.out.println("JSEngine output - setValue : " + keyStr.toString() + " ------> " + o.toString()); } //本地java方法 public String getValue(String keyStr) { System.out.println("JSEngine output - getValue : " + keyStr.toString() ); return "获取到值了"; } /** * 执行JS * @param js js执行代码 eg: "var v1 = getValue('Ta');setValue(‘key’,v1);" */ public void runScript(String js){ String runJSStr = allFunctions + "\n" + js;//运行js = allFunctions + js org.mozilla.javascript.Context rhino = org.mozilla.javascript.Context.enter(); rhino.setOptimizationLevel()-1; try { Scriptable scope = rhino.initStandardObjects(); ScriptableObject.putProperty(scope, "javaContext", org.mozilla.javascript.Context.javaToJS(this, scope));//配置属性 javaContext:当前类JSEngine的上下文 ScriptableObject.putProperty(scope, "javaLoader", org.mozilla.javascript.Context.javaToJS(clazz.getClassLoader(), scope));//配置属性 javaLoader:当前类的JSEngine的类加载器 rhino.evaluateString(scope, runJSStr, clazz.getSimpleName(), 1, null); } finally { org.mozilla.javascript.Context.exit(); } }}
使用测试
public class JSActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_js); JSEngine jsEngine = new JSEngine(); jsEngine.runScript(testjs); } private String testjs ="var val = getValue('testKey');" + "setValue('setKey',val)"; }
结果输出如下:
System.out: JSEngine output - getValue : testKeySystem.out: JSEngine output - setValue : setKey ------> 获取到值了
方法测试 - 返回本地类
- 添加本地测试类
class TestObject{ private String name; private String address; TestObject(String name ,String address){ this.name = name; this.address = address; }}
- 为JSEngine添加方法 getObjectValue
public Object getObjectValue(Object keyStr){ System.out.println("JSEngine output - getObjectValue : " + keyStr.toString()); return new TestObject("小明","广州");}
- allFunctions 添加 getObjectValue 声明
allFunctions = " var ScriptAPI = java.lang.Class.forName(\"" + JSEngine.class.getName() + "\", true, javaLoader);\n" + " var methodGetValue= ScriptAPI.getMethod(\"getValue\", [java.lang.String]);\n" + " function getValue(key) {\n" + " return methodGetValue.invoke(javaContext,key);\n" + " }\n" + " var methodSetValue=ScriptAPI.getMethod(\"setValue\",[java.lang.Object,java.lang.Object]);\n" + " function setValue(key,value) {\n" + " methodSetValue.invoke(javaContext,key,value);\n" + " }\n"+ " var methodGetObjectValue=ScriptAPI.getMethod(\"getObjectValue\",[java.lang.Object]);\n" + " function getObjectValue(key) {\n" + " return methodGetObjectValue.invoke(javaContext,key);\n" + " }\n";
- 修改执行语句,尝试获取 name属性的值
private String testjs = "var test = getObjectValue('objectKey');" + "setValue('testvalue',test.name);";
- 运行,发现无法获取到属性name的值
System.out: JSEngine output - setValue : testvalue ------> undefined
解决方案
思路:先将返回的对象转成字符串,再利用 javascript 的 eval 函数将字符串转成符合要求的对象
此时,需要修改 JSEngine 中的getObjectValue方法和 allFunctions 中的 getObjectValue 方法
//修改 JSEngine 中的getObjectValue方法public String getObjectValue(Object keyStr){ System.out.println("JSEngine output - getObjectValue : " + keyStr.toString()); return new Gson().toJson(new TestObject("小明","广州"));//利用Gson 将 TestObject对象先转成String}
//修改 allFunctions 中的getObjectValue方法 " var methodGetObjectValue=ScriptAPI.getMethod(\"getObjectValue\",[java.lang.Object]);\n" +" function getObjectValue(key) {\n" +" var retStr = methodGetObjectValue.invoke(javaContext,key);\n" +" var ret = {};" +" eval('ret='+retStr);" +" return ret;" +" }\n";
仍执行以下语句
private String testjs = "var test = getObjectValue('objectKey');" + "setValue('testvalue',test.name);";
输出如下:可以看到能使用 .name 的形式获取到 name属性的值
System.out: JSEngine output - getObjectValue : objectKeySystem.out: JSEngine output - setValue : testvalue ------> 小明
反射构建js语句
通过上面可以看到,在JSEngine中每添加一个方法,在JS语句中也要对应多添加一个方法。而在js语句的编写过程中则需要注意多处细节,比较容易书写错误,所以能否自动生成js语句而不用每次都手写呢?
有的,利用注解和反射。
首先观察前面的js语句
每个本地方法在js中的定义主要包括两部分:
1、通过本地方法的方法名来获得该方法
var methodGetValue= ScriptAPI.getMethod(\"getValue\", [java.lang.String]);\n
2、自定义js方法(可重新命名),在js方法中调用本地方法的引用
function getValue(key) { return methodGetValue.invoke(javaContext,key);}
其他有差异的话则在于返回值类型为本地类对象时候的js方法的不同,如
function getObjectValue(key) { var retStr = methodGetObjectValue.invoke(javaContext,key); var ret = {}; eval('ret='+retStr); return ret; }
下面开始反射构建js语句
1、创建注解 JSAnnotation ,设定参数 returnObject ,用于区分上面所述的方法是否返回本地类对象。
/** * 注解 */@Target(value = ElementType.METHOD)@Retention(value = RetentionPolicy.RUNTIME)public @interface JSAnnotation { boolean returnObject() default false;//是否返回对象,默认为false 不返回}
2、为方法添加注解
//本地java方法,声明注解@JSAnnotationpublic void setValue(Object keyStr, Object o) { System.out.println("JSEngine output - setValue : " + keyStr.toString() + " ------> " + o.toString());}//本地java方法,声明注解@JSAnnotationpublic String getValue(String keyStr) { System.out.println("JSEngine output - getValue : " + keyStr.toString() ); return "获取到值了";}//有返回本地类对象,则returnObject 设置为true @JSAnnotation(returnObject = true) function getObjectValue(key) { var retStr = methodGetObjectValue.invoke(javaContext,key); var ret = {}; eval('ret='+retStr); return ret;}
3、利用注解生成js语句
/** * 通过注解自动生成js方法语句 */private String getAllFunctions(){ String funcStr = " var ScriptAPI = java.lang.Class.forName(\"%s\", true, javaLoader);\n" ; Class cls = this.getClass(); for (Method method: cls.getDeclaredMethods()){ JSAnnotation an = method.getAnnotation(JSAnnotation.class); if (an == null ) continue; String functionName = method.getName(); String paramsTypeString ="";//获取function的参数类型 String paramsNameString = "";//获取function的参数名称 String paramsNameInvokeString = ""; Class [] parmTypeArray = method.getParameterTypes(); if (parmTypeArray != null && parmTypeArray.length > 0){ String[] parmStrArray = new String[parmTypeArray.length]; String[] parmNameArray = new String[parmTypeArray.length]; for (int i=0;i < parmTypeArray.length; i++){ parmStrArray[i] = parmTypeArray[i].getName(); parmNameArray[i] = "param" + i ; } paramsTypeString = String.format(",[%s]",TextUtils.join(",",parmStrArray)); paramsNameString = TextUtils.join(",",parmNameArray); paramsNameInvokeString = "," + paramsNameString; } Class returnType = method.getReturnType(); String returnStr = returnType.getSimpleName().equals("void") ? "" : "return";//是否有返回值 String methodStr = String.format(" var method_%s = ScriptAPI.getMethod(\"%s\"%s);\n",functionName,functionName,paramsTypeString); String functionStr = ""; if (an.returnObject()){//返回对象 functionStr = String.format( " function %s(%s){\n" + " var retStr = method_%s.invoke(javaContext%s);\n" + " var ret = {} ;\n" + " eval('ret='+retStr);\n" + " return ret;\n" + " }\n" ,functionName,paramsNameString,functionName,paramsNameInvokeString ); }else {//非返回对象 functionStr = String.format( " function %s(%s){\n" + " %s method_%s.invoke(javaContext%s);\n" + " }\n",functionName,paramsNameString,returnStr,functionName,paramsNameInvokeString ); } funcStr = funcStr + methodStr + functionStr; } return funcStr;}
js自动生成的完整封装
public class JSEngine { private Class clazz; private String allFunctions ="";//js方法语句 public JSEngine(){ this.clazz = JSEngine.class; allFunctions = String.format(getAllFunctions(), clazz.getName());//生成js语法 } class TestObject{ private String name; private String address; TestObject(String name ,String address){ this.name = name; this.address = address; } } /** * 本地方法 - 返回本地类对象 * @param keyStr * @return */ @JSAnnotation(returnObject = true) public String getObjectValue(Object keyStr){ System.out.println("JSEngine output - getObjectValue : " + keyStr.toString()); return new Gson().toJson(new TestObject("小明","广州")); } /** * 本地java方法 * @param keyStr * @param o */ @JSAnnotation public void setValue(Object keyStr, Object o) { System.out.println("JSEngine output - setValue : " + keyStr.toString() + " ------> " + o.toString()); } /** * 本地java * @param keyStr * @return */ @JSAnnotation public String getValue(String keyStr) { System.out.println("JSEngine output - getValue : " + keyStr.toString() ); return "获取到值了"; } /** * 执行JS * @param js js执行代码 eg: "var v1 = getValue('Ta');setValue(‘key’,v1);" */ public void runScript(String js){ String runJSStr = allFunctions + "\n" + js;//运行js = allFunctions + js org.mozilla.javascript.Context rhino = org.mozilla.javascript.Context.enter(); rhino.setOptimizationLevel(-1); try { Scriptable scope = rhino.initStandardObjects(); ScriptableObject.putProperty(scope, "javaContext", org.mozilla.javascript.Context.javaToJS(this, scope));//配置属性 javaContext:当前类JSEngine的上下文 ScriptableObject.putProperty(scope, "javaLoader", org.mozilla.javascript.Context.javaToJS(clazz.getClassLoader(), scope));//配置属性 javaLoader:当前类的JSEngine的类加载器 rhino.evaluateString(scope, runJSStr, clazz.getSimpleName(), 1, null); } finally { org.mozilla.javascript.Context.exit(); } } /** * 通过注解自动生成js方法语句 */ private String getAllFunctions(){ String funcStr = " var ScriptAPI = java.lang.Class.forName(\"%s\", true, javaLoader);\n" ; Class cls = this.getClass(); for (Method method: cls.getDeclaredMethods()){ JSAnnotation an = method.getAnnotation(JSAnnotation.class); if (an == null ) continue; String functionName = method.getName(); String paramsTypeString ="";//获取function的参数类型 String paramsNameString = "";//获取function的参数名称 String paramsNameInvokeString = ""; Class [] parmTypeArray = method.getParameterTypes(); if (parmTypeArray != null && parmTypeArray.length > 0){ String[] parmStrArray = new String[parmTypeArray.length]; String[] parmNameArray = new String[parmTypeArray.length]; for (int i=0;i < parmTypeArray.length; i++){ parmStrArray[i] = parmTypeArray[i].getName(); parmNameArray[i] = "param" + i ; } paramsTypeString = String.format(",[%s]", TextUtils.join(",",parmStrArray)); paramsNameString = TextUtils.join(",",parmNameArray); paramsNameInvokeString = "," + paramsNameString; } Class returnType = method.getReturnType(); String returnStr = returnType.getSimpleName().equals("void") ? "" : "return";//是否有返回值 String methodStr = String.format(" var method_%s = ScriptAPI.getMethod(\"%s\"%s);\n",functionName,functionName,paramsTypeString); String functionStr = ""; if (an.returnObject()){//返回对象 functionStr = String.format( " function %s(%s){\n" + " var retStr = method_%s.invoke(javaContext%s);\n" + " var ret = {} ;\n" + " eval('ret='+retStr);\n" + " return ret;\n" + " }\n" ,functionName,paramsNameString,functionName,paramsNameInvokeString ); }else {//非返回对象 functionStr = String.format( " function %s(%s){\n" + " %s method_%s.invoke(javaContext%s);\n" + " }\n",functionName,paramsNameString,returnStr,functionName,paramsNameInvokeString ); } funcStr = funcStr + methodStr + functionStr; } return funcStr; } /** * 注解 */ @Target(value = ElementType.METHOD) @Retention(value = RetentionPolicy.RUNTIME) public @interface JSAnnotation { boolean returnObject() default false;//是否返回对象,默认为false 不返回 }}
- 补充:运行在子线程
AsyncTask task = new AsyncTask() { @Override protected Object doInBackground(Object[] params) { if (ukjsEngine == null) ukjsEngine = new UKJSEngine(ukjsEngineListener); ukjsEngine.runScript(jsStr); return null; }};task.execute();
则引擎初始化与执行 runScript 需要都在子线程中;如果引擎初始化在主线程,而runScript在子线程,则会报错
- ConsString 问题
var data = { "entityId": "2c63b681-1de9-41b7-9f98-4cf26fd37ef1", "recId": id, "needPower": 0 };var result = app.request('api/dyxxxxxity/dxxxxl', 'post', data, {});
在本地的request() 方法中,对data进行转化处理
mArgs = new Gson().toJson(data );
如下图,可以看到数据是正常的
但在下面实例中:
var productseries = app.getValue('xilie'); var data = { "ProductsetId":productseries, "Direction":"DOWNER" }; var result = app.request('api/prxxxxts/gexxxxes', 'post', data, {});
可以看到本地获取到 ProductsetId 参数类型为 ConsString,从而导致
在进行 Gson().toJson(data ) 操作获得的结果有问题
/** * 参数调整: * 存在问题:从js传入的JSON 对象,类型变为 NativeObject;而NativeObject 中的String类型可能被js转为 * ConsString 类型;用 Gson.toJson(xxx) 处理带有ConsString 类型的数据会出现异常。其中的ConsString * 类型的数据转化出来并不是 String 类型,而是一个特殊对象。 * 解决方案:遍历 NativeObject 对象,将其中的 ConsString 类型的数据转为 String 类型 * @param input * @return */ public static Object argsNativeObjectAdjust(Object input) { if (input instanceof NativeObject){ JSONObject bodyJson = new JSONObject(); NativeObject nativeBody = (NativeObject) input; for (Object key : nativeBody.keySet()){ Object value = nativeBody.get(key); value = argsNativeObjectAdjust(value); try { bodyJson.put((String) key,value); } catch (JSONException e) { e.printStackTrace(); } } return bodyJson; } if (input instanceof NativeArray){ JSONArray jsonArray = new JSONArray(); NativeArray nativeArray = (NativeArray) input; for (int i = 0; i < nativeArray.size() ; i++){ Object value = nativeArray.get(i); value = argsNativeObjectAdjust(value); jsonArray.put(value); } return jsonArray; } if (input instanceof ConsString){ return input.toString(); } return input; }
参考资料
- Rhino官网
https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino#Rhino_downloads - Rhino 使 JavaScript 应用程序更灵动
https://www.ibm.com/developerworks/cn/java/j-lo-rhino/ 【Android】不使用WebView来执行Javascript脚本(Rhino)本文也主要是参考该文章
http://www.cnblogs.com/over140/p/3389974.htmlRhino 文档
https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino/DocumentationRhino API
http://mozilla.github.io/rhino/javadoc/index.html使用 Rhino 作为 Java 的 JSON 解析/转换包
- Android JS解析引擎 Rhino 使用笔记(不借助webview)
- 【Android】不使用WebView来执行Javascript脚本(Rhino)
- 【Android】不使用WebView来执行Javascript脚本(Rhino)
- android webview js不执行原因解析
- Rhino 使用笔记
- java调用javascript :js引擎rhino
- java中使用javascript Rhino 引擎
- Android中使用WebView与JS交互全解析
- Android中使用WebView与JS交互全解析
- Android中使用WebView与JS交互全解析
- Android中使用WebView与JS交互全解析
- Android WebView使用全面解析(加载网络资源、本地HTML,JS交互)
- Android WebView使用全面解析(加载网络资源、本地HTML,JS交互)
- WebView使用笔记 Android
- Android WebView 使用笔记
- Android WebView使用笔记
- Android webview使用 webview和js交互
- Android中WebView使用解析
- codeforces 816A Karen and Morning
- SSH 原理与运用:如何远程自动免密码登录
- 购物车
- C语言:一维数组数组
- HM代码中Z-order扫描和Raster扫描之间的地址映射问题
- Android JS解析引擎 Rhino 使用笔记(不借助webview)
- 周一日记
- Java_基础—字节流读写中文
- 快速提高 CSDN 访问量
- NYOJ 57 6174问题
- Monkey测试环境搭建---更新时间截止20170724可用
- 计算机网络---网络层
- HTML标签--表单
- Spring事务管理只对出现运行期异常进行回滚