Meoo

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

在使用过程中有个需求是在不大改动移动端现有处理逻辑的基础上,通过后期配置来灵活更改本地的逻辑联系。最终选定的方案是借助Js,一开始想到用webview,但webview开销大。经查找,最终使用了Rhino。

在使用过程中有个需求是在不大改动移动端现有处理逻辑的基础上,通过后期配置来灵活更改本地的逻辑联系。最终选定的方案是借助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 : testKey

System.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() + "",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 : 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",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,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",paramsNameString,paramsNameInvokeString );

}else {//非返回对象

functionStr = String.format(

" function %s(%s){n" +

" %s method_%s.invoke(javaContext%s);n" +

" }n",returnStr,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,null);

} finally {

org.mozilla.javascript.Context.exit();

}

}

/**

* 通过注解自动生成js方法语句

*/

private String getAllFunctions(){

String funcStr = " var ScriptAPI = java.lang.Class.forName("%s",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 );

如下图,可以看到数据是正常的


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

但在下面实例中:

var productseries = app.getValue('xilie');

var data = {

"ProductsetId":productseries,

"Direction":"DOWNER"

};

var result = app.request('api/prxxxxts/gexxxxes',{});

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

可以看到本地获取到 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.html

Rhino 文档


https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino/Documentation

Rhino API


http://mozilla.github.io/rhino/javadoc/index.html

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

阅读全文

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文已收录于以下专栏:

相关文章推荐

【Android】不使用WebView来执行Javascript脚本(Rhino)

【Android】不使用WebView来执行Javascript脚本(Rhino)

 

 

前言

动态执行脚本能有效的降低重要功能硬编码带来的问题,尤其是依赖于第三方的应用,可以通过动态脚...

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

lltaoyy

2016年06月12日 10:30

1675

整理android逆向工程师技能表 by非虫from看雪

1.       密码学基础

1.1.    古典密码学基础

1.2.    现代密码学基础

1.2.1.      哈希算法

1.2.2.      非对称加密与解密

1.2.3.      椭圆曲...

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

p2011211616

2017年08月20日 15:33

255

在Android上使用Google V8 JS 引擎

在Android上使用Google V8 JS 引擎

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

absurd

2015年08月15日 06:59

7143

如何在iOS和Android上选择一个JavaScript 引擎进行应用开发

在我开始使用OpenAphid-Engine的时候,已经有几种类似的iOS/Android

项目.这些商业项目或者开源项目使用JavaScript实现代码特性。比如,Titanium 和Phone...

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

Kaitiren

2014年04月09日 11:01

7192

采用Rhino在JAVA中运行JavaScript

由于有些网页采用的是swing技术实现(用swing也可以做出好看的界面),在其中使用JavaScript的话可以采用Rhino

对于Rhino的介绍和使用可以参看这篇文章——Rhino 使 Jav...

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

fengshuiyue

2017年05月15日 16:07

953

Delphi7高级应用开发随书源码

2003年04月30日 00:00

676KB

下载

Rhino详解:Java与JS互操作

深入浅出Rhino:Java与JS互操作

什么是Rhino?

Rhino 是JavaScript 的一种基于Java的实现,原先由Mozilla开发,现在被集成进入JDK 6.0。下面这两行代码...

gladmustang

2014年11月30日 13:20

1978

JavaScript引擎速度比较: rhino 与 V8

java 下的rhino 和 C++ 下的 V8 都是非常出色的开源的JavaScript引擎。

最近本人因为工作需要对这两个引擎进行了一些研究,发现两个的一些不同。

1.使用难以程度

    ...

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

a137993530

2015年05月13日 19:54

1199

Android&Java解析JS

private void parseHtml() throws Exception { new Thread(new Runnable() {

@Override...

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

u010838785

2017年03月16日 16:23

740

Android之JSON封装与Javascript解析JSON

转载请注明出处:http://blog.csdn.net/catchingsun/article/details/49182019

Android JSON封装:

JSON解析类可以分为以下几个类...

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

catchingSun

2015年10月16日 18:55

1103

0

上一篇:

:下一篇

精彩评论

暂无评论...
验证码 换一张
取 消

热门标签