Monday, 13 December 2010

String.format is a nono...

Flattr this

I have been working this weekend on an application called "WiFi Wraith" (Already on the market, there's a free edition too).

This is the first time I am working on cavases, bitmaps and programmatic drawing. I draw pretty graphs straight on some UI elemts, which I have a ListView full of. This is done each time the app rescans the networks.
I realized quite fast, that I am doing something silly with my drawing, or so I thought, because the ListView got kinda jittery while dragging. My first obvious idea was to offload the graph drawing to another thread, cache the bitmaps and fetch the "latest" bitmap whenever it is needed.
However, out of curiosity, I also wanted to know how to do profiling on Android! So a quick googling told me that the Android team has made it pretty damn easy!
Only thing I need to do is to call Debug.startMethodTracing() when I want to start logging and Debug.stopMethodTracing() when I want to stop it. Then I can visually inspect the produced logfile by pulling it from the root of the sdcard and use 'traceview' from the android-sdk/tools/.

So, I tried this. My first suspectible suspects of suspicion were these:

Suspect  #1:

private static void drawData(Canvas canvas, List signalHistory) {
 float prevx, prevy;
 float w = canvas.getWidth();
 float linelen = (float) Math.ceil(w/signalHistory.size());
 prevx = prevy = 0f;
 float xpoint = 0f;
 boolean first = true;
 int datanum = 0;
 for(Integer ia : signalHistory) {
  int q = WifiManager.calculateSignalLevel(ia, 10); 
  float ypoint = canvas.getHeight();
  ypoint = ypoint - ypoint / 10 * q;
  if(ypoint < 1) ypoint = 1;
  Paint p = selectBrush(q);
  if(first) {
   first = false;
   prevx = xpoint;
   prevy = ypoint;
  xpoint += linelen;
  canvas.drawLine(prevx, prevy, xpoint, ypoint, p);
  int makeline = datanum % 10; 
  if(makeline == 0) {
   canvas.drawLine(xpoint, canvas.getHeight(), xpoint, ypoint,; 
  prevx = xpoint;
  prevy = ypoint;

Suspect  #2:

if(signalHistory.size() > 100) {
 signalHistory = new ArrayList(signalHistory.subList(signalHistory.size()-100, signalHistory.size()));

Sooo.... Let's fire up traceview and see which one of these is the culprit!

Wait! What the hell! It seems that neither of them!
Drawing takes 10% of time. Resizing of the signalHistory takes pretty much nothing at all...

Most of the time the program is doing this:

if(lastSeen < System.currentTimeMillis() - 10000) {
 Date d = new Date(lastSeen);
 String tstr = DateFormat.getDateTimeInstance().format(d);
 out = String.format(ctx.getString(R.string.item_format_oor), 
   data.SSID, data.BSSID, tstr, locstr);
else {
 out = String.format(ctx.getString(R.string.item_format), 
   data.SSID, data.BSSID, data.level, data.frequency, 
   data.capabilities, locstr);   

The bloody String.format is killing my app! I just have to try if this really can be the problem, so I'll just comment out the string formatting...

Well, yeah. It made it hell of a lot smoother. Another thing that seems to take a lot of time is populating the list with new items. I have no idea how to side step that.
In fact, I'm not sure how to fix the string formatting. It's kinda crucial for an app to actually show some meaningful info. I recall from some old Android statistics that creating objects is very slow on Android, so I try to avoid it. Based on the old stats, I think concatenating assload of strings and manually converting items to strings should be even slower.

Update: Holy hell! Just concatenating the stings with stupid string + int + float + string, etc... Is faster by about factor of 100!