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

基本使用参考 【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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

前端CSS面试题文档,JavaScript面试题文档,Vue面试题文档,大厂面试题文档,需要的读者可以戳这里获取!

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

  • 50

  • 51

  • 52

  • 53

  • 54

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ZEz4eceH-1712864914302)]

[外链图片转存中…(img-2FYW2a9R-1712864914302)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-5jBX1mcH-1712864914303)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

前端CSS面试题文档,JavaScript面试题文档,Vue面试题文档,大厂面试题文档,需要的读者可以戳这里获取!

[外链图片转存中…(img-UOgpcBBH-1712864914303)]

[外链图片转存中…(img-6tKBFVYR-1712864914303)]

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、付费专栏及课程。

余额充值