SL4A 之实现原理解析

关于SL4A的简介和在Android系统的安装及使用,请参考我的上一篇博文《Android 脚本设计之 SL4A》,本篇来分析其内部的实现机制。

深入理解SL4A

SL4A架构实现了本地脚本和原生态Android程序的内部消息通信,所以任何本地脚本语言,只要实现了这套兼容的JSON RPC通信接口,就可以呼叫SL4A的RPC Server端程序。至于为什么要选用JSON,及这种格式的优点和特征,此处就不详细叙述了,大家可以查看JSON官网。

“JavaScript Object Notation (JSON) is a lightweight, text-based,
language-independent data interchange format. It was derived from the
ECMAScript Programming Language Standard. JSON defines a small
set of formatting rules for the portable representation of structured data.
JSON can represent four primitive types (strings, numbers, booleans,
and null) and two structured types (objects and arrays).”

SL4A总体架构

从上图可以看出,SL4A总体包括Client和Server两部分来实现通信和整体架构的独立性,Client端负责解析本地脚本,这样只要本地脚本实现了兼容的接口,就可以方便实现脚本语言的扩展,而Server端则封装了Android原生态程序的设计,即使在android底层API发生API变化的时候,Client端也基本不会受到影响,Client把脚本中解析出来的函数调用通过RPC通信,远程呼叫Server端代理接口,然后由Server调用原生态的Android API(Android Facade架构)来完成具体的功能,在调用结束后,Server端将执行结果反馈给Client端。

整个执行过程如下:

 -­‐-­‐ Script Interpreter
-­‐-­‐-­‐-­‐ Client/Caller/Consumer Script
-­‐-­‐-­‐-­‐-­‐-­‐ "Android" Script Object (locally wraps RPC calls) -­‐ Local Proxy
-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐ Remote Procedure Calls (RPC) – Exchanges contain a JSON payload
-­‐-­‐-­‐-­‐-­‐-­‐ Android API Java Facade -­‐ Remote Proxy
-­‐-­‐-­‐-­‐ API Server/Provider -­‐ Android Java application
-­‐-­‐ The Android Platform itself

Local Proxy 的实现机制

其实每个本地模块都封装实现了一个android实体类,然后开放其调用接口,内部通过RPC与SL4A Server端通信。

Python 模块(android.py)之实现

 

# Copyright (C) 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
 
__author__ = 'Damon Kohler <[email protected]>'
 
import collections
import json
import os
import socket
import sys
 
PORT = os.environ.get('AP_PORT')
HOST = os.environ.get('AP_HOST')
HANDSHAKE = os.environ.get('AP_HANDSHAKE')
Result = collections.namedtuple('Result', 'id,result,error')
 
class Android(object):
 
  def __init__(self, addr=None):
    if addr is None:
      addr = HOST, PORT
    self.conn = socket.create_connection(addr)
    self.client = self.conn.makefile()
    self.id = 0
    if HANDSHAKE is not None:
      self._authenticate(HANDSHAKE)
 
  def _rpc(self, method, *args):
    data = {'id': self.id,
            'method': method,
            'params': args}
    request = json.dumps(data)
    self.client.write(request+'\n')
    self.client.flush()
    response = self.client.readline()
    self.id += 1
    result = json.loads(response)
    if result['error'] is not None:
      print result['error']
    # namedtuple doesn't work with unicode keys.
    return Result(id=result['id'], result=result['result'],
                  error=result['error'], )
 
  def __getattr__(self, name):
    def rpc_call(*args):
      return self._rpc(name, *args)
    return rpc_call
 

BeanShell 模块(android.bsh)之实现

 

// Copyright (C) 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
 
import org.json.*;
 
Android() {
  String AP_PORT = System.getenv().get("AP_PORT");
  String AP_HOST = System.getenv().get("AP_HOST");
  String AP_HANDSHAKE = System.getenv().get("AP_HANDSHAKE");
  Socket conn = new Socket(AP_HOST, Integer.decode(AP_PORT));
  BufferedReader in = new BufferedReader(
      new InputStreamReader(conn.getInputStream(), "8859_1"), 1 << 13);
  OutputStream out_stream = new BufferedOutputStream(
      conn.getOutputStream(), 1 << 13);
  PrintWriter out = new PrintWriter(
      new OutputStreamWriter(out_stream, "8859_1"), true);
  int id = 0;
 
  call(String method, JSONArray params) {
    JSONObject request = new JSONObject();
    request.put("id", id);
    request.put("method", method);
    request.put("params", params);
    out.write(request.toString() + "\n");
    out.flush();
    String data = in.readLine();
    if (data == null) {
      return null;
    }
    return new JSONObject(data);
  }
 
  call(String method) {
    JSONArray args = new JSONArray();
    call(method, args);
  }
 
  call(String method, Object arg1) {
    JSONArray args = new JSONArray();
    args.put(arg1);
    call(method, args);
  }
 
  call(String method, Object arg1, Object arg2) {
    JSONArray args = new JSONArray();
    args.put(arg1);
    args.put(arg2);
    call(method, args);
  }
 
  call(String method, Object arg1, Object arg2, Object arg3) {
    JSONArray args = new JSONArray();
    args.put(arg1);
    args.put(arg2);
    args.put(arg3);
    call(method, args);
  }
 
  call(String method, Object arg1, Object arg2, Object arg3, Object arg4) {
    JSONArray args = new JSONArray();
    args.put(arg1);
    args.put(arg2);
    args.put(arg3);
    args.put(arg4);
    call(method, args);
  }
 
  JSONArray handshake = new JSONArray();
  handshake.put(AP_HANDSHAKE);
  call("_authenticate", handshake);
 
  return this;
}
 

JavaScript 模块(android.js)之实现

 

/* * Copyright 2009 Brice Lambson * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */
 
var AP_PORT = java.lang.System.getenv("AP_PORT");
var AP_HOST = java.lang.System.getenv("AP_HOST");
var AP_HANDSHAKE = java.lang.System.getenv("AP_HANDSHAKE");
 
load('/sdcard/com.googlecode.rhinoforandroid/extras/rhino/json2.js');
 
function Android() {
 
  this.connection = new java.net.Socket(String(AP_HOST), AP_PORT),
  this.input = new java.io.BufferedReader(
      new java.io.InputStreamReader(this.connection.getInputStream(), "8859_1"),
                                    1 << 13),
  this.output = new java.io.PrintWriter(new java.io.OutputStreamWriter(
      new java.io.BufferedOutputStream(this.connection.getOutputStream(),
                                       1 << 13),
      "8859_1"), true),
  this.id = 0,
 
  this.rpc = function(method, args) {
    this.id += 1;
    var request = JSON.stringify({'id': this.id, 'method': method,
                                  'params': args});
    this.output.write(request + '\n');
    this.output.flush();
    var response = this.input.readLine();
    return eval("(" + response + ")");
  },
 
  this.__noSuchMethod__ = function(id, args) {
    var response = this.rpc(id, args);
    if (response.error != null) {
      throw response.error;
    }
    return response.result;
  }
 
  this._authenticate(String(AP_HANDSHAKE));
}
 

android-cruft(ndk-to-sl4a.c)之C语言实现

 

// Released into the public domain, 15 August 2010
 
// This program demonstrates how a C application can access some of the Android
// API via the SL4A (Scripting Languages for Android, formerly "ASE", or Android
// Scripting Environment) RPC mechanism. It works either from a host computer
// or as a native binary compiled with the NDK (rooted phone required, I think)
 
// SL4A is a neat Android app that provides support for many popular scripting
// languages like Python, Perl, Ruby and TCL. SL4A exposes a useful subset of
// the Android API in a clever way: by setting up a JSON RPC server. That way,
// each language only needs to implement a thin RPC client layer to access the
// whole SL4A API.
 
// The Android NDK is a C compiler only intended for writing optimized
// subroutines of "normal" Android apps written in Java. So it doesn't come
// with any way to access the Android API.
 
// This program uses the excellent "Jansson" JSON library to talk to SL4A's
// RPC server, effectively adding native C programs to the list of languages
// supported by SL4A.
 
// To try it, first install SL4A: http://code.google.com/p/android-scripting/
//
// Start a private server with View->Interpreters->Start Server
//
// Note the port number the server is running on by pulling down the status
// bar and tapping "SL4A service". 
 
// This program works just fine as either a native Android binary or from a
// host machine.
 
// ------------
 
// To compile on an ordinary linux machine, first install libjansson. Then:
 
// $ gcc -ljansson ndk-to-sl4a.c -o ndk-to-sl4a
 
// To access SL4A on the phone use "adb forward tcp:XXXXX tcp:XXXXX" to port
// forward the SL4A server port from your host to the phone. See this
// page for more details:
// http://code.google.com/p/android-scripting/wiki/RemoteControl
 
// ------------
 
// To compile using the NDK:
// 1. Make sure you can compile "Hello, world" using the NDK. See:
// http://credentiality2.blogspot.com/2010/08/native-android-c-program-using-ndk.html
//
// 2. If you followed the above instructions, you have a copy of the agcc.pl
// wrapper that calls the NDK's gcc compiler with the right options for
// standalone apps.
//
// 3. Unpack a fresh copy of the jansson sources. Tell configure to build for
// Android:
//
// $ CC=agcc.pl ./configure --host=arm
// $ make
//
// 4. Cross your fingers and go! (I'm quite certain there's a more elegant
// way to do this)
//
// $ agcc.pl -I/path/to/jansson-1.3/src -o ndk-to-sl4a-arm ndk-to-sl4a.c /path/to/jansson-1.3/src/*.o
//
// 5. Copy to the phone and run it with the port of the SL4A server!
 
#include <stdio.h>
#include <jansson.h>
#include <unistd.h>
#include <string.h>
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
 
// This mimics SL4A's android.py, constructing a JSON RPC object and
// sending it to the SL4A server.
int sl4a_rpc(int socket_fd, char *method, json_t *params) {
  static int request_id = 0; // monotonically increasing counter
 
  json_t *root = json_object();
 
  json_object_set(root, "id", json_integer(request_id));
  request_id++;
 
  json_object_set(root, "method", json_string(method));
 
  if (params == NULL) {
    params = json_array();
    json_array_append(params, json_null());
  }
 
  json_object_set(root, "params", params);
 
  char *command = json_dumps(root, JSON_PRESERVE_ORDER | JSON_ENSURE_ASCII);
  printf("command string:'%s'\n", command);
 
  write(socket_fd, command, strlen(command));
  write(socket_fd, "\n", strlen("\n"));
 
  // At this point we just print the response, but really we should buffer it
  // up into a single string, then pass it to json_loads() for decoding.
  printf("Got back:\n");
  while (1) {
    char c;
    read(socket_fd, &c, 1);
    printf("%c", c);
    if (c == '\n') {
      break;
    }
  }
  fflush(stdout);
  return 0;
}
 
// This function is just boilerplate TCP socket setup code
int init_socket(char *hostname, int port) {
  int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (socket_fd == -1) {
    perror("Error creating socket");
    return 0;
  }
 
  struct hostent *host = gethostbyname(hostname);
  if (host == NULL) {
    perror("No such host");
    return -1;
  }
 
  struct sockaddr_in socket_address;
 
  int i;
  for (i=0; i < sizeof(socket_address); i++) {
    ((char *) &socket_address)[i] = 0;
  }
 
  socket_address.sin_family = AF_INET;
 
  for (i=0; i < host->h_length; i++) {
    ((char *) &socket_address.sin_addr.s_addr)[i] = ((char *) host->h_addr)[i];
  }
 
  socket_address.sin_port = htons(port);
 
  if (connect(socket_fd, (struct sockaddr *) &socket_address, sizeof(socket_address)) < 0) {
    perror("connect() failed");
    return -1;
  }
 
  return socket_fd;
}
 
main(int argc, char **argv) {
  int port = 0;
  if (argc != 2) {
    printf("Usage: %s port\n", argv[0]);
    return 1;
  }
  port = atoi(argv[1]);
 
  int socket_fd = init_socket("localhost", port);
  if (socket_fd < 0) return 2;
 
  json_t *params = json_array();
  json_array_append(params, json_string("w00t!"));
  sl4a_rpc(socket_fd, "makeToast", params);
}
 

Hello Android之多语言实现

在文章的最后,给大家一个经典的Hello Android多语言实现,O(∩_∩)O哈哈~

BeanShell:
source("/sdcard/com.googlecode.bshforandroid/extras/bsh/android.bsh");
droid = Android();
droid.call("makeToast", "Hello Android!");

JavaScript:
load("/sdcard/com.googlecode.rhinoforandroid/extras/rhino/android.js");
var droid = new Android();
droid.makeToast("Hello Android!");

Perl:
use Android;
my $a = Android-->new();
$a-->makeToast("Hello Android!");

Python:
import android
andy = android.Android()
andy.makeToast("Hello Android!")

Ruby:
droid = Android.new
droid.makeToast "Hello Android!"

TCL:
package require android
set android [android new]
$android makeToast "Hello Android!"

你可能感兴趣的:(解析)