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

- 对 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

  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 + 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();         }     } }`

   



*   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

*   39

*   40

*   41

*   42

*   43

*   44

*   45

*   46

*   47

*   48

*   49

*   50

*   51

*   52

*   53

*   54

*   55

*   56

*   57

*   58

*   59

*   60

*   61





使用测试


`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)";   }`                           

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8

*   9

*   10

*   11

*   12

*   13

*   14





结果输出如下:


`System.out: JSEngine output - getValue : testKey System.out: JSEngine output - setValue : setKey ------> 获取到值了`

   



*   1

*   2






方法测试 - 返回本地类

  1. 添加本地测试类

`class TestObject{     private String name;     private String address;      TestObject(String name ,String address){         this.name = name;         this.address = address;     } }`

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8

*   9





  1. 为JSEngine添加方法 getObjectValue

`public Object getObjectValue(Object keyStr){     System.out.println("JSEngine output - getObjectValue : " + keyStr.toString());     return new TestObject("小明","广州"); }`

   



*   1

*   2

*   3

*   4





  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

*   2

*   3

*   4

*   5

*   6

*   7

*   8

*   9

*   10

*   11

*   12

*   13

*   14





  1. 修改执行语句,尝试获取 name属性的值

`private String testjs =                          "var test = getObjectValue('objectKey');" +                         "setValue('testvalue',test.name);";`

   



*   1

*   2

*   3





  1. 运行,发现无法获取到属性name的值

`System.out: JSEngine output - setValue : testvalue ------> undefined`

   



*   1





解决方案

思路:先将返回的对象转成字符串,再利用 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 }`

   



*   1

*   2

*   3

*   4

*   5






`//修改 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";`

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8





仍执行以下语句


`private String testjs =                          "var test = getObjectValue('objectKey');" +                         "setValue('testvalue',test.name);";`

   



*   1

*   2

*   3





输出如下:可以看到能使用 .name 的形式获取到 name属性的值


`System.out: JSEngine output - getObjectValue : objectKey System.out: JSEngine output - setValue : testvalue ------> 小明`

   



*   1

*   2





反射构建js语句

通过上面可以看到,在JSEngine中每添加一个方法,在JS语句中也要对应多添加一个方法。而在js语句的编写过程中则需要注意多处细节,比较容易书写错误,所以能否自动生成js语句而不用每次都手写呢?

有的,利用注解和反射。

首先观察前面的js语句

每个本地方法在js中的定义主要包括两部分:

1、通过本地方法的方法名来获得该方法


`var methodGetValue=  ScriptAPI.getMethod(\"getValue\", [java.lang.String]);\n`

   



*   1





2、自定义js方法(可重新命名),在js方法中调用本地方法的引用


`function getValue(key) {     return  methodGetValue.invoke(javaContext,key); }`

   



*   1

*   2

*   3





其他有差异的话则在于返回值类型为本地类对象时候的js方法的不同,如


`function getObjectValue(key) {        var retStr = methodGetObjectValue.invoke(javaContext,key);        var ret = {};        eval('ret='+retStr);        return ret;  }`

   



*   1

*   2

*   3

*   4

*   5

*   6





下面开始反射构建js语句

1、创建注解 JSAnnotation ,设定参数 returnObject ,用于区分上面所述的方法是否返回本地类对象。


`/**  * 注解  */ @Target(value = ElementType.METHOD) @Retention(value = RetentionPolicy.RUNTIME) public @interface JSAnnotation {     boolean returnObject() default false;//是否返回对象,默认为false 不返回 }`

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8





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; }`

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8

*   9

*   10

*   11

*   12

*   13

*   14

*   15

*   16

*   17

*   18

*   19

*   20

*   21





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; }`

   



*   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

*   39

*   40

*   41

*   42

*   43

*   44

*   45

*   46

*   47

*   48

*   49

*   50






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 不返回     }  }`

   



*   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

*   39

*   40

*   41

*   42

*   43

*   44

*   45

*   46

*   47

*   48

*   49

*   50

*   51

*   52

*   53

*   54

*   55

*   56

*   57

*   58

*   59

*   60

*   61

*   62

*   63

*   64

*   65

*   66

*   67

*   68

*   69

*   70

*   71

*   72

*   73

*   74

*   75

*   76

*   77

*   78

*   79

*   80

*   81

*   82

*   83

*   84

*   85

*   86

*   87

*   88

*   89

*   90

*   91

*   92

*   93

*   94

*   95

*   96

*   97

*   98

*   99

*   100

*   101

*   102

*   103

*   104

*   105

*   106

*   107

*   108

*   109

*   110

*   111

*   112

*   113

*   114

*   115

*   116

*   117

*   118

*   119

*   120

*   121

*   122

*   123

*   124

*   125

*   126

*   127

*   128

*   129

*   130

*   131

*   132

*   133






  • 补充:运行在子线程

`AsyncTask task = new AsyncTask() {     @Override     protected Object doInBackground(Object[] params) {         if (ukjsEngine == null) ukjsEngine = new UKJSEngine(ukjsEngineListener);         ukjsEngine.runScript(jsStr);         return null;     } }; task.execute();`

   



*   1

*   2

*   3

*   4

*   5

*   6

*   7

*   8
DuktapeJava 是针对 Android 平台封装的 Duktape  JavaScript 引擎;实现Javascript 和 Java的无缝调用。 初始化DuktapeEngnine   @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); duktapeEngine = new DuktapeEngine(); duktapeEngine.put("activity",this); duktapeEngine.execute(AssetScript.toScript(getBaseContext(), "duk.js")); duktapeEngine.call("activityListener", "onCreate", savedInstanceState); } @Override protected void onDestroy() { if (duktapeEngine != null) { duktapeEngine.destory(); duktapeEngine = null; } super.onDestroy(); }   duk.js javascript 代码示例   importClass("com.furture.react.R") importClass("android.view.View.OnClickListener") importClass("android.widget.Toast") importClass("java.lang.Runnable") var activityListener = {}; activityListener.onCreate = function(){ print("activity onCreate onJavaScript"); activity.setContentView(R.layout.activity_duk) button1 = activity.findViewById(R.id.button1); button1.setOnClickListener(new OnClickListener(function(){ Toast.makeText(activity, "Button1 Clicked", Toast.LENGTH_SHORT).show(); var intent = new Intent(activity, "com.furture.react.activity.DetailActivity"); activity.startActivity(intent); })); button2 = activity.findViewById(R.id.button2); button2.setOnClickListener(new OnClickListener({ onClick:function(){ Toast.makeText(activity, "Button2 Clicked", Toast.LENGTH_SHORT).show(); } })); } activityListener.onStart = function(){ print("activity onStart"); } activityListener.onResume = function(){ print("activity onResume"); } activityListener.onPause = function(){ print("activity onPause"); } activityListener.onStop = function(){ print("activity onStop"); } activityListener.finish = function(){ print("activity finish" num); }   Java 和 Javascript 的无缝调用 javascript 调用java方法示例: importClass
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值