I’ve been meaning to write down some general impressions from my first attempt at a SIP app on Android, specifically my RTTApp. This was my first time using JAIN SIP and Android, so it was a bit rough, but I eventually got it all working reasonably well. Surely any future attempt at using either of those will go better, and the things that bothered me might not so much later, but there really were quite a few things that made this harder than I expected.
First of all, this full-featured framework was way more full-featured than I expected. It’s fairly imposing. Since I don’t have a ton of software development experience, it might not have been such a great choice to dive into this one. I wanted to make a simple client, but this library can be used for all sorts of other SIP apps, like servers and proxies. If you don’t need that, and you really just want something one step up from the useless Android APIs, you might try looking around to see if anything else will meet your needs. JAIN SIP is not easy to use the first time.
My usual method of using some new library is to look at some example code, try making a few calls, and check the documentation when I need it. In this case, that does not work. You need to know how the whole things fits together before you get very far, and what things it does or doesn’t do for you. You need to know about its various layers. Read (really read) the lengthy documentation for SipStack, SipProvider, SipListener, and Dialog. And consider a book. There are plenty of short examples online showing how to send your first REGISTER message, but you really might want the conceptual overview and detail that an actual book could provide.
For example, while it handles some things like retransmissions (usually) under the hood for you, there are other cases where it hardly does anything for you at all. You won’t get very far if you don’t know anything about SIP. You can’t just say, “here’s a SIP address, please call it”. You need to construct all the messages yourself, and fill in most of the details, so you’d better know which headers are required on different types of requests. The Request and Response classes are basically dumb containers, and it’s up to you to know what to put in them. There’s very little hand holding in constructing these messages. You will be participating in SIP interactions and had better know what messages to expect in response, no way around it. This guy really loves SIP and wrote a number of posts that I found useful in understanding all sorts of SIP interactions.
When sending those messages, you need to be careful about how you handle client and server transactions created by the SIP stack. Sometimes you have to request a new transaction from it that you use immediately. Sometimes it seems to know what to do and has a transaction for you. Sometimes a RequestEvent has an existing transaction that you need to use to respond. But sometimes you need to hang on to a transaction you created yourself, and use that one specifically for a certain message. This all felt like a real mess to me, but maybe I just don’t understand SIP well enough. Then again, JAIN SIP’s notion of transactions is apparently not the same as in the actual SIP standard, or is that dialogs? To be honest I’m still no expert. This probably sounds stupid to more experienced JAIN SIP users, but that’s kind of my point; trying to use this framework for the first time makes you feel stupid, and then you start to ask if maybe the people who wrote it were the stupid ones, and I still don’t really know.
When you receive a message, it’s up to you to know the context for it. Is it a response to a request you sent? Is it for someone else entirely? Is it just some garbage that’s floating around the internet? The SIP stack takes very little responsibilty for correlating related messages and responses. My app doesn’t actually do enough of this, but I now know that you should probably keep some kind of data structure with the messages you’ve sent and received so you can relate future ones to them, and also have existing transactions and dialogs available if you need them. For all the complexity of the various layers of the SIP stack, it seems like it doesn’t keep track of that much for you. You have to keep track of some of its own state yourself, e.g. the state of transactions and dialogs, if you really want to be thorough and careful.
Issues with JAIN SIP on Android
One issue I had in trying to write a SIP app on Android was in the logging. When writing logic to handle incoming SIP messages, I wanted to see what messages were received, so I tried to write them to the log, but basically none of them were showing up. This turned out to be entirely Android’s problem, and had nothing to do with JAIN SIP, but it was caused by trying to log large SIP messages. SIP requests can be pretty long, compared to the stuff one normally writes in a log, and Android refuses to handle this. The Log class silently eats any messages it deems “too long” (seems to be around 100 chars?), and there is no way to know this has happened. Indeed, there is no way for a new developer to know this can happen, because it is not documented. However well-intentioned the functionality is, doing it silently and secretly is colossally stupid and rude, which is typical of Android, but more on that later.
Speaking of logs, JAIN SIP wants to use Log4j to handle its own logging, which I gather is common enough on desktop Java apps but unnecessary on Android. Since Log4j is not normally present, including the JAIN SIP jar will cause your Android build to fail right off the bat. You need to include the log4j.jar in your libs directory, even if you don’t plan on using it. Alternatively, I suppose you could modify JAIN SIP from source to exclude this and recompile it yourself, but that sounds a lot harder than simply including this other jar in what will probably be a multi-MB app anyway.
After managing to receive SIP messages, I encountered another Android hiccup when trying to send some. ClientTransaction.sendRequest(), for example, will fail with a NetworkOnMainThreadException. This is an effort to make the UI thread smoother by preventing people from trying to wait on TCP sessions, and the solution is to do network stuff in an AsyncTask subclass. This is not something you will see in any JAIN SIP example code, since they all assume that Java desktop apps can handle the network however they like.
Doing some network stuff in a background thread is probably a good idea, so I appreciate the strong suggestion on the part of Android, but in this case I’m not sure how much sense it makes. I did use a number of AsyncTasks for sending my various kinds of messages, but it really created a lot of hassle in JAIN SIP. AsyncTask is kind of a pain generally, since it has a really general API that has to work for any kind of data you are passing in and returning. When sending UDP messages, as one generally does in real-time apps, this may not be necessary. You can disable this strict thread policy, but if you do, you’ll need to be careful that your app isn’t doing any other TCP stuff on the main thread that you didn’t consider. Note that the StrictMode.ThreadPolicy documentation is wrong, and LAX is not the default.
Wrong docs, eh? That brings us to…
Other Android Gripes
Warning: this section is basically complaining.
The first thing to say about Android specifically is that it can’t do much in the way of real-time SIP stuff except for really basic audio calls. Hence, the need for JAIN SIP. It’s too bad, though. Android’s APIs make it straightforward to establish audio calls, so why not any other type of call? Did they really never think that phones would be used for something besides, you know, talking on the phone? If they are going to offer this interface at all, it would really be best if they would put some effort into beefing up this functionality to make a more approachable interface than JAIN SIP. I would understand if this were the first version of this API, but it’s been like this for over 5 years. I guess they’re too busy with ugly watches and ways to make you crash your car ::eyeroll::
But this also gets to a larger problem with Android. This simple API doesn’t do much, but the ones that do are complex. To be sure, that is a feature of APIs in general: the more you do, the more complex it is. But to me — and yes I’m still fairly new at this — Android just seems pretty bad. It’s a little hard for me to judge that, not having used a system this big before, so I tried to see if other people feel the same way. These people sure do, so I don’t feel quite so dumb now. It doesn’t help that the official tutorials I’ve been using are well out of date and in some cases not even correct at the time they were written (like, syntax errors, seriously). It’s changing so fast that there’s all this crap in there from “old” ways of doing things a couple years ago, and nothing is removed yet, and it’s hard to know if there is a good reason to use the “new” way or not. Sometimes the docs say some new way is preferred, but not why, or when. (And of course a lot of them are wrong or incomplete. I appreciate that writing and updating really good docs is hard, but you’d think one of the most successful software companies in the world could handle this)
They’re adding all this complexity, but not hiding some where they should. Take the SQLiteDatabase. The API here looks like it probably comes straight from the database. There’s a cursor you have to move once a query is returned. Why? Is that really necessary for most Android uses? Some database concepts don’t make sense when you’re dealing with a simple little thing most often used in simple little ways in simple little apps. Is it too much to ask to get an actual data structure back from a query, rather than mucking around with the cursor?
Another example: Cursor.getString() requires you to know the column number you queried, which you have to get from another method by looking up the column index for the column name. Why does this workflow even exist? What possible use could a smartphone developer ever have for the column number directly, when they could be querying by name? Saving this one line in the API could probably save millions of lines across all the apps in the world, and would take about 15 seconds to implement at Google. And, oh yeah, get this fuckery: the documentation says that some behavior is implementation-defined. Excuse me? Is there another implementation of Android that I’m not aware of? Maybe it’s dependent on the implementation of SQLite, but, um, I don’t recall personally selecting which implementation is used on every single Android device. Perhaps that was done by Google, who wrote this doc?
Why have they preserved this useless database interface, which many apps will need, but added their cut-down toy API on top of SIP, which is much more niche?