Android WebSocket. Feedback, or When every second matters

Datetime:2016-08-22 22:29:50          Topic: WebSocket  Design Pattern  Andrew Development           Share

Apps that are using network communicate with the server by executing queries and receiving answers. Server acts as an information desk providing answers to any questions and accepting inquiries made in a certain way to further process and save them. This communication model allows your service to perform most of the required tasks. And what if you need to tell the client that the required data has already arrived? Let’s take a look at this situation using the information desk analogy.

So the client needs some important info and contacts the information desk to get it. After submitting the question he gets a reply (for the purpose of this task it doesn’t matter which one exactly). Some time passes, help desk acquires new information the client might need. What to do next?

Of course, we can inform the client about news when he contacts the help desk next time (Long pulling). While it isn’t very convenient, doing so solves part of the problem. But what to do if we’re dealing with information that quickly expires? We need a two-way communication channel between the client and the information desk, such as a phone (WebSocket).

The information desk has client’s number so it can call him anytime to provide updated info, right? Well, there’s a problem here too. Client might not pick up the phone. Then information desk staff sends him a text message (Push notification) about the information update. Depending on the volume of data, it might be enough or the client might need to contact the information desk to learn about the details.

This operating principles are used by most of the famous services where the data actuality is important and information can expire in seconds.

What is WebSocket and how apps use it

WebSocket is a protocol providing full-duplex communication channels over a single TCP connection. It is used for exchanging messages between a client and a server in real time. Currently API Web Sockets are being standardized by W3C. Standard draft has already been approved by IETF (Wikipedia) . Let’s skip the details of protocol implementation since there’s a lot of technical info on that all over the Internet. Here I’d like to talk about use cases for WebSocket, simplified business logic and auxiliary libraries .

Where to use it? WebSocket will help developers who are creating an app with intense data exchange, require high exchange rate and a stable channel. Here’re a few examples of apps using socket for their business logic. Currency quotation, stock prices, stock market statistics — all this require real time monitoring and client data is constantly updated via socket connections. More examples:

  • In text chats, no matter how many users are logged in, all chat participants are practically simultaneously receiving messages from each other over the same connection.
  • When using booking services we are expecting our request to get a prompt answer (for example, from a taxi driver) or server to book a service for us (for example, a seat in the movie theater) notifying us and other users that a particular seat is booked.

Of course, it is a simplified view and in reality business logic is more complicated than this. But nonetheless a socket connection does a key job here by maintaining a two-way (full-duplex) communication : a server can contact a client at any moment and a client can reply it and vice versa.

Implementing WebSocket connection for your app

So at first we had chosen Java-WebSocket library supporting RFC 6455 standard for implementing socket connection. But it soon turned out that on some Android versions it damages packages when using wss-connection. We haven’t recreated the exact sequence for this problem but since the rep already contained several messages about this kind of problem, we’ve decided to switch to something else. The next suitable options was nv-websocket-client that turned out to be a good one.

nv-websocket-clientimplements a full stack of methods and interfaces for working with a socket connection. For developers who have at least some experience in writing network apps documentation would be quite easy to understand.

When starting with socket implementation for the app, you think: what’s the best way, from the architecture standpoint, to implement connection within the app? Based on the connection logic and its life cycle it should be a component that works wherever the user is at any given moment.

Implementing connection as a separate service is both inconvenient (since the latter needs constant binding) and irrational (since we need the connection only when the user is active). You can implement the connection with a component that will be responsible for communication within a separate channel , keep an instance of this component in Application of your app and manage its state through the public methods, for example like this:

public class ExampleApp extends Application {
 
   private ExampleSocketConnection exampleSocketConnection;
 
   @Override
   public void onCreate() {
       super.onCreate();
 
       exampleSocketConnection = new ExampleSocketConnection(this);
       BackgroundManager.get(this).registerListener(appActivityListener);
   }
 
 
   public void closeSocketConnection() {
       exampleSocketConnection.closeConnection();
   }
 
   public void openSocketConnection() {
       exampleSocketConnection.openConnection();
   }
 
   public boolean isSocketConnected() {
       return exampleSocketConnection.isConnected();
   }
 
   public void reconnect() {
       exampleSocketConnection.openConnection();
   }

The component and the client are available on our GitHub .

Let’s take a closer look at ClientWebSocket . Simple constructor/builder gets a callback for returning messages and connection address as input. Client maintains a wss connection that opens in a separate stream. Interaction with connection is carried out using WebSocket instance for sending messages and SocketListener for receiving them.

sendText() method is used for sending messages. Binary sequences can also be sent using SocketListener . SocketListener also has a number of methods for receving and we have overridden some of them:

public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
public void onTextMessage(WebSocket websocket, String message)
 
@Override
public void onError(WebSocket websocket, WebSocketException cause)
 
@Override
public void onDisconnected(WebSocket websocket,
                          WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame,
                          boolean closedByServer)
@Override
public void onUnexpectedError(WebSocket websocket, WebSocketException cause)
 
@Override
public void onPongFrame(WebSocket websocket, WebSocketFrame frame)

Most of the methods are related to life cycle connection but we will talk about it later. Now we are interested in onTextMessage() . In this case we are receiving text messages that contain json wrapper with a data model that was sent to us. The wrapper looks like this:

public class RealTimeEvent {
 
 
   @SerializedName("event")
   private int event;
 
   @SerializedName("params")
   private JsonObject params;
 
   public int getType() {
       return event;
   }
 
   public String getUserId() {
       return userId;
   }
 
   public <T> T getParams(Class<T> type) {
       return new Gson().fromJson(params.toString(), type);
   }
 
}

After receiving a message we identify its type and use generic to get a model that was sent to us. For example, like this:

Message message = event.getParams(Message.class)

Quite simple and easy to use. Having the list of all the events that can arrive via socket we parse and update UI.

There’s also something to say about maintaining the connection. All connections are managed by the server. To understand which connection is active and which can be closed it regularly examines them . This process is called PingPong and to every Pong from the server, client should reply with Ping value. Here’s how it looks like:

@Override
public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
   super.onPongFrame(websocket, frame);
   websocket.sendPing("Are you there?");
}

WebSocket connection life cycle

What about the life cycle? It is directly affected by the business logic of your app. There’re a couple of cases for client and server behavior here:

  • On the server side : we got data that needs to be transferred. We check whether socket connection is available by sending data on the socket. If it’s not — we send a push notification.
  • On the client side : when the client is online, the connection is active and a message will arrive on the socket. If the user has switched off the screen or minimized the app, the connection is closed* and all messages arrive as push notifications.

*If you need to, you can keep the connection opened by implementing the same service and sending an instance of the component to it. But it should be justified by the business logic of the app and your server part developers should take this behavior into account since connections will be almost constant and their number will increase proportionally with the increase in the number of users.

Thus socket connection life cycle forces the developer to watch the status of the device, app, user authorization and close the connection when the status changes to offline or a user logs off . It is implemented in several ways. Receiver that is set to receive screen switch off/switch on status watches the status of the device.

private BroadcastReceiver screenStateReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
       if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
           Log.i("Websocket", "Screen ON");
           openConnection();
       } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
           Log.i("Websocket", "Screen OFF");
           closeConnection();
       }
   }
};

BackgroundManager component watches the status of the app . Here we need to explain why we need it. The thing is, it would be logical to close the connection on the onPause method of the activity life cycle. But usually we have more than one activity in the app and a reconnection occurs when switching between the screens. It could have been solved with a simple Boolean flag but there’s one more problem here: activities work asynchronously so in certain situations onStart of one of them activates before the onPause of the other and vice versa. All in all, it causes lack of transparency in connection life cycle implementation.

With BackgroundManager you don’t have to take into account activity and use Application to subscribe to onActivityResumed and onActivityPaused . On onPause it generates a delayed action that will subsequently inform you that the app was minimized. On onResume the action is cancelled if it wasn’t done yet and a notification about the app being again in focus is sent when needed.

@Override
public void onActivityResumed(Activity activity) {
   if (mBackgroundTransition != null) {
       mBackgroundDelayHandler.removeCallbacks(mBackgroundTransition);
       mBackgroundTransition = null;
   }
 
   if (mInBackground) {
       mInBackground = false;
       notifyOnBecameForeground();
       Log.i(LOG, "Application went to foreground");
   }
}
 
private void notifyOnBecameForeground() {
   for (Listener listener : listeners) {
       try {
           listener.onBecameForeground();
       } catch (Exception e) {
           Log.e(LOG, "Listener threw exception!" + e);
       }
   }
}
 
@Override
public void onActivityPaused(Activity activity) {
   if (!mInBackground && mBackgroundTransition == null) {
       mBackgroundTransition = new Runnable() {
           @Override
           public void run() {
               mInBackground = true;
               mBackgroundTransition = null;
               notifyOnBecameBackground();
               Log.i(LOG, "Application went to background");
           }
       };
       mBackgroundDelayHandler.postDelayed(mBackgroundTransition, BACKGROUND_DELAY);
   }
}
 
private void notifyOnBecameBackground() {
   for (Listener listener : listeners) {
       try {
           listener.onBecameBackground();
       } catch (Exception e) {
           Log.e(LOG, "Listener threw exception!" + e);
       }
   }
}

And don’t forget about opening a connection, authorizing a user and cancelling authorization after the user logs off. You can easily do it with Application public methods.

Observer pattern and EventBus

Observer is a behavioral design pattern . It is also known as Dependents. It creates class mechanic that allows an instance of the object belonging to this class to receive notifications from other objects about changes in their state and watch them this way (Wikipedia) . Using this pattern you implement data update within the app after receiving data in real time. Observer allows you to build a loosely coupled architecture which has a positive influence on further scaling efforts.

EventBus library helps us to implement this pattern. Again, nothing complicated here. In any observer class you can add a handler method of a particular event by adding @Subscribe annotation to it and register an observer using EventBus.getDefault().register(this) method. It is important to remember to unsubscribe observers when you no longer need them: EventBus.getDefault().unregister(this) . If you forget to do it, you will have serious memory leaks. In our case an example of an observer activity looks like this:

@Override
public void onResume() {
   EventBus.getDefault().register(this);
}
 
@Override
public void onPause() {
   EventBus.getDefault().unregister(this);
}
 
@Subscribe
public void handleMessage(RealTimeEvent event) {
   if (event.getType() == RealTimeEvent.EVENT_TEXT_MESSAGE) {
	...
   }
}

Any object can be an observer. The only requirement is that everything fits nicely with the logic of your app and correctly interacts with the life cycle. For example, implementing observer in the holder doesn’t make much sense since the latter has its own specific life cycle and can’t access data. If you move logic into adapter, the situation becomes better but, again, it isn’t quite right either: such element of architecture as an adapter shouldn’t be forced to manage data — it is only responsible for displaying it. So a right decision here would be to implement observer logic in activity or view model, depending on which kind of architecture you’re using .

Events can be sent from any context, independent of time. If observer that can process a particular event is subscribed to updates, it will receive them. Sending looks like this:

EventBus.getDefault().post(gson.fromJson(message, RealTimeEvent.class));

There you go: Greenrobot Eventbus is practically doing all the job by itself. In the end we get a handy linking bridge within the app — all the components can instantly receive and process data meant for them without having a direct connection with each other.

P.S. Additional logic that is added to the app with real time event handling. It seems to be pretty easy: receive an event, update UI, display an unread message, change user status — so where’s the trap? Well, a trap might be too harsh a word here but many developers are used to designing linear apps and in this case we have a quite serious case of asynchronous behavior. A lot of problems will emerge if in the beginning you forget to remind yourself that you’re working on the app with asynchronous logic.

For example, you had 2 Fragments with lists of the same elements that are sorted using different criteria. If an element from one of the lists updates its state, the same element in another list should be updated too. And don’t forget that this rule works vice versa as well: if the user updates the state of the element on his own, we should see the update in the second list as well. Take a second and think how you are going to implement it.

Also, lots of UI-related situations might not be foreseen by the designer and present in the mockups. For example, a user went to some screen with a content the state of which can be changed by other people who are using your app. In this case the content should be updated without confusing the current user.

Afterword

As you can see, socket implementation is not a very complex or time-consuming task. But you’ve got to consider various situations connected with the business logic of your app and only then start working on your implementation.

And here’s a GitHub link.





About List