Harmony of scripts inside the Android application

Harmony of scripts inside the Android application

I think many readers of the hub on android-development have heard that Java allows you to run ClassLoader in runtime to modify the dex of an already installed application. With this you can run the compiled code in runtime and use it. But Google, to put it mildly, is not too loyal to such frauds 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”. 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. The current version of JLua at the time of this writing is available in mvnrepository: org.luaj: luaj-jse: 3.0.1 .
Each 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 child.

UI self-respecting development environment:

  & lt;? xml version = "1.0" encoding = "utf-8"? & gt;
 & lt; 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" & gt;

  & lt; 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"/& gt;

  & lt; 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"/& gt;

  & lt; 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"/& gt;

 & lt;/androidx.constraintlayout.widget.ConstraintLayout>

In order to execute a Lua script, we need to get a global environment in which it will run, 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 up message output streams, because java.lang.System.out , java.lang.System.err is used by default, which is not quite 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 we will try to expand the range 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) {

//called from lua
  fun show (message: String) {
  Toast.makeText (context, message, Toast.LENGTH_SHORT) .show ()

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
Rhino ( android wrapper )

Source text: Harmony of scripts inside the Android application