Harmony of scripts inside Android application
- Tutorial
I think many readers of the android development hub have heard that Java allows you to modify the dex of an already installed application in runtime via ClassLoader . Using this, you can load compiled code in runtime and use it. But Google treats such frauds, to put it mildly, not too loyally and bans those caught in such an application.
However, there are alternative ways to download and execute scripts on a mobile device. For details under the cat!
So, although we cannot update dex applications in runtime, we can use interpreters of scripting languages that are written entirely in Java. So Oracle, starting with version 6, includes the jhascript engine Rhino in the JVM. This happened thanks to the implementation of the JSR-223 specification, which declares Java support for scripting programming languages.
Currently, there are several built-in engines for such popular programming languages as: Lua (Luaj), Python (Jython), Ruby (Jruby) and java-script (Rhino, ...). Each of them allows you to both execute scripts and access functions written in Java.
As a demonstration of the possibilities, I propose to implement a development “environment”. I'll leave a link to the sources at the end of the article. In order not to clutter up the example, I will focus on Lua, although there is nothing stopping you from connecting all the engines at the same time and switching between them. The current version of JLua at the time of writing is available in mvnrepository: org.luaj: luaj-jse: 3.0.1 .
Each self-respecting development environment should have a field for entering a script, a field for displaying the result, and a button that allows you to complete your brainchild.
UI of a self-respecting development environment:
In order to execute the Lua script, we need to get the global environment in which it will be executed - Globals . Luaj allows you to customize it, for example, by setting variables or adding binders to Java classes. An important opportunity for us here will be setting message output streams, because java.lang.System.out , java.lang.System.err is used by default , which is not very convenient when you need to display the result of execution in TextView. To change this you need to redefine the values Globals # STDOUT and Globals # STDERR .
Thus, now we only have to load our creak into the environment and execute it.
This is what it looks like in my example:
private fun runLua(script: String) {
val charset = StandardCharsets.UTF_8
val globals = JsePlatform.standardGlobals()
val outStream = ByteArrayOutputStream()
val outPrintStream = PrintStream(outStream, true, charset.name())
globals.STDOUT = outPrintStream
globals.STDERR = outPrintStream
try {
globals.load(script).call()
scriptOutput.setTextColor(Color.BLACK)
scriptOutput.text = String(outStream.toByteArray(), charset)
} catch (e: LuaError) {
scriptOutput.setTextColor(Color.RED)
scriptOutput.text = e.message
} finally {
outPrintStream.close()
}
}
Now let's try to expand the set of available functions with the ability to show Toast using the above binding of Java classes. Make it easy using CoerceJavaToLua :
globals.set("bubble", CoerceJavaToLua.coerce(Bubble(this)))
...
private class Bubble(private val context: Context) {
// called from lua
fun show(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
The result I got is this:
Thus, with a small example, we examined the possibility of running scripts inside a mobile application. An inquisitive reader can guess that scripts can be downloaded from assets, application resources, or from the server. What can be useful, for example, in games. Fortunately, luaj is compatible with one of the most popular game java frameworks - Libgdx. In general, the scope here is limited only by the imagination of the developer.
→ Sample sources
→ Luaj
→ Jython
→ Jruby
→ Rhino ( android wrapper )