Use the Kaazing WebSocket Gateway Android Client API
This procedure describes how you can use the Android Client API to create an Android client that connects to and sends and receives message from the Echo service running on the Kaazing WebSocket Gateway. This API allows you to take advantage of the WebSocket standard as described in the HTML5 specification.
The Android Client API is nearly identical to the Java API and you can review the Java API information in Interact with Kaazing WebSocket Gateway Using the WebSocket API to understand the general functionality of both APIs:
- To Use the WebSocket API in Java
- WebSocket and WsURLConnection
- URLFactory
- Setting and Overriding Defaults on the WebSocketFactory
- Methods for Text and Binary Messages
Refer to the Android Client API documentation for a complete description of all of the available methods.
In this procedure you will do the following:
- Install the Android Client API.
- Set up an Android project in Eclipse.
- Import the Kaazing WebSocket Gateway Android libraries.
- Create the Android client Touch User Interface (TUI).
- Add a dispatch queue class to the Android client.
- Add the import statements for the common Java and Android classes.
- Add the import statements for the Kaazing WebSocket Gateway Android API classes.
- Add the variables for the program.
- Add the onCreate() method with event listeners.
- Add the MessageReceiver that uses the WebSocketMessageReader to receive binary and text messages.
- Add the connect() to connect to the Gateway and receive messages.
- Add the disconnect() method called when a user clicks the Disconnect button.
- Add the methods for updating buttons according to the state of the WebSocket connection.
- Add the logMessage() method for Log area displayed in the client.
- Update the Echo service on the Gateway configuration file to accept on your local IP address.
- Run the Android client in the Android Emulator.
- Test the Android client in the Android Emulator.
Before You Begin
This procedure is part of Checklist: Build Android Clients Using Kaazing WebSocket Gateway:
- Use the Kaazing WebSocket Gateway Android Client API
- Secure Your Java and Android Clients
- Display Logs for the Android Client
Note: Learn about supported browsers, operating systems, and platform versions in the Release Notes.
Build the Android Client API WebSocket Demo
The following procedure walks through the steps of creating the out of the box Android WebSocket demo that is included with the Kaazing WebSocket Gateway bundle. The demo code displays how to use the Android Client API to create a client that creates a WebSocket connection with the Gateway and sends and receives text and binary messages.
Note: The following procedure uses the Android ADT Bundle (which includes the Eclipse IDE). You can download the Android ADT Bundle from http://developer.android.com/sdk/index.html.- Install the full version of the Gateway (Gateway + Documentation + Demos) as described in Setting Up Kaazing WebSocket Gateway. You need the Gateway installed to access the Android demo files, but you do not need to start the Gateway to test the demo.
- Download the Android ADT Bundle from http://developer.android.com/sdk/index.html. For steps on installing the Android SDK with an existing Eclipse IDE, see http://developer.android.com/sdk/installing/index.html.
- To set up the ADT Bundle, see http://developer.android.com/sdk/installing/bundle.html.
- Launch Eclipse from the installed ADT Bundle.
- Install the Android Client API.
- In Eclipse, click Window, and then click Android SDK Manager.
- Select any package from Android 2.3.3 or later and then click Install package.
- Set up an Android project in Eclipse.
- From the File menu, click New and then Android Application Project.
- In Application Name, name the project EchoActivity.
- In Package Name, enter com.kaazing.gateway.client.demo.
In Minimum Required SDK, choose API 10: Android 2.3.3 (Gingerbread) and click Next.
Note: To confirm or modify the SDK requirement on a project, right-click the project, and click Properties. Click Android, and then look at the Project Build Target settings.
- On the New Android Application page, click Next.
On the Configure Launcher Icon page, in Image File, click Browse, navigate to the following location in the Gateway bundle and select the icon.png file:
GATEWAY_HOME/demo/android/echo-demo/res/drawable-hdpi/icon.png
- Click Next.
- In Create Activity, select Blank Activity and click Next.
- In New Blank Activity, in Activity Name, enter EchoActivity.
- In Layout Name enter main and click Finish. If there are dependencies that you need to install, the Finish button is not available.
- The new project is generated.
- Import the Kaazing WebSocket Gateway Android libraries.
- Right-click the new project and click Properties.
- In the Properties dialog, click Java Build Path, click the Libraries tab, and click Add External JARs.
- Locate and select the Kaazing WebSocket Gateway Android library. The library is located in GATEWAY_HOME/lib/client/android. The library you need is com.kaazing.gateway.client.android.jar.
- In the Properties dialog, click OK. You can see the imported library and its classes in your project under Reference Libraries.
- Create the Android client Touch User Interface (TUI). Next you will add the text strings and layout for the Android client TUI. When you are finished, the Android client will look like this:
Figure: Android Client TUI - Open the strings.xml file located at EchoActivity/res/values/strings.xml and replace its contents with the following:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">WebSocket Demo</string> <string name="username_hint">Username</string> <string name="password_hint">Password</string> <string name="ok_label">OK</string> <string name="cancel_label">Cancel</string> <string name="location_default">ws://echo.websocket.org/</string> <string name="disconnect_label">Disconnect</string> <string name="connect_label">Connect</string> <string name="message_default">Hello World!</string> <string name="send_label">Send</string> <string name="clear_label">Clear</string> <string name="location_label">Location</string> <string name="message_label">Message</string> <string name="sendBinary_label">Send Binary</string> </resources>
You can see all of the buttons that will be displayed in the TUI. The location_default field is empty but you enter the location into the Android client when you test it.
- Open the main.xml file located at EchoActivity/res/layout/main.xml and replace its contents with the following:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#F27A31"> <TextView android:id="@+id/locationText" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/location_label" android:textColor="#ffffff" android:textSize="@dimen/edit_text_size" /> <EditText android:id="@+id/location" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/locationText" android:background="@android:drawable/editbox_background" android:text="@string/location_default" android:textSize="@dimen/edit_text_size" > <requestFocus /> </EditText> <Button android:id="@+id/connect" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/location" android:layout_marginLeft="10dip" android:text="@string/connect_label" android:textSize="@dimen/edit_text_size" /> <Button android:id="@+id/disconnect" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/location" android:layout_marginLeft="10dip" android:layout_toLeftOf="@+id/connect" android:enabled="false" android:text="@string/disconnect_label" android:textSize="@dimen/edit_text_size" /> <TextView android:id="@+id/label" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/connect" android:text="@string/message_label" android:textColor="#ffffff" /> <EditText android:id="@+id/message" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/label" android:background="@android:drawable/editbox_background" android:text="@string/message_default" android:textSize="@dimen/edit_text_size" /> <CheckBox android:id="@+id/sendBinaryCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/send" android:layout_alignParentLeft="true" android:layout_below="@id/message" android:text="@string/sendBinary_label" android:textSize="@dimen/edit_text_size" /> <Button android:id="@+id/send" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/message" android:layout_marginLeft="10dip" android:enabled="false" android:text="@string/send_label" android:textSize="@dimen/edit_text_size" /> <LinearLayout android:id="@+id/logContainer" android:layout_width="match_parent" android:layout_height="fill_parent" android:orientation="vertical" android:layout_below="@+id/send"> <TextView android:id="@+id/log" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:background="@android:drawable/editbox_background" android:maxLines="20" android:scrollbars="horizontal|vertical" android:textColor="#000000" android:textSize="@dimen/edit_text_size" /> <Button android:id="@+id/clear" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="@string/clear_label" android:textSize="@dimen/edit_text_size" /> </LinearLayout> </RelativeLayout>
- Open the strings.xml file located at EchoActivity/res/values/strings.xml and replace its contents with the following:
Add a dispatch queue class to the Android client.
A dispatch queue class is used to run tasks in a separate thread from the main thread (to run some tasks asynchronously). The dispatch queue class is used to add Runnable in a queue. Runnable will be run in a first-in first-out basis. This dispatch queue class is useful to run a series of tasks sequentially in a separate thread from the main thread of the Android client. All of the blocking calls of the Android client will be run in a background thread so that the TUI is not blocked and can remain responsive.
- Right-click the package com.kaazing.gateway.client.demo, click New, and click Class.
- In Name enter DispatchQueue and click Finish. The new DispatchQueue.java class is added to the src folder.
- Double-click DispatchQueue.java.
- Replace the contents with the following code:
package com.kaazing.gateway.client.demo; import android.os.Handler; import android.os.HandlerThread; public class DispatchQueue extends HandlerThread { private Handler handler; public DispatchQueue(String name) { super(name); } // The message blocks until the thread is started. This should be called // after call to start() to ensure the thread is ready. public void waitUntilReady() { handler = new Handler(getLooper()); } // Adds the Runnable to the message queue which will be run on the thread. // The runnable will be run in a first-in first-out basis. public void dispatchAsync(Runnable task) { handler.post(task); } public void removePendingJobs() { handler.removeCallbacksAndMessages(null); } }
- Modify the main class for the Android client. In the src folder for the project, under com.kaazing.gateway.client.demo, double-click EchoActivity.java. You will add the main Java code for the Android client in this file.
- Delete all of the contents except the package com.kaazing.gateway.client.demo package declaration and the EchoActivity class declaration:
package com.kaazing.gateway.client.demo; // Import statements will go here public class EchoActivity extends FragmentActivity { // the remaining code will go here }
- Add the import statements for the common Java and Android classes directly after the package com.kaazing.gateway.client.demo package declaration:
import java.io.IOException; import java.net.PasswordAuthentication; import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.Semaphore; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView;
- Add the import statements for the Kaazing WebSocket Gateway Android Client API classes that will be used in the client:
// Include these statements with any client import com.kaazing.net.ws.WebSocket; import com.kaazing.net.ws.WebSocketFactory; import com.kaazing.net.ws.WebSocketMessageReader; import com.kaazing.net.ws.WebSocketMessageType; import com.kaazing.net.ws.WebSocketMessageWriter; // Include these statements when a client must authenticate with the Gateway import com.kaazing.net.auth.BasicChallengeHandler; import com.kaazing.net.auth.ChallengeHandler; import com.kaazing.net.auth.LoginHandler;
- Add the variables for the program directly after the EchoActivity class declaration:
private TextView location; private TextView message; private TextView log; private Button sendBtn; private Button connectBtn; private Button disconnectBtn; private Button clearBtn; private CheckBox sendBinaryCheckBox; private WebSocket webSocket; private DispatchQueue dispatchQueue; private boolean wasConnectedBeforePaused = false; private boolean closedExplicitly = false; private LoginDialogFragment loginDialog;
- Add the onCreate() method with event listeners for the Connect, Send, Disconnect, and Clear buttons. This is a long method that includes event listeners for user interactions (clicks), and defines the program’s actions in response to when the user clicks the Connect, Send, Disconnect, and Clear buttons.
// Called when the activity is first created. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Add values to the variables location = (TextView)findViewById(R.id.location); message = (TextView)findViewById(R.id.message); log = (TextView)findViewById(R.id.log); sendBtn = (Button)findViewById(R.id.send); connectBtn = (Button)findViewById(R.id.connect); disconnectBtn = (Button)findViewById(R.id.disconnect); clearBtn = (Button)findViewById(R.id.clear); sendBinaryCheckBox = (CheckBox)findViewById(R.id.sendBinaryCheckBox); // Run when the Connect button is clicked connectBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { connectBtn.setEnabled(false); // call the connect() method in the MessageReceiver class connect(); } }); // Run when the Send button is clicked sendBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { final boolean sendBinary = sendBinaryCheckBox.isChecked(); // Run as part of the dispatch queue in DispatchQueue.java // Receive messages in a separate thread dispatchQueue.dispatchAsync(new Runnable() { public void run() { try { WebSocketMessageWriter messageWriter = webSocket.getMessageWriter(); if (sendBinary) { String messageToSend = message.getText().toString(); ByteBuffer payload = ByteBuffer.wrap(messageToSend.getBytes()); logMessage("SEND BINARY:" + getHexDump(payload)); messageWriter.writeBinary(payload); } else { logMessage("SEND: " + message.getText()); messageWriter.writeText(message.getText()); } } catch (Exception e) { e.printStackTrace(); logMessage(e.getMessage()); } } }); } }); // Run when the Disconnect button is clicked, // and call the disconnect() method in the MessageReceiver class. disconnectBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { disconnect(); } }); // Run when the Clear button is clicked, and clear the log. clearBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { log.setText(""); } }); }
This sendBtn.setOnClickListener() event listener gets the text submitted by the user using message.getText() and then uses the WebSocketMessageWriter class to send the text message to the Gateway using the writeText() method. An instance of the WebSocketMessageWriter class is obtained by invoking the getMessageWriter() method on WebSocket. Once the WebSocket connection is closed, a new WebSocketMessageReader should be obtained after the connection has been established. Using the old reader will result in IOException.
- Add the MessageReceiver that uses the WebSocketMessageReader to receive binary and text messages:
private class MessageReceiver implements Runnable { // run this in the background thread // Use WebSocketMessageReader to receive binary // and text messages private WebSocketMessageReader messageReader; private MessageReceiver(WebSocketMessageReader reader) { this.messageReader = reader; } public void run() { WebSocketMessageType type = null; try { // The next() method returns the type of the received message. // Returns TEXT, BINARY, and EOS if the connection is closed. while ((type = messageReader.next()) != WebSocketMessageType.EOS) { switch (type) { case BINARY: // getBinary() returns the payload as a CharSequence. // Place sequence in a byte buffer, and send it to // getHexDump() to be converted into hexadecimal and // then ASCII characters for output. // getHexDump() is defined later in this client. ByteBuffer data = messageReader.getBinary(); logMessage("RECEIVED: " + getHexDump(data)); break; case TEXT: // readText() returns the payload as a CharSequence CharSequence text = messageReader.getText(); logMessage("RECEIVED: " + text.toString()); break; } } if (!closedExplicitly) { // Connection got closed due to either of the cases // - Server closing the connection because of authentication timeout // - network failure webSocket = null; logMessage("Connection Closed!!"); updateButtonsForDisconnected(); // disable Disconnect button } } catch (Exception ex) { ex.printStackTrace(); logMessage(ex.getMessage()); } } }
- Add the getHexDump() method used when receiving binary messages as defined in MessageReceiver:
private String getHexDump(ByteBuffer buf) { if (buf.position() == buf.limit()) { return "empty"; } StringBuilder hexDump = new StringBuilder(); for (int i = buf.position(); i < buf.limit(); i++) { hexDump.append(Integer.toHexString(buf.get(i)&0xFF)).append(' '); } return hexDump.toString(); }
- Add the connect() to connect to the Gateway and receive messages. This method defines the parameters of the WebSocket connection (lines 21-22), connects to the Gateway (line 29), and define how received messages are handled (lines 33). Note the use of a try catch block. A try catch block is the recommended method for connections in order to catch any exceptions (lines 42-44).
private void connect() { closedExplicitly = false; logMessage("CONNECTING"); // Initialize dispatch queue that will be used to run // Blocking calls asynchronously on a separate thread dispatchQueue = new DispatchQueue("Async Dispatch Queue"); dispatchQueue.start(); dispatchQueue.waitUntilReady(); // Since WebSocket.connect() is a blocking method that will not return until // the connection is established or connection fails, it is a good practice to // establish the connection on a separate thread and prevent the TUI from being blocked. dispatchQueue.dispatchAsync(new Runnable() { public void run() { try { WebSocketFactory webSocketFactory = WebSocketFactory.createWebSocketFactory(); // Create the WebSocket connection using the target location webSocket = webSocketFactory.createWebSocket( URI.create(location.getText().toString())); // Connect with the server using an endpoint. // The thread invoking this method will be blocked until // a successful connection is established. If the connection // cannot be established, then an IOException is thrown // and the thread is unblocked. webSocket.connect(); logMessage("CONNECTED"); // Call the messageReceiver() method in the MessageReceiver class WebSocketMessageReader messageReader = webSocket.getMessageReader(); MessageReceiver messageReceiver = new MessageReceiver(messageReader); // Receive messages as a separate thread new Thread(messageReceiver).start(); // Change button state to reflect connection status updateButtonsForConnected(); } catch (Exception e) { updateButtonsForDisconnected(); logMessage(e.getMessage()); dispatchQueue.quit(); } } }); }
- Add the disconnect() method called when a user clicks the Disconnect button.
private void disconnect() { closedExplicitly = true; disconnectBtn.setEnabled(false); logMessage("DISCONNECTING"); dispatchQueue.removePendingJobs(); dispatchQueue.quit(); // Run this as a new thread new Thread(new Runnable() { public void run() { try { // Close the WebSocket connection webSocket.close(); logMessage("DISCONNECTED"); } catch (IOException e) { logMessage(e.getMessage()); } finally { webSocket = null; // Update buttons with connection status updateButtonsForDisconnected(); } } }).start(); }
- Add the methods for when the client is paused, resumes, and when an activity is finished.
- Add onPause() method to disconnect the WebSocket connection when the client is paused on the device.
public void onPause() { if (webSocket != null) { wasConnectedBeforePaused = true; disconnect(); } super.onPause(); }
- Add the onResume() method to connect the client when it is resumed from a paused state.
public void onResume() { if (wasConnectedBeforePaused) { wasConnectedBeforePaused = false; connect(); } super.onResume(); }
- Add the onDestroy() method to perform any final cleanup before an activity is finished.
public void onDestroy() { if (webSocket != null) { disconnect(); } super.onDestroy(); }
- Add onPause() method to disconnect the WebSocket connection when the client is paused on the device.
- Add the methods for updating buttons according to the state of the WebSocket connection. The updateButtonsForConnected() method updates buttons for an active WebSocket connection and the updateButtonsForDisconnected() method updates buttons for an inactive connection.
private void updateButtonsForConnected() { runOnUiThread(new Runnable() { public void run() { connectBtn.setEnabled(false); sendBtn.setEnabled(true); disconnectBtn.setEnabled(true); } }); } private void updateButtonsForDisconnected() { runOnUiThread(new Runnable() { public void run() { connectBtn.setEnabled(true); disconnectBtn.setEnabled(false); sendBtn.setEnabled(false); } }); }
- Add the logMessage() method for the Log area displayed in the client.
private void logMessage(final String logMessage) { runOnUiThread(new Runnable() { public void run() { log.setText(logMessage + "\n" + log.getText()); } }); }
- Update the Echo service on the Gateway configuration file to accept on your local IP address.
- Open the Gateway configuration file located at GATEWAY_HOME/conf/gateway-config.xml.
- Modify the Echo service to accept on your local IP address (in the following example, the IP address 192.168.0.103 is used).
<service> <name>echo</name> <description>Simple echo service</description> <accept>ws://build-macmini.kaazing.test:${gateway.extras.port}/echo</accept> <accept>ws://192.168.0.103:${gateway.extras.port}/echo</accept> <type>echo</type> <realm-name>demo</realm-name> <!-- <authorization-constraint> <require-role>AUTHORIZED</require-role> </authorization-constraint> --> <cross-site-constraint> <allow-origin> http://build-macmini.kaazing.test:${gateway.extras.port} </allow-origin> </cross-site-constraint> <cross-site-constraint> <allow-origin> http://192.168.0.103:${gateway.extras.port} </allow-origin> </cross-site-constraint> </service>
- Save the Gateway configuration file and restart the Gateway as described in How do I start and stop the Gateway? in Setting Up Kaazing WebSocket Gateway.
- Run the Android client in the Android Emulator. You will need to create an Android Virtual Device (AVD) first. For information on creating an AVD, see Managing AVDs with AVD Manager.
- Right-click the EchoActivity project, click Run As, and then click Android Application. The Android Emulator will launch with a locked display screen. Unlock the display screen to display the Android client.
Figure: The Android Client in the Android Emulator
- Right-click the EchoActivity project, click Run As, and then click Android Application. The Android Emulator will launch with a locked display screen. Unlock the display screen to display the Android client.
- Test the Android client in the Android Emulator.
- In the Android client running in the Android Emulator, in Location, enter ws://<Your local IP address>:8001/echo, replacing <Your local IP address> with your local IP address.
- Click Connect. The messages CONNECTING and then CONNECTED appear. The WebSocket connection was successful.
- Click Send. The message Hello World! is sent to the Echo service over WebSocket and the message is echoed back as Hello World!.
- Click the Send Binary checkbox and click Send again. The message Hello World! is sent to the Echo service over WebSocket as binary and is echoed back as:
RECEIVED: 48 65 6c 6c 6f 2c 20 57 65 62 53 6f 63 6b 65 74 21
SEND BINARY: 48 65 6c 6c 6f 2c 20 57 65 62 53 6f 63 6b 65 74 21
- To test the Android client on an Android device, ensure USB debugging is enabled on your Android device (Settings > Applications > Development), and plug the device into your system.
- In Eclipse, right-click the EchoActivity project, select Run As, and then click Android Application.The Android client is deployed to your Android device. Follow the location, connection and send steps to test the client on your device.