最近在学习graalvm,发现有一个graaljs项目,项目中介绍可以让java与JavaScript做数据转换,比如JavaScript中可以使用java的数据类型与结构。突然想到之前遇到的一个问题,小程序中开发的代码和基础库的部分代码都是j2v8来执行的,其中的数据通信是通过bridge去做的,其实就是把数据结构都转换为字符串,这样就存在问题,比如Android这边的网络请求、音视频帧数据、文件流对外都是通过java封装的对象,无法直接在JavaScript中使用,只能是通过转换为base64来做,而且一个buffer数据基本需要两次转换,sdk转一次,基础库转一次,比较消耗性能。
如果JavaScript和java之间的结构互通,那是不是就解决这个问题了,理论上来说如果js引擎是通过java编写的解释器去执行,那确实是可以这样的。
GraalJs核心代码如下:
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("/Users/xxx/tmp/index.md"));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
Set<String> languages = context.getEngine().getLanguages().keySet();
for (String id : languages) {
context.initialize(id);
if (id.equals("js")) {
context.eval("js", "function main(data) { console.log(data.readLine()); }");
Value funMain = context.getBindings("js").getMember("main");
funMain.execute(br);
}
}
}
这里我们使用GraalVM的jdk是可以运行的,可以看到JavaScript中接收到的data其实是java的BufferedReader,执行之后就发现JavaScript中的data.readLine成功执行并打印了文件内容,WC,如果Android可以这样那就太强了。可惜,GraalJs必须使用GraalVM提供的jdk,也不好直接替换android中使用的jdk。。。
那有没有其他办法?对了,GraalVM是可以将java代码生成分享库的,比如.so,Android也正好可以加载so库。有没有可能这样搞。最终项目如下https://github.com/schizobulia/GraalJs-Android ,可以看到releases中tag为v0.0.8中有arrch64和armv7的so库
可惜拿到so库之后去Android中使用发现,这个so库不是ABI。麻了…
然后查了相关资料发现GraalVM目前也没有计划支持Android。
不过好在最后发现ChatGPT确实是个好东西,现在查资料基本都是先问问ChatGPT,上面项目中的GitHub Action配置文件也是ChatGPT写的。
参考资料:
Android 关于CPU类型的so文件兼容问题(ABI) - 掘金
一分钟搞明白Android的.so文件、ABI和CPU的关系-CSDN博客
那有没有可能从j2v8入手,比如node很多内置函数其实在v8中也是没有的,如果可以在v8中实现这些函数然后提供给JavaScript调用好像有可以,然后开始查node这部分功能的实现原理,最后看到一篇博客:Node.js的底层原理 - 掘金其中说到这个部分的实现是V8的自定义拓展实现的,好像确实可以。不过后来试了试,发现还是不行。
主要原因有:比如文件流、帧数据在Android端对外都是通过java封装的对象,v8的自定义扩展需要c++或者v8提供的语言来编写,依然存在语言之间数据结构转换的问题。
次要原因:实在不想看c++的代码
然后通过ChatGPT最终选择试试Rhino,因为他也是java写的,缺点是性能可能没没v8那么强,JavaScript中新的语法糖或者全局属性可能没那么快支持到(不过可以使用一些打包库和第三方静态分析库去优化)。
小程序中存在大量的数据交互,如果使用Rhino说不定会有更好的效果,以下为示例代码,可以看到JavaScript中可以使用java的数据结构,这得益于解释器是使用java写的,节省了不同语言之间数据结构的转换。
var System = java.lang.System;
System.out.println(myClass.add(1, 2));
function test(str) {
myClass.show(str);
}
通过下面代码的测试,发现读取一个53kb的文件从调用test方法到show方法的触发基本没消耗时间。而且根据这种方式其实也很节省内存,因为数据其实只保存在了jvm中,按j2v8的思路同一份数据需要在v8、jvm中各保存一份。
以下为完整代码:
package com.example.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.ui.theme.MyApplicationTheme
import org.mozilla.javascript.Context
import org.mozilla.javascript.Function
import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.ScriptableObject
import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.charset.Charset
class MainActivity : ComponentActivity() {
class MyFunctions {
fun add(a: Int, b: Int): Int {
return a + b
}
fun show(buffer: ByteArray) {
println("这是js传递过来的:")
println(System.currentTimeMillis())
println(buffer)
// val text = String(buffer)
// println("File Content: $text")
}
}
class JsEngine {
public val rhino: Context = Context.enter()
public val scope: Scriptable
init {
rhino.optimizationLevel = -1
scope = rhino.initStandardObjects()
}
fun evaluate(jsCode: String) {
try {
ScriptableObject.putProperty(scope,"myClass", Context.javaToJS(MyFunctions(), scope))
ScriptableObject.putProperty(scope, "javaContext", Context.javaToJS(this, scope))
ScriptableObject.putProperty(scope, "javaLoader", Context.javaToJS(JsEngine::class.java.classLoader, scope))
rhino.evaluateString(scope, jsCode, JsEngine::class.java.simpleName, 1, null)
} finally {
// Context.exit()
}
}
fun callFunction(functionName: String, vararg args: Any): Any? {
try {
val function = scope[functionName, scope] as Function
return function.call(rhino, scope, scope, args)
} finally {
// Context.exit()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Greeting("Android")
}
}
}
val inputStream = resources.assets.open("data.txt");
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
inputStream.close()
Thread {
val jsCode = """
var System = java.lang.System;
System.out.println(myClass.add(1, 2));
function test(str) {
myClass.show(str);
}
""".trimIndent()
val jsEngine = JsEngine()
jsEngine.evaluate(jsCode)
println(System.currentTimeMillis())
jsEngine.callFunction("test", buffer)
}.start()
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
MyApplicationTheme {
Greeting("Android")
}
}
最后:ChatGPT真👍🏻