RubyMotion Runtime Guide for Android
The RubyMotion runtimes implement the Ruby language functionality required during the execution of an application. The object model, built-in classes, and memory management system are part of the runtime.
RubyMotion comes with a runtime for Android, based on the Android runtime (Dalvik or ART) and core Java classes.
Although similar in appearance, the RubyMotion Android runtime has completely different implementations than CRuby, the mainstream implementation (which is also called MRI, or simply Ruby). We will cover the main differences in the following sections.
The key feature of the RubyMotion Android runtime is its tight integration with the Java virtual machine, which makes it suitable for use on power efficient devices as well as for writing performance-intensive code.
RubyMotion follows the Ruby 1.9 language specifications.
1. Object Model
The RubyMotion object model is based on Java, the de-facto programming language of Android, Google’s mobile platform.
While Java has certainly less similarities with Ruby than Objective-C, they are still close enough to make such an integration possible.
More specifically, RubyMotion re-implements the Ruby language on top of the Java Native Interface (JNI) of the Android runtime.
In RubyMotion, all objects are Java objects, all Java classes and methods are available in Ruby, and some Ruby classes and methods are available to Java. This unified approach allows us to interface with the native Java APIs at no extra performance cost.
1.1. Classes
All Java classes are natively exposed to Ruby. However, the way to access them differs from the syntax you would use in Java.
Java features the notion of packages, which is a way to group a set of classes under a component that can be either accessed directly or included.
As an example, java.lang.Object
represents a path where the Object
class is defined under the java.lang
package. Java classes are often described using their full package path.
In RubyMotion, Java packages are accessed as if they were defined as a chain of Ruby constants, where each part of the package starts with an upper-case character.
In Ruby, the java.lang.Object
path can be accessed using Java::Lang::Object
.
obj = Java::Lang::Object.new
Java classes which are not defined as final can be subclassed. As an example, the public non-final android.app.Activity
class is available as Android::App::Activity
in Ruby, and can be subclasses as following.
class MyActivity < Android::App::Activity
# ...
end
Note
|
At the time of this writing, packages cannot be included in Ruby, so you have to use full paths. |
1.2. Primitive Java Types
Like Objective-C, Java comes with a set of primitive / builtin data types. Java methods make use of these types, and it is important to describe how they are interfaced with Ruby code.
Java Type | From Ruby to Java | From Java to Ruby |
---|---|---|
|
If the object is |
Either |
|
If the object is a |
Either a |
|
||
|
||
|
||
|
||
|
If the object is a |
A |
|
1.3. Builtin Ruby Classes
Some of the built-in classes of RubyMotion are based on the Java Class Library, the core classes of the Android SDK.
Java comes with a class called java.lang.Object
, which is the root class of all Java classes.
Because of RubyMotion’s unified runtime approach, all Ruby classes are also based on java.lang.Object
. The Object
constant point to that class as well.
Here is a table showing the base class of a newly created class, Hello
, as well as some of the Ruby built-in classes.
Ruby Class | Base |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The main purpose of this design is to allow the exchange of primitive types between Java and Ruby at no performance cost, since they don’t have to be converted. This is important as most of the types that will be exchanged in a typical application are likely going to be built-in types.
A String object created in Ruby can have its object reference passed as the argument of an Java method that expects an object that implements the java.lang.CharSequence
interface, such as the setText
method of android.widget.TextView
class.
textView.text = "foo"
1.4. Methods
Java methods are natively exposed to Ruby, and can be accessed as if they have been defined as Ruby methods.
As an example, the java.lang.Object
class implements the toString()
method.
String toString()
That method can be called on all objects in Ruby.
42.toString #=> "42"
true.toString #=> "true"
textView.toString #=> "android.widget.TextView{3607..."
When you are subclassing a Java class in Ruby, you can override any of its methods.
As an example, the android.app.Activity
class implements the onCreate()
method, which is meant to be overridden by subclasses.
void onCreate (Bundle savedInstanceState)
We can do that naturally from Ruby.
class MyActivity < Android::App::Activity
def onCreate(saved_instance)
super
# ...
end
end
Note that we use the super
Ruby language keyword to call the super class implementation, defined in Java.
1.5. Method Shortcuts
The RubyMotion runtime provides convenience shortcuts for certain Java methods.
Method | Shortcut |
---|---|
|
|
|
|
|
|
As an example, the getText
and setText
methods of android.widget.TextView
can be called using the text
and text=
shortcuts.
view.text # instead of view.getText
view.text = "foo" # instead of view.setText("foo")
2. Interfacing with Java
2.1. Interfaces
Java interfaces are an important concept to understand when programming Android apps with RubyMotion.
In Java, an interface is basically a list of method declarations. A class is said to implement an interface when it provides definitions for all the methods in the interface.
Note
|
Java interfaces are similar to protocols in Objective-C. |
In RubyMotion, you can implement an interface in a class just by defining the required methods. There is no need to specify the name of the interface, the compiler will determine that for you automatically.
As an example, the setOnClickListener
method of android.view.View
expects an object that implements the android.view.View.OnClickListener
interface, which features only one method, onClick
.
abstract void onClick(View v)
In order to implement that interface in Ruby, you just need to provide an implementation for that method in a class.
class MyButtonListener
def onClick(view)
# ...
end
end
# ...
button.onClickListener = MyButtonListener.new
2.2. Methods Overloading
Java features the concept of method overloading, which does not exist in Ruby.
In Java, it is possible to define the same method name multiple times but with different argument types. The compiler will then identify which method has to be called.
As an example, the android.widget.TextView
Java class features the following setText
methods:
void setText(int resid)
void setText(char[] text, int start, int len)
void setText(int resid, TextView.BufferType type)
void setText(CharSequence text)
void setText(CharSequence text, TextView.BufferType type)
As you can see, each of these methods have the same name (and return type) but have different arguments.
Because Ruby is dynamically-typed, overloaded methods have to be resolved at runtime and not at compile time.
The setText(int resid) method will be called if you pass a Fixnum to it. The setText(CharSequence text) method will be called if you provide an object that implements the CharSequence
interface (such as a string).
textView.text = 42 #=> calls setText(int)
textView.text = "foo" #=> calls setText(CharSequence)
In case the runtime cannot figure out which overloaded method to call during a method dispatch, a NoMethodError
exception will be raised with an appropriate message.
2.3. Fields
Java fields are variables attached to a class. There are two types of Java fields:
-
Instance fields; these are similar to instance variables in Ruby.
-
Static fields; these are similar to class variables in Ruby.
In the Android SDK, constants are usually static Java fields, and can be retrieved exactly as if they were constants in Ruby.
For example, the android/widget/LinearLayout
class has a few constants.
public static final int HORIZONTAL
public static final int VERTICAL
...
These constants can be naturally retrieved in Ruby.
layout = Android::Widget::LinearLayout.new(self)
layout.orientation = Android::Widget::LinearLayout::VERTICAL
Instance Java fields can be retrieved in Ruby as if they were defined using attr_accessor
.
2.4. Annotations
Java annotations are special objects that can be applied to methods (and other types).
An example is the WebKit framework of Android, which requires methods to be exposed to JavaScript to be properly annotated, for security reasons.
This can be done in RubyMotion using the annotation
special method right before the definition of a method. The annotation will be retrieved at compilation time.
class DemoJavaScriptInterface
__annotation__("@android.webkit.JavascriptInterface")
def methodFromJavaScript
end
end
Note
|
At the time of this writing, only methods can be annotated in RubyMotion. |
3. Memory Management
RubyMotion relies on the Dalvik or ART garbage collector to allocate and dispose of unused memory.
From a Ruby developer perspective, you do not have to think about memory management at all.
Internally, RubyMotion makes use of local references for local variables and global references for everything else. The destruction of local references is determined at compilation time.
There are two important things you might need to consider:
-
The garbage collector is multithreaded. Collections might happen on other threads. If you override the
finalize
method ofjava.lang.Object
in your code, make sure it’s properly re-entrant. -
The garbage collector is able to detect and handle cyclic references. Unlike RubyMotion for iOS and OS X, you do not need to create
WeakRef
objects.
4. Concurrency
RubyMotion has been designed for concurrency in mind.
RubyMotion has the concept of virtual machine objects, which wrap the state of a thread of execution. A piece of code is running through a virtual machine.
Virtual machines don’t have locks and there can be multiple virtual machines running at the same time, concurrently.
Caution
|
Unlike the mainstream Ruby implementation, race conditions are possible in RubyMotion, since there is no Global Interpreter Lock (GIL) to prohibit threads from running concurrently. You must be careful to secure concurrent access to shared resources. |
The Thread
class is available for concurrency patterns. It is a direct subclass of java.lang.Thread
.