We now deal with areas that
are part of the native capabilities of Symbian OS. We first give advice
on how to better understand some of the messages that appear in Java
exceptions. Then we discuss how some mobile applications can use both
Java and native capabilities.
1. Native Error Codes in Java Exception Messages
Error handling in Java ME on Symbian OS remains the
same as error handling anywhere else in Java ME; it is identical to
standard Java error handling. The platform implementation informs you of
errors through the usual mechanism of throwing Java exceptions as
specified in the Java Language Specification. That does not change; what
changes is what appears in the error message.
The thrown exception carries a description, which
should provide the developer with sufficient information about the
problem, e.g., 'Connection not found'. Java ME on Symbian OS is tightly integrated on top of the native
Symbian OS APIs, therefore an error which occurs deep down in the native
domain eventually triggers a Java exception to be thrown back in the
Java ME run-time environment. In some cases, the Java exception error
message includes the native error code which has triggered the Java
exception. For example, your Java code might catch an IOException with a description that includes a native error code such as −36,
which means that a local IPC session was disconnected (and you might
want to restart the device). Naturally, at run time your application
should treat the thrown Java exception in the same way as on any other
Java ME platform. However, during development, if a native error code is
specified, it could provide you with some additional information that
might help you to diagnose the error and track down the reason it
occurred.
In most cases, native error codes should not appear
in the Java exception message. For example, if your application fails to
send an SMS message or a TCP peer connection is dropped, there is no
reason to expect a native Symbian OS error code. When there is a native
error code, it might be related to missing settings on the device. Then
it would be worth looking up the native error code in the Symbian OS
documentation and using this additional information to identify or better understand the problem.
2. Combining MIDlets and Other Run-time Environments
CLDC was not designed to be interoperable with native
code or other run-time environments but when something is possible, a
developer will find a use for it. In Symbian OS, there are many other
run-time environments and very quickly some developers understood that
this opens up opportunities; for example, native applications can launch
a MIDlet or provide a MIDlet with services that it cannot get from the
Java ME APIs. Such interoperability can be achieved without breaking any
Java ME rule, guideline or specification. All it takes is a bit of
creativity and a small amount of work. Note that you are doing something
which is proprietary to Symbian OS and requires knowledge of native
Symbian C++ development.
Three questions frequently occur in discussions on
this topic – can a MIDlet suite be installed together with a native
application, can a native application launch a MIDlet and can MIDlets
use the native Symbian OS APIs?
A MIDlet suite can be installed with a native application. The suite can be bundled with the native application in the SIS file.
In exceptional circumstances, when there is dependency between a MIDlet
and an associated native application, it may be convenient to deliver
the entire application in a single SIS file. By default, the user must
manually install the suite using a file browser. On some platforms, it
is possible to automate this process but the System Installer is a
manufacturer-customizable component and not all platforms support this
behavior.
A MIDlet can be launched from a native application
using the MIDP Push mechanism. For example, the JAD attribute below
shows static registration of an inbound socket connection on port 5000,
with no address filtering, which activates PushMIDlet:
MIDlet-Push-1: socket://:5000, PushMIDlet, *
Using JSR-211 CHAPI, you can register your MIDlet to
be a handler for a specific type of content and use that mechanism to
launch it from native code.
There is no standard way to invoke native Symbian OS
APIs from CLDC, in general, or on Symbian OS, specifically. Java Native
Interface (JNI) is a powerful Java SE mechanism that enables desktop
applications to jump out of the Java run-time environment and execute
native code but CLDC does not include JNI.
3. Simulating Inter-process Communication
A recurrent question is whether it is possible to
invoke native Symbian OS APIs, either through Java Native Interface
(JNI) or by using another mechanism. The answer is that there is no
standardized way to do so in CLDC in general or on Symbian OS
specifically. JNI is indeed a powerful Java SE mechanism that enables
desktop applications to jump out of the Java run-time environment and
execute native code but, due to the nature of mobile platforms, CLDC
does not include JNI.
You may need to invoke native Symbian OS APIs where
the required functionality is not available on the Java ME platform
(e.g., when an MIDP application requires an SQL database) or the Java
API does not have sufficiently fine-grained control (e.g., extremely
fine-grained multimedia capabilities are not found in JSR-234 AMMS but
are available in the native Multimedia Framework of Symbian OS). It may
also be desirable for a MIDlet to communicate with an application
(another MIDlet, a native Symbian C++ application or an application
hosted by another run-time environment, such as Flash, Python, or Ruby)
running in another process.
Symbian OS is a multitasking open platform in which
native applications can be installed and multiple applications can run
concurrently. Therefore, an inter-process communication (IPC) mechanism
can be a solution, if required; for example, you may want to develop a
hybrid mobile application that uses Java ME APIs for the back end and
Flash Lite for the front end.
A possible solution could utilize a very simple concept (see Figure 1).
Just as MIDlets can communicate over a network protocol with a remote
server or a remote peer, they can communicate with another application
running on the same host using the localhost address. Both
sides then need to agree a common protocol to be used for requests and
responses. Here we present a simple and generic reference design in
order to demonstrate how the concept could be implemented.
First we start with possible code for an IPC client:
private static final int MY_NATIVE_SERVER_PORT = 9876;
private IpcCommand cmd = new IpcCommand(...);
private IpcResponse res = null;
...
private void sendAndReceive() {
IpcClientConnection ipcConn;
try {
ipcConn = IpcClientConnection.openConnection(MY_NATIVE_SERVER_PORT);
// send command
ipcConn.sendCommand(cmd);
// receive response
res = ipcConn.receiveResponse();
}
catch (Exception e) {
e.printStackTrace();
}
}
So the steps to perform are:
Open an IPC client connection.
Process the data received.
For the sake of clean code and encapsulation, we create command and response classes:
public class IpcMessage {
private byte[] data;
public IpcMessage(byte[] data) {
this.data = data;
}
byte[] getBuffer() {
// TODO: handle buffer according to protocol
return data;
}
}
// TODO: implement derived classes according to protocol
public class IpcResponse extends IpcMessage {
...
}
public class IpcCommand extends IpcMessage {
...
}
We define the base class for all IPC connections, which provides low-level synchronous bi-directional communication:
public abstract class IpcConnection {
private final InputStream in;
private final OutputStream out;
IpcConnection(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
}
// Generic IPC operations
protected void send(byte[] serializedCommand) throws IOException {
out.write(serializedCommand.length);
out.write(serializedCommand);
out.flush();
}
protected byte[] receive() throws IOException {
int responseLength = in.read();
byte[] responseBuffer = new byte[responseLength];
int read = 0;
while(read != responseLength) {
read += in.read(responseBuffer, read, responseLength - read);
}
return responseBuffer;
}
}
We define the IPC client-side connection class which can be returned from the static factory method IpcClientConnection.openConnection() when it is called with the remote IPC server port parameter:
public class IpcClientConnection extends IpcConnection {
private final StreamConnection sc;
private IpcClientConnection(StreamConnection sc) throws IOException {
super(sc.openInputStream(), sc.openOutputStream());
this.sc = sc;
}
public static IpcClientConnection openConnection(int ipcServerPort)
throws IOException {
StreamConnection sc = (StreamConnection)Connector.open
("socket://127.0.0.1:" + ipcServerPort,
Connector.READ_WRITE, false);
return new IpcClientConnection(sc);
}
// IPC operations
public void sendCommand(IpcCommand cmd) throws IOException {
super.send(cmd.getBuffer());
}
public IpcResponse receiveResponse() throws IOException {
return new IpcResponse(super.receive());
}
}
On the other side, there could be a native Symbian
C++ application, a Flash Lite IPC client that uses ActionScript or a
Python script. The IPC server code can be implemented in any run-time
environment that supports accepting incoming TCP connections.
For the sake of providing a reference implementation, here is an IPC server-side connection in Java ME:
public class IpcServerConnection extends IpcConnection {
private final SocketConnection sc;
private IpcServerConnection(SocketConnection sc) throws IOException {
super(sc.openInputStream(), sc.openOutputStream());
this.sc = sc;
}
public static IpcServerConnection openConnection(int ipcServerPort)
throws IOException {
ServerSocketConnection ssc =
(ServerSocketConnection)Connector.open("socket://:9876");
SocketConnection sc = (SocketConnection) ssc.acceptAndOpen();
// TODO: use sc.getAddress() to ensure only local connections are
// handled
return new IpcServerConnection(sc);
}
// IPC operations
public IpcCommand receiveCommand() throws IOException {
return new IpcCommand(super.receive());
}
public void sendResponse(IpcResponse response) throws IOException {
super.send(response.getBuffer());
}
}
Opening a TCP port on your device is potentially a
security hole. You should, therefore, apply any methods required to
ensure authorization of incoming connections (e.g., accept incoming
connections only from the device itself).
An additional workaround that could be considered is a
somewhat more primitive form of communication. The passed information
is put into a file (using JSR-75, FileConnection) at a known location
and is polled on the other side. Obviously, this is not a suitable
solution for the majority of cases, which require either JNI or IPC.
However, it is an additional option for passing a message in an
unreliable protocol.
There are obvious downsides
to using this solution, such as breaking platform portability and the
need to manage and maintain both Java and native support. However, TCP
can be used to invoke native functionality that is not available through
Java APIs or to provide IPC between a MIDlet and an application running
in another process. In some cases, this option can mean that
implementing a mobile application in Java becomes feasible.