Open pansong291 opened 1 month ago
@kkevsekk1
我尝试在 js 里调用安卓原生的 setOnLongClickListener
来绑定长按事件,发现会报错「不能将 null
转为 boolean
」,估计是 Rhino 在包装接口的时候没有处理返回值,我没试过 Rhino 里的 JavaAdapter
,不过感觉应该一样没有处理返回值。
同时 Rhino 还有一个无法解决的痛点,就是无法调用签名相似的 java 重载函数。比如以下代码:
public class TestObj {
public static void test(int i) {}
public static void test(long l) {}
}
用 js 调用的话大概像这样:
TestObj.test(1)
Rhino 无法判断究竟应该调用哪个函数,直接就报错不管了。
既然 js 调用 java 时无法精确确定签名,干脆就在 java 里自定义一个无重载的函数,再在 js 里加载外部的 class 或 jar 或 dex 文件,调用自定义的函数。
比如先写出以下 java 代码:
package com.my;
public class MyObj {
public static void testInt(int i) {
TestObj.test(i);
}
public static void testLong(long l) {
TestObj.test(l);
}
}
把它编译为 dex 文件,然后在脚本中加载它:
let javaClass
function loadJavaClass() {
if (!javaClass) {
const loader0 = new Packages.dalvik.system.DexClassLoader(files.path('./MyObj.dex'), '/storage/emulated/0/temp', null, context.getClassLoader())
javaClass = loader0.loadClass('com.my.MyObj')
console.log(javaClass, '加载成功')
}
return javaClass
}
再调用里面的函数:
loadJavaClass()
.getMethod('testInt', Packages.java.lang.Integer.TYPE)
.invoke(null, 1)
loadJavaClass()
.getMethod('testLong', Packages.java.lang.Long.TYPE)
.invoke(null, 1)
这样就能成功调用。
这里的绑定长按事件我也是使用这种方式自定义的函数来解决的。
能不能把这种方式集成到 Autox 软件里面呢?比如在 js 里面写 java 代码,然后 Autox 把其中的 java 代码编译为 dex 提供给 js 调用。
基于上述加载 dex 的原理,一个实现安卓原生长按事件的可能方案:
public interface CustomOnLongClickListener {
void onLongClick(View v, boolean[] consumed);
}
// 封装设置自定义长按监听器的方法
public static void setCustomOnLongClickListener(View view, final CustomOnLongClickListener listener) {
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
boolean[] consumed = new boolean[1];
if (listener != null) {
listener.onLongClick(v, consumed);
}
return consumed[0];
}
});
}
PS:我没有具体测试过这个 consumed
数组是否能在 js 中成功更新其元素的值,我都是在 java 中直接返回的 true
。
提供一个调用具体函数的 java 实现:
package com.my;
import java.lang.reflect.Array;
public class MethodInvoker {
public static Object invoke(Object obj, String method, String[] types, Object[] args) {
if (obj == null) throw new IllegalArgumentException("obj 不能为 null");
return invoke(obj.getClass(), obj.getClass().getCanonicalName(), method, types, obj, args);
}
public static Object invokeStatic(String className, String method, String[] types, Object[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName(className);
return invoke(clazz, className, method, types, null, args);
}
private static Object invoke(Class<?> clazz, String className, String method, String[] types, Object obj, Object[] args) {
try {
// 获取参数类型
Class<?>[] classTypes = new Class<?>[types.length];
for (int i = 0; i < types.length; i++) {
classTypes[i] = convertToType(types[i]);
}
// 转换参数
Object[] convertedArgs = new Object[types.length];
for (int i = 0; i < convertedArgs.length; i++) {
if (args[i] == null) {
convertedArgs[i] = null;
} else if (args[i] instanceof String) {
// 字符串到原始类型的转换
convertedArgs[i] = convertToPrimitiveType(classTypes[i], (String) args[i]);
} else {
// 其他直接当作正确的类型或数组使用
convertedArgs[i] = args[i];
}
}
return clazz.getMethod(method, classTypes).invoke(obj, convertedArgs);
} catch (Exception e) {
throw new RuntimeException("调用函数异常: "
+ className + "." + method + "(...)\n"
+ "注意,基础类型在传参的时候一律使用字符串表示。\n"
+ "例如,boolean 应该传 '' 或者 'false' 或者其他非空字符串。", e);
}
}
private static Class<?> convertToType(String typeName) {
// 处理数组类型
if (typeName.endsWith("[]")) {
String componentTypeName = typeName.substring(0, typeName.length() - 2);
Class<?> componentType = convertToType(componentTypeName);
return Array.newInstance(componentType, 0).getClass();
}
// 处理基本类型
switch (typeName) {
case "short":
return short.class;
case "byte":
return byte.class;
case "int":
return int.class;
case "long":
return long.class;
case "boolean":
return boolean.class;
case "float":
return float.class;
case "double":
return double.class;
case "char":
return char.class;
}
// 尝试加载类(处理普通类类型)
try {
return Class.forName(typeName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("无法识别的类型名称: " + typeName, e);
}
}
private static Object convertToPrimitiveType(Class<?> type, String str) {
if (type == String.class) {
return str;
} else if (type == boolean.class || type == Boolean.class) {
return !str.isEmpty() && !str.equals("false");
} else if (type == int.class || type == Integer.class) {
return Integer.parseInt(str);
} else if (type == long.class || type == Long.class) {
return Long.parseLong(str);
} else if (type == float.class || type == Float.class) {
return Float.parseFloat(str);
} else if (type == double.class || type == Double.class) {
return Double.parseDouble(str);
} else if (type == char.class || type == Character.class) {
return str.charAt(0);
} else {
throw new IllegalArgumentException("不支持的类型: " + type);
}
}
public static void main(String[] args) {
// 测试用例
try {
Class<?> cls1 = convertToType("java.lang.String");
System.out.println(cls1.getName()); // java.lang.String
Class<?> cls2 = convertToType("boolean[]");
System.out.println(cls2.getName()); // [I
Class<?> cls3 = convertToType("char");
System.out.println(cls3.getName()); // char
Class<?> cls4 = convertToType("int[][][][][][][][][][][][]");
System.out.println(cls4.getName()); // [[[[[[[[[[[[I
Class<?> cls5 = convertToType("java.lang.String[][]");
System.out.println(cls5.getName()); // [[Ljava.lang.String;
} catch (Exception e) {
e.printStackTrace();
}
}
}
软件内置的「按钮控件」示例代码:
在运行时,长按「点我」按钮会显示 "我被长按啦",松开后又会显示 "我被点啦" 。 可以看出
click
也被触发了,如果触发了长按事件,则不应该触发点按事件。