Tuesday, September 28, 2010

Dynamic Languages on Android - Talk at AndroidOnly 2010

The following are notes from my talk at Android Only, Stockholm September 29-30, 2010.

Dynamic Languages on Android

Mikael Kindborg
MoSync AB
mikael.kindborg@mosync.com
http://divineprogrammer.se

Early bound vs. Late bound

When you
                                learn this...
----------------------------------------!------>
            ^
Can you go back
and change this?

The answer is "Yes", if you late bind...

Outline of talk

Dynamic languages on Android
JavaScript in Rhino
DroidScript
JavaScript in WebKit
MobileLua and MoSync
Hobbyist programming - making app development easy & fun

Why use dynamic Languages?

Incremental development - no build process
Less code, less syntactic noise
Dynamic typing and polymorphism - refactoring friendly
Higher level programming constructs

Dynamic languages on Android

Scripting Layer for Android (SL4A) - Python, Lua, JavaScript, Ruby, Perl, BeanShell, ...
Interpreter embedded in a native Java app - JavaScript (Rhino), JRuby, BeanShell, Kawa, Clojure, ...
JavaScript running in WebKit
Stand-alone authoring tool/engine - Flash, Corona, AppInventor, Squeak, MobileLua, ...

Mozilla Rhino

JavaScript system written in Java
Has both interpreter/runtime engine and bytecode compiler
Mature project
Moderate footprint (DroidScript is around 360KB)
Good performance

DroidScript

JavaScript on Android
Micke's project for learning Android
Experimental open-source project (MIT)
Based on Mozilla Rhino JavaScript engine
Full access to the Android API (minus subclassing)
Remote browser-based editor built with CodeMirror
Goal: Interactive programming, developing Android applications without a heavyweight IDE

Code example - display a Toast

var Toast = Packages.android.widget.Toast;
Toast.makeText(
    Activity,
    "Hello World!", 
    Toast.LENGTH_SHORT).show();

Ways DroidScript can be used

Write code in the on-device editor (works crappy).
Load/Run JavaScript files from the SD card or over the Internet.
Load/Run JavaScript code embedded on a web page within DROIDSCRIPT_BEGIN and DROIDSCRIPT_END tags - this means that programs can be published on a blog page, for example.
Do remote programming via the built-in web server that accepts PUT requests with JavaScript code.
Build a native app that embeds JavaScript files as assets.

JavaScript advantages

Late binding
Reduced syntactic noise
High-level language constructs
Closures
Compact event callback functions
Flexible object model (prototypes)
More of a language for everyone compared to Java
JavaScript is the new 'machine language'; old and new languages compile to JavaScript, e.g. CoffeeScript

JavaScript drawbacks

Poor tool support (editor, debugger)
Almost no static code checking - lots of runtime errors
No dynamic byte code generation

Scripting Java from Rhino - what works and what doesn't

Can use Java packages
Can instantiate Java classes
Can call methods and access instance variables
Data types are automatically converted between Java and JavaScript
Can instantiate interfaces that have one method (similar to an anonymous inner class in Java, but shorther syntax)
Must code a bit to implement multi-method interfaces
Cannot generate Dalvik byte code on the fly
Cannot subclass
Must run in interpreted mode
Android resource XML is early bound; some classes need resource ids, e.g. ArrayAdapter, means you cannot dynamically specify views as list items.

Code example - making a clickable button

// Short names for packages.
var widget = Packages.android.widget;
var graphics = Packages.android.graphics;

// onCreate is called when the script has been loaded.
function onCreate(bundle)
{
    var font = graphics.Typeface.create(
        graphics.Typeface.SANS_SERIF, 
        graphics.Typeface.BOLD);

    // Activity is a global variable that refers
    // to the Android activity.
    var button = new widget.Button(Activity);
    button.setText("Hello World!");
    button.setTypeface(font);
    button.setTextSize(26);
    button.setBackgroundColor(graphics.Color.rgb(0, 0, 64));
    button.setTextColor(graphics.Color.rgb(255, 255, 255));
    
    // One method interface implemented as a function.
    button.setOnClickListener(function() { 
        button.setText("You Clicked Me!"); });
        
    Activity.setContentView(button);
}

Refering to packages and classes

// Packages is a global variable that contains
// all packages and classes."),

// Write out full package path.
var button = new Packages.android.widget.Button(Activity);"),

// Use variable to refer to a package.
var widget = Packages.android.widget;
var button = new widget.Button(Activity);

// Use variable to refer to a class.
var Button = Packages.android.widget.Button;
var button = new Button(Activity);

Interfaces, closures, and polymorphism

// You implement a single method interface as a function.
// Example interface: View.OnClickListener.onClick(View v)

// Variable button is contained in the function closure.
// Method parameters need not to be spelled out.
button.setOnClickListener(function() { 
    button.setText("You Clicked Me!"); });

// Using the supplied parameter, note the use of
// dynamic typing and polymorphism.
button.setOnClickListener(function(view) { 
    view.setText("You Clicked Me!"); });

// First example in Java, variable button must be final.
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View view) {
        button.setText("You Clicked Me!"); } });

// Second example in Java, you must type cast.
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View view) {
        ((Button)view).setText("You Clicked Me!"); } });

Code example - a counting button

var widget = Packages.android.widget;
var graphics = Packages.android.graphics;

function onCreate(bundle)
{
    var font = graphics.Typeface.create(
        graphics.Typeface.SANS_SERIF, 
        graphics.Typeface.BOLD);
    var button = new widget.Button(Activity);
    button.setText("Hello World!");
    button.setTypeface(font);
    button.setTextSize(26);
    button.setBackgroundColor(graphics.Color.rgb(0, 0, 64));
    button.setTextColor(graphics.Color.rgb(255, 255, 255));

    var counter = 0;
    button.setOnClickListener(function() {
        counter++;
        button.setText("Click count: " + counter); });
    
    Activity.setContentView(button);
}

How to implement multi-method interfaces

// Program that displays images from the camera.

var Camera = Packages.android.hardware.Camera;
var SurfaceHolder = Packages.android.view.SurfaceHolder;
var SurfaceView = Packages.android.view.SurfaceView;
var Window = Packages.android.view.Window;

function onCreate(bundle)
{
    Activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
    var preview = createPreviewSurface();
    Activity.setContentView(preview.getSurfaceView());
}

function createPreviewSurface()
{
    var camera = null;
    var surface = new SurfaceView(Activity);

    // JavaScript object that implements the interface
    // android.view.SurfaceHolder.Callback
    var object = {
        getSurfaceView: function() {
            return surface; },
        surfaceCreated: function(holder) {
            camera = Camera.open();
            try {
                camera.setPreviewDisplay(holder); }
            catch (exception) {
                camera.release();
                camera = null; } },
        surfaceDestroyed: function(holder) {
            camera.stopPreview();
            camera.release();
            camera = null; },
        surfaceChanged: function(holder, format, w, h) {
            var parameters = camera.getParameters();
            parameters.setPreviewSize(w, h);
            // Causes camera to fail on Nexus One
            //camera.setParameters(parameters);
            camera.startPreview(); }
    };

    // Create the instance of the interface.
    var callback = createInstance(
        // Must use full package name for some reason...
        Packages.android.view.SurfaceHolder.Callback, 
        object);
    
    // Set the callback for the surface.
    surface.getHolder().addCallback(callback);
    surface.getHolder().setType(
        SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    return object;
}

// Create an instance of a Java interface.
//   javaInterface - the interface type
//   object - JS object that will receive messages
//     sent to the instance
function createInstance(javaInterface, object)
{
    // Local function to convert a Java array to
    // a JavaScript array
    function javaArrayToJsArray(javaArray) {
        var jsArray = [];
        for (var i = 0; i < javaArray.length; ++i) {
            jsArray[i] = javaArray[i]; }
        return jsArray; }

    // Short name for lang package.
    var lang = Packages.java.lang;

    // Interface we implement.
    var interfaces = 
        lang.reflect.Array.newInstance(lang.Class, 1);
    interfaces[0] = javaInterface;

    // Create proxy object.
    var obj = lang.reflect.Proxy.newProxyInstance(
        lang.ClassLoader.getSystemClassLoader(),
        interfaces,
        // This function is called on method invocation.
        // Note that args is a Java array.
        function(proxy, method, args) {
            // Apply JavaScript function to arguments.
            return object[method.getName()].apply(
                null,
                javaArrayToJsArray(args)); });
    return obj;
}

DroidScript architecture

DroidScriptActivity.java - basic Android activity that embeds the Rhino engine
DroidScriptApp.java - extends DroidScriptActivity, provides the starting point of the application
DroidScriptApp.js - The actual code for the DroidScript application, all of the user interface and application logic is here
DroidScriptServer.java - tiny web server that can be used from JavaScript
DroidScriptServer.js - The user interface and logic for the server activity
Limitation: require is currently not implemented, but you can load JavaScript code into the global scope

Event handling functions (called from DroidScriptActivity.java)

onCreate
onStart
onRestart
onResume
onPause
onStop
onDestroy
onCreateContextMenu
onContextItemSelected
onCreateContextMenu
onCreateOptionsMenu
onPrepareOptionsMenu
onOptionsItemSelected

Evaluating JavaScript (methods in DroidScriptActivity.java)

eval(String code)
evalFileOrUrl(String fileNameOrUrl)
evalAssetFile(String fileName)
// These can be called from JavScript, for example:
Activity.evalFileOrUrl("http://www.mydomain.se/MyCode.js");
Or use eval in JavaScript

How to build a native JavaScript app

DroidScriptActivity.java - use as is
MyApp.java - extend DroidScriptActivity, and load your JavaScript code from asset files, or download over the Internet and optionally cache files on the device
MyApp.js - the JavaScript code of the application, place in the assets folder in the Eclipse project
// MyApp.java - example of a stand alone DroidScript app.
package mydomain.myapp;

import android.content.Intent;
import android.os.Bundle;

public class MyApp extends comikit.droidscript.DroidScriptActivity
{
    @Override
    public void onCreate(Bundle bubble)
    {
        Intent intent = getIntent();
        intent.putExtra("ScriptAsset", "MyApp.js");
        super.onCreate(bubble);
    }
}
As a starting point, check out DroidZine (project by Jonas Beckman and Mikael Kindborg): http://github.com/kamidev/droidzine/

JavaScript in WebKit

This example shows how to access Java from JavaScript in WebKit, via DroidScript.
function onCreate(bundle)
{
    var WebView = Packages.android.webkit.WebView;

    var webview = new WebView(Activity);
    webview.getSettings().setJavaScriptEnabled(true);
    webview.addJavascriptInterface(Activity, "activity");
    Activity.setContentView(webview);   
    
    var content =
    """<html>
        <body>
            <script>
            function showToast(message) {
                activity.eval(
                    "var Toast = Packages.android.widget.Toast;" +
                    "Toast.makeText(Activity, '" + message + "', " +
                    "Toast.LENGTH_SHORT).show();"); }
            </script>
            <h1>Take the pill</h1>
            <input 
                type="button" 
                value="Take pill" 
                onclick="showToast('You have taken the red pill!')">
        </body>
    </html>""";
    
    webview.loadData(content, "text/html", "utf-8");
}

MobileLua - An easy way to create Android apps (developed with MoSync)

Minimal painting app in Lua:
function OnInit()
  SetColor(255, 255, 255)
  FillRect(0, 0, ScreenWidth(), ScreenHeight())
  UpdateScreen()
end

function OnTouchDown(x, y)
  SetColor(0, 0, 0)
  FillRect(x - 5, y - 5, 10, 10)
  UpdateScreen()
end

function OnTouchDrag(x, y)
  OnTouchDown(x, y)
end
http://code.google.com/p/mobilelua/

0 comments:

Post a Comment