Reflections from my first big outing with Android
Its fun! Trying to build an app that is going to be used by at least a few thousand people in a baseball stadium is not a humble task to start with if you are not an expert in Android. I tried this with my team and I can vouch that it does not come easy and may not be a great way to go if you aim to learn android to its very depth in your first attempt.
Few things that I experimented in order to achieve just enough performance to handle numerous requests are ,
- Websockets instead of HTTP
A fellow team member suggested websockets and I followed it up with some research on it. Why websockets are better than HTTP is a well debated topic, widely discussed in many forums. In short, websockets are believed to have negligible overhead in request/response in subsequent package transfers after a connection is established between the client and server. This makes it suited for low-latency requirements.
Sadly the specific library to support Websockets is a part of JAVAX package that the Android framework does not include. But woohoo! There is an amazing open source library that extends websockets for android. It works like magic.
Our application involved a situation where each click of a button has to be aggregated as a contribution to a particular choice in the server. Basically, a user can break your system by clicking say 1000 times a second. Although, this rate is not humanly possible. For those who played cookie clicker, this sort of behavior will sound familiar.
True to its claims, websockets did handle the load pretty well with about 20 concurrent users. We did not get to test it with thousands of users due to logistic issues.
2. Be very careful NOT to create multiple connections at the same time
I made this mistake in the beginning. This error can bring down all the performance in one go and defeat the purpose of you using websockets in the first place. Asynchronous programming for network and database connections in Java can be annoying at times and I made the mistake of creating connections every click because the connection objects created in the runnable thread were inaccessible from the UI thread in order to reuse them in the next click. The solution to avoid this is simple. Maintain an array of callback objects that are accessible across threads. Since the ‘array’ is final it can be accessed inside runnable. But the contents of the array are not final and can be modified by the UI thread.
3. Maintain timer and state controls in the server. Mercilessly kill timer threads when needed!
Maintaining Game states is important to manage and restore states for all users uniformly. Timers cannot go wrong even if the device is set to the wrong time if the time data is retrieved from the server. In case of a countdown, proper timing can be achieved even if the server is not set to the right time.
I also noticed some strange behavior when trying to kill a CountDownTimer thread. Ensuring that all the previously created timer threads are killed is crucial to avoid twitching of different times in the UI. Setting the timer thread to null is needed in addition to cancelling it. I resorted to allocating and killing one global timer object repeatedly. This is a possible mistake that can occur on the server side too.
4. Write a decent server. Simple and straightforward
I used Node.js to write mine. Whatever the language may be, there are thousand ways to optimize servers, follow what suits you.
Finally, I looked around in the internet and found several load testing websites but was not quite satisfied with any of them. It would have been ideal if I had written my own test harness to try and break the system. But time constraints.