
I think many readers of the hub on android-development have heard that Java allows you to modify the dex of an already installed application in
Runtime via
ClassLoader . With this you can run the 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 scripting language interpreters that are written entirely in Java. So, Oracle, starting with version 6, includes javascript engine Rhino in the JVM. This happened due to the implementation of the JSR-223 specification, which declares support in Java scripting programming languages.
')
At the moment there are several embedded engines for such popular programming languages ​​as: Lua (Luaj), Python (Jython), Ruby (Jruby) and java-script (Rhino, ...). Each of them allows you to both run scripts and access functions written in Java.
As a demonstration of opportunities, I propose to implement a “development environment”. The link to the source will leave at the end of the article. In order not to overload the example, I’ll focus on Lua, although nothing prevents me from connecting all the engines at the same time and switching between them. Actual at the time of this writing, the version of JLua is available in mvnrepository:
org.luaj: luaj-jse: 3.0.1 .
Every self-respecting development environment should have a field for entering the script, a field for displaying the result and a button that allows you to perform your brainchild.
UI self-respecting development environment:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/scriptInput" android:layout_width="0dp" android:layout_height="0dp" android:gravity="top|start" android:hint="@string/write_script" android:inputType="textMultiLine" android:padding="4dp" android:textColor="#000000" app:layout_constraintBottom_toTopOf="@id/scriptOutput" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/scriptOutput" android:layout_width="0dp" android:layout_height="0dp" android:hint="@string/script_output" android:padding="4dp" android:textColor="#000000" app:layout_constraintBottom_toTopOf="@id/executeButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/scriptInput" /> <Button android:id="@+id/executeButton" android:layout_width="0dp" android:layout_height="48dp" android:text="@string/run_script" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
In order to execute a Lua script, we need to get a 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 to set the message output threads, 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 override the
Globals # STDOUT and
Globals # STDERR values.
Thus, now we just have to load our creak into the environment and execute it.
So 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-mentioned binding of Java classes. Make it easy using
CoerceJavaToLua :
globals.set("bubble", CoerceJavaToLua.coerce(Bubble(this))) ... private class Bubble(private val context: Context) {
The result I got is this:

Thus, on a small example, we considered the possibility of executing scripts inside a mobile application. An inquisitive reader can guess that scripts can be downloaded from assets, application resources, or from a server. What can be useful, for example, in games. Fortunately, luaj is compatible with one of the most popular gaming frameworks java - Libgdx. In general, the scope of application here is limited only by the imagination of the developer.
→
Sample sources→
Luaj→
Jython→
Jruby→
Rhino (
android wrapper )