Android JS解析引擎 Rhino 使用笔记(不借助webview)

编程入门 行业动态 更新时间:2024-10-07 10:18:17

Android JS解析<a href=https://www.elefans.com/category/jswz/34/1771263.html style=引擎 Rhino 使用笔记(不借助webview)"/>

Android JS解析引擎 Rhino 使用笔记(不借助webview)

在使用过程中有个需求是在不大改动移动端现有处理逻辑的基础上,通过后期配置来灵活更改本地的逻辑联系。最终选定的方案是借助Js,一开始想到用webview,但webview开销大。经查找,最终使用了 Rhino。
注:本文主要参考自【Android】不使用WebView来执行Javascript脚本(Rhino)


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 地址:
Rhino 官网地址 :


AndroidStudo 中导入Rhino

  1. 下载Rhino jar 包。 Rhino jar 包下载
  2. 将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 + jsorg.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{@Overrideprotected 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 : testKey
System.out: JSEngine output - setValue : setKey ------> 获取到值了

方法测试 - 返回本地类

  1. 添加本地测试类
class TestObject{private String name;private String address;TestObject(String name ,String address){this.name = name;this.address = address;}
}
  1. 为JSEngine添加方法 getObjectValue
public Object getObjectValue(Object keyStr){System.out.println("JSEngine output - getObjectValue : " + keyStr.toString());return new TestObject("小明","广州");
}
  1. 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";
  1. 修改执行语句,尝试获取 name属性的值
private String testjs = "var test = getObjectValue('objectKey');" +"setValue('testvalue',test.name);";
  1. 运行,发现无法获取到属性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 : objectKey
System.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方法,声明注解
@JSAnnotation
public void setValue(Object keyStr, Object o) {System.out.println("JSEngine output - setValue : " + keyStr.toString() + " ------> " + o.toString());
}//本地java方法,声明注解
@JSAnnotation
public 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*/@JSAnnotationpublic void setValue(Object keyStr, Object o) {System.out.println("JSEngine output - setValue : " + keyStr.toString() + " ------> " + o.toString());}/*** 本地java* @param keyStr* @return*/@JSAnnotationpublic 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 + jsorg.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() {@Overrideprotected 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官网
  • Rhino 使 JavaScript 应用程序更灵动
    /
  • 【Android】不使用WebView来执行Javascript脚本(Rhino)本文也主要是参考该文章
    .html

  • Rhino 文档

  • Rhino API
    .html

  • 使用 Rhino 作为 Java 的 JSON 解析/转换包

更多推荐

Android JS解析引擎 Rhino 使用笔记(不借助webview)

本文发布于:2024-02-14 01:21:10,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1761512.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:引擎   笔记   JS   Android   Rhino

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!