Why mobile web apps are slow

Why mobile web apps are slow

Mobile Phone with cloud of application icons flying arround
Why mobile web apps are slow.

A great fact-driven discussion, on the challenges facing mobile application developers, with regard to speed and memory management.

Blackberry Numeric Formatted Output – You’re SOL

Blackberry Numeric Formatted Output – You’re SOL

If you’ve tried to do some basic formatting for – oh say – currency or thousands-separated floating point numbers in Blackberry Java, you quickly realize something:

You’re spit outta luck.

For you old C developers, I daresay you can’t do without printf, fprintf, or sprintf for doing external and internal string formatting.

But somehow, the geniuses behind Blackberry failed to supply one of the most basic elements used by developers in their apps – the ability to easily format numeric strings for output.

It’s not like Java doesn’t have a Formatter class – it’s just that Blackberry’s version of it doesn’t support locale-specific decimal points and separators. So, something like Formatter fmt = new Formatter("###,###.00") – which works like a charm in most Java environments and on Android – is exception city on Blackberry.

In short, you’re screwed.

There is a solution – write your own formatter (ugh).

The fine folks at Sun do have a sample implementation of a Java printf analog (http://java.sun.com/developer/technicalArticles/Programming/sprintf/PrintfFormat.java). Unfortunately, they prohibit it’s use in commercial applications. Which means it’s worthless as a solution for professional developers.

This may seem like a trivial thing.

Except that you have to take time away from doing real project work to fix a shortcoming in the Blackberry SDK, so that your app can have basic numeric and currency string formatting.

And for those playing along at home, time is money. You heard it here first.

There really isn’t an excuse for this type of shortcoming, in a product line years on the market.

Always Challenge Your Assumptions

Always Challenge Your Assumptions

I spent the better part of the weekend trying to fix a bug that was perplexing me and arresting my progress on a new Blackberry project.

Not that you particularly care, but it had to do with how Blackberry creates “tunnels” (connections) to communicate over the internet. There are about a dozen different ways to connect (BIS, BES, Direct/TCP, WiFi, WAP, WAP2, in HTTP and Sockets variations), and if you don’t hold your tongue ** just right ** your connections won’t happen.

Anyway, almost all of my connections were working perfectly. After all, this wasn’t my first Blackberry project and I was using a library I had developed and used on other projects.

But I had one call that was failing every time I tried to run it on a real device (and maddeningly, working perfectly under the simulator).

What was going on? I mean, I’ve used the library before with no issues whatsoever.

But I hadn’t used it in ** just ** the way I was now using it; and there was the rub.

As it turned out, I was using a call in the library that I had never used before.

And because of this, there was a place in the code where I had neglected to correctly determine the type of connection I needed to make (again with the BIS, BES, Direct/TCP, yada, yada, yada) and so my connections were failing on a real device.

It only took me two days to realize that I needed to challenge my assumptions that the library was right… and if I had done what I should have done originally (namely, examine directly the call in question) I would have had more time to myself and my family this weekend.

For the more technically inclined, I present the code I usually use to create internet connections for Blackberry apps below:

public static final int CONNECTION_DEFAULT = 0;
public static final int CONNECTION_BIS = 1;
public static final int CONNECTION_BES = 2;
public static final int CONNECTION_TCPIP = 3;
public static final int CONNECTION_WIFI = 4;
public static final int CONNECTION_WAP = 5;
public static final int CONNECTION_WAP2 = 6;

public static HttpConnection makeHttpConnection(String url,
HttpHeaders requestHeaders,
byte[] postData,
int connType,
String requestMethod,
boolean uploadData)
{
HttpConnection conn = null;
OutputStream out = null;

if (StringUtilities.startsWithIgnoreCase(url, "www.")) {
url = "http://" + url;
}

try {
if (url.indexOf(";deviceside=") == -1) {
switch (connType) {
case CONNECTION_BES:
url = url + ";deviceside=false";
break;
case CONNECTION_BIS:
url = url + ";deviceside=false;connectiontype=mds-public";
break;
case CONNECTION_TCPIP:
url = url + ";deviceside=true";
break;
case CONNECTION_WIFI:
url = url + ";interface=wifi";
break;
case CONNECTION_WAP2:
url = buildWAP2(url);
break;
}
System.out.print("\n****\nThis is what we're trying to open: " + url);
}

conn = (HttpConnection) Connector.open(url, Connector.READ_WRITE);
try {
if (1==0) {
ConnectionFactory connFact = new ConnectionFactory();
TransportDescriptor[] transports = TransportInfo.getAvailableTransports();
System.out.println( "Available transports: " + transports.length );
for ( int i = 0; i < transports.length; i++ ) { TransportDescriptor t = transports[ i ]; int type = t.getTransportType(); boolean hasCoverage = TransportInfo.hasSufficientCoverage( type ); System.out.println( "Type: " + TransportInfo.getTransportTypeName( type ) ); System.out.println( "Coverage: " + hasCoverage ); System.out.println( "Cid: " + t.getCid() ); System.out.println( "Uid: " + t.getUid() ); System.out.println( "\n" ); } int preferredTransportTypes[] = {TransportInfo.TRANSPORT_BIS_B, TransportInfo.TRANSPORT_TCP_WIFI, TransportInfo.TRANSPORT_MDS, TransportInfo.TRANSPORT_WAP2, TransportInfo.TRANSPORT_TCP_CELLULAR,}; ConnectionDescriptor connDesc; connFact.setPreferredTransportTypes(preferredTransportTypes); connDesc = connFact.getConnection("http://myserver.gr"); if (connDesc != null) { conn = (HttpConnection) connDesc.getConnection(); final int code = conn.getResponseCode(); System.out.println("Code: " + code); } else { System.out.println("No connection"); } } } catch (Exception e) { System.out.println("Exception: " + e.getClass().toString() + " -> " + e.getMessage());
}

if (requestHeaders != null) {
String referer = requestHeaders.getPropertyValue("referer");

boolean sendReferrer = true;

if (referer != null &&

StringUtilities.startsWithIgnoreCase(referer, "https:") &&

!StringUtilities.startsWithIgnoreCase(url, "https:"))
{
sendReferrer = false;
}

int size = requestHeaders.size();
for (int i = 0; i < size;) {
String header = requestHeaders.getPropertyKey(i);
if (!sendReferrer && header.equals("referer")) {
requestHeaders.removeProperty(i);
--size;
continue;
}

String value = requestHeaders.getPropertyValue(i++);
if (value != null) {
conn.setRequestProperty(header, value);
}
}
}

conn.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.0");
conn.setRequestMethod(requestMethod);

if (requestMethod.equalsIgnoreCase("PUT") || requestMethod.equalsIgnoreCase("POST")) {

if (postData!=null) {
conn.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(postData.length));
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
out = conn.openOutputStream();
out.write(postData);
out.flush();
}
}
}
catch (IOException e1) {
System.out.println("makeHttpConnection: " + e1.toString());
close(conn, null); // Close the connection
conn = null;
}
finally {
close(null, out); // Close the output, but keep connection open
}

return conn;
}

public static int getCoverageBasedConnectionType() {
int preferredTransportTypes[] = {TransportInfo.TRANSPORT_BIS_B, TransportInfo.TRANSPORT_TCP_WIFI, TransportInfo.TRANSPORT_MDS, TransportInfo.TRANSPORT_WAP2, TransportInfo.TRANSPORT_TCP_CELLULAR,};
ConnectionFactory cf = new ConnectionFactory();
cf.setPreferredTransportTypes(preferredTransportTypes);
ConnectionDescriptor cd = cf.getConnection("http://www.google.com");
if (cd!=null) {
TransportDescriptor transportUsed = cd.getTransportDescriptor();
int _transportMode = transportUsed.getTransportType();
System.out.println("\n\n_____\n\nTransportType: " + _transportMode + "\n\n_____\n\n");
switch (_transportMode) {
case TransportInfo.TRANSPORT_BIS_B:
System.out.println("Transport: BIS");
return CONNECTION_BIS;
case TransportInfo.TRANSPORT_MDS:
System.out.println("Transport: MDS");
return CONNECTION_BES;
case TransportInfo.TRANSPORT_TCP_CELLULAR:
System.out.println("Transport: Cell (Direct TCP)");
return CONNECTION_TCPIP;
case TransportInfo.TRANSPORT_TCP_WIFI:
System.out.println("Transport: Wi-Fi");
return CONNECTION_WIFI;
case TransportInfo.TRANSPORT_WAP:
System.out.println("Transport: WAP");
return CONNECTION_WAP;
case TransportInfo.TRANSPORT_WAP2:
System.out.println("Transport: WAP2");
return CONNECTION_WAP2;
default:
System.out.println("Transport: WAP");
return CONNECTION_TCPIP;
}
}

return CONNECTION_TCPIP;
}

static String buildWAP2(String url) {
ServiceBook sb = ServiceBook.getSB();
ServiceRecord[] records = sb.findRecordsByCid("WPTCP");
String uid = null;

for(int i=0; i < records.length; i++)
{
//Search through all service records to find the
//valid non-Wi-Fi and non-MMS
//WAP 2.0 Gateway Service Record.
if (records[i].isValid() && !records[i].isDisabled())
{

if (records[i].getUid() != null && records[i].getUid().length() != 0)
{
if ((records[i].getUid().toLowerCase().indexOf("wifi") == -1) &&
(records[i].getUid().toLowerCase().indexOf("mms") == -1))
{
uid = records[i].getUid();
break;
}
}
}
}

if (uid != null)
{

//open a WAP 2 connection
return url + ";ConnectionUID=" + uid;
}
else
{return "";}
}