Android Development Gotchas

Android Development Gotchas

This isn’t designed to be a definitive list of unexpected occurrences while developing for Android, but it is a list that I scratched down while I was picking up some skill for myself. It won’t save you from every issue but there may be cases where you can stop and think “Yeah, Dave told me that would happen”.

My development environment included Eclipse 3.5 with the Google ADT plugin and all testing was against a rooted HTC Desire and unroot Samsung Galaxy SII both running Android 2.2, development code targeted Android 2.1. Problems may be an issue with ADT, HTC, Samsung, Android 2.1 or 2.2, rooting or the specific hand sets ūüôā

Java 1.6 on a Dell XPS 1530 running Ubuntu 10.10, if that matters.

Virtual Devices

I found these too slow and unusable.

Early on in development I gave up trying to use the the ADT virtual devices and tested directly against the target hardware devices. Luckily for me the application was for internal use only and would be released to specific clients with specific handsets, but I was disappointed at how slow the VMs were start and how slow they ran. The develop-deploy-test cycle was just too slow using the ADT.

Virtual Devices Cannot Support Bluetooth

ADT Virtual Devices cannot support Bluetooth, and as our application includes Bluetooth communication this was another reason ADT development was excluded.

Inconsistent Bluetooth Support Between Vendors

This was a strange one to us, and not one that we have (to this point) been able to resolve although my guess is that it is a problem with the HTC code.

Our application involved having a file sent to the phone using OBEX over Bluetooth. No problems so far. Pair the devices, enter the code, initiate the file transfer.

The Samsung Galaxy SII displayed the preferred behaviour, where the incoming file prompt the phone user to accept the file, but also provides a checkbox to always accept files from this device. If you don’t select the checkbox then you are prompted each time a file arrives, but you are still able to check the box later on.

When the first file arrives, HTC Desire asks if you want to allow the remote device to synchronize contacts, and whether you always want to allow this. If you don’t allow this, you aren’t able to get to the next step where you accept the file. If you don’t accept contact swap all the time you’ll be prompted each time. Regardless of whether you swap contacts or not, you must accept each file as it comes in. Accepting every file manually is not our preferred behaviour.

Android Apps Run on a Single Thread

Some of this cuts into Android’s Handlers, task scheduling and communication across threads, but it may come as a surprise during development that all standard processing occurs on a single Thread. This is something that you’ll want to play with and become familiar with as it may be surprising that UI performance is impacted by Runnables and Services.

I do recommend you have a play.

Creating new Threads is one option, although Android’s AsyncTask is the preferred mechanism for truly asynchronous processing that doesn’t freeze the GUI.

… and that Thread is a UI Thread

Following on from the previous point, the main thread is a UI thread and that that thread is only active when the UI is active. Sort of. Check the Android fundamentals processes and threads page, but just be aware that if you want something to process while (for example) the screen is locked, you need to be aware of the processing thread.

Beware Empty Elements in XML Layouts

If you’re like me, you like tidy XML and prefer an empty element (where applicable) to an element which is not ’empty’ but has no child elements.

	<!-- this element has no children -->
	<ListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@android:id/list"></ListView>
	<!-- this is an empty element -->
	<TextView android:id="@android:id/empty" style="@style/EmptyContent" android:text="@string/emptyResponseEntries" />

In this example, the TextView is empty and causes no problems, but the ListView will. If you make the ListView an empty XML element, you can write items to the List, but they won’t show up on the screen. There won’t be any error reported, it just won’t show up.

Can’t Read Property Defaults

Property defaults were also something I found perplexing. If you specify a properties.xml file, each property is able to define a default value. Likewise when reading properties via the SharedPreferences you can specify a default value if the property doesn’t exist, but the two default values aren’t related. You can’t read the default value as defined in the preferences.xml file for use elsewhere, the API doesn’t provide access.

Note that recommended properties management is via the PreferenceFragment

Notifications Can Be Empty

I found this one amusing. It is possible to create an empty Notification using new Notification(), specify a notification sound, and annoy users without providing any indication of where the annoying sounds are coming from.

Improving the custom Android Gallery

I was looking for a custom gallery for displaying and selecting images on an Android phone, and while I found the sample code here to be a good start it needed some work before it was useful.

Before starting, it is worth pointing out that the Android API supplies a perfectly useful image gallery, and the StackOverflow thread how to pick a image from gallery (SD Card) for my app in android? has everything you need to get plug it into your own application.

If you require customised behaviour it isn’t too hard to get something rough working, so go to the original thread and create the ImageThumbnailsActivity and ViewImage classes, then configure the AndroidManifest.xml, res/layout/main.xml and missing res/values/strings.xml files.

The layout specifies a single GridView to hold the images, and the main ImageThumbnailsActivity activity performs two main jobs. First it loads all existing thumbnails and provides the code necessary to interact with the thumbnail if it is selected, and it has a nested adapter to manage the display of the thumbnails using a Cursor position into the GridView.

There are quite a few comments in the original posting indicating that the code is not quite right, and if you run it on a phone with a decent history of photos you’ll get some unusual behaviour. The two main issues are that firstly there are far more thumbnails displayed than there are images stored, and secondly that the images displayed are repeated as you scroll through the gallery.

The first issue can be resolved using the StackOverflow thread here and involves initially loading the full images rather than the thumbnail, but associating with the thumbnail when displaying on the grid. This means that we don’t try to show a thumbnail for an image that no longer exists or is incorrect for some other reason.

The second issue is a little more involved, but related to the fact that the nested ImageAdapter class will attempt to reuse View instances if possible (in the getView method), and the code provided assumes that if a View is returned to be reused that it is correct, which it is not. The GridView only creates as many View instances as required to fill the screen. Once you scroll to a new section it asks if you want to reuse the instances that have scrolled off the screen. Obviously this is an acceptable reuse of resources, but the reused instances will be showing the thumbnail for a different picture and will need to be updated.

As a matter of style, I also made the following changes:

  • Remove all of the System.gc() calls, they aren’t required.
  • close cursors
  • make variables local where applicable
  • as stated we load images from the MediaStore.Images.Media rather than the MediaStore.Images.Thumbnails but return the thumbnail for display.
  • specify the MediaStore.Images.Thumbnails.MICRO_KIND for display. The benefit here is that the images are the size we intend to show and eliminited scaling. Feel free to play with settings but I found this the most pleasing layout and matches the existing Android gallery

Finally, if you look up the thumbnail when the View is visible on the screen, the scrolling is slow and limited to a row at a time rather than being able to race past images they do not want. Since we specify the thumbnails as the smallest size, I chose to load and cache the android.graphics.Bitmap for the thumbnails so they are ready to display when required.

Just to Review

We’re doing this to provide a customised image gallery. The Android gallery is fully functional and should suit most needs, but if for example you also needed to display the file name or size, date or other information, this solution allows a custom View on the GridView.

Everything else remains the same, and the updated ImageThumbnailsActivity looks like this:

public class ImageThumbnailsActivity extends Activity
{
    private int count;
    /**
     * Caching this is a trade off in memory versus performance.
     * We deliberated use the smallest thumbnails so the size of each should be small (92x92x1 byte = about 10kb each)
     * but can be significant if there are thousands of images on the device.
     */
    private Bitmap[] windows;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_thumbnail);
        init_phone_image_grid();
    }

    private void init_phone_image_grid()
    {
        final String[] columns = { MediaStore.Images.Media._ID };
        final String orderBy = MediaStore.Images.Media._ID;
        Cursor imagecursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, orderBy);
        int image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID);
        this.count = imagecursor.getCount();
        this.windows = new Bitmap[this.count];
        for(int i=0;i<this.count;i++)
        {
            imagecursor.moveToPosition(i);
            int id = imagecursor.getInt(image_column_index);
            windows[i] = MediaStore.Images.Thumbnails.getThumbnail(getApplicationContext().getContentResolver(),
                    id, MediaStore.Images.Thumbnails.MICRO_KIND, null);
        }
        GridView imagegrid = (GridView) findViewById(R.id.PhoneImageGrid);
        imagegrid.setAdapter(new ImageAdapter(getApplicationContext()));
        imagegrid.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(@SuppressWarnings("rawtypes") AdapterView parent, View v, int position, long id)
            {
                String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID };
                Cursor actualimagecursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null);
                final int dataColumnIndex = actualimagecursor.getColumnIndex(MediaStore.Images.Media.DATA);
                final int idColumnIndex = actualimagecursor.getColumnIndex(MediaStore.Images.Media._ID);
                actualimagecursor.moveToPosition(position);
                final String filename = actualimagecursor.getString(dataColumnIndex);
                final long imageId = actualimagecursor.getLong(idColumnIndex);
                final Intent intent = new Intent(ImageThumbnailsActivity.this, ViewImage.class);
                intent.putExtra("filename", filename);
                intent.putExtra("dataUid", imageId);
                actualimagecursor.close();
                startActivity(intent);
            }
        });
        imagecursor.close();
    }

    public class ImageAdapter extends BaseAdapter
    {
        private Context mContext;

        public ImageAdapter(Context c)
        {
            mContext = c;
        }

        @Override
        public int getCount()
        {
            return count;
        }

        @Override
        public Object getItem(int position)
        {
            return position;
        }

        @Override
        public long getItemId(int position)
        {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            ImageView i = (ImageView)convertView;
            if(i!=null)
            {
               i.setImageBitmap(windows[position]);
            }
            else
            {
                i = new ImageView(mContext.getApplicationContext());
                i.setImageBitmap(windows[position]);
                i.setLayoutParams(new GridView.LayoutParams(92, 92));
            }
            return i;
        }
    }
}

Live from TSS-JS – Mobile Development with Mark

Currently attending “Comparing, Contrasting, and Differentiating Between Mobile Platforms” by Mark Spritzler, a fellow CodeRanch moderator. ¬†The presentation is in part an open discussion with the audience of what people have tried and works well in the Mobile environment.

1.  What is out there?

  • Android OS (Java)
  • Apple iOS for iPhone/iPod/iPad
  • Web applications with custom UI for mobile applications. ¬†CodeRanch currently offers a mobile web version of the website
  • J2ME (not common)
  • BlackBerry (custom Java)
  • SymbianOS (C)

2.  Android OS Review
Built by Google and uses Java and can run Flash. ¬†UI built with declarative UIs using XML primarily and supports visual tools such as Droid Draw and/or Interface Builder. ¬†MVC-like architecture with view as XML, and control/model as Java classes. ¬†The API is quite open so there’s a lot of ability to customize for developers.

There is currently a large variety of Android devices so splintering of the code base could be in the future.  Some devices cannot upgrade the Android OS, leading to permanent branching of code base.

Also, Android requires a lot of manual work to integrate with a database, such as SQL Lite, whereas Apple iOS has this built in.

3.  Apple iOS Review
Built by Apple and uses Objective-C, and cannot run Flash.  Developers must manage memory manually.  The API is completely proprietary and there are limit tools for developers.  For example, the developer must have a Mac and use an xCode.  Closed APIs but Apple promises stability (although it did change in iPad with split/view feature).

Dicussion on Apple’s strigent application approval process followed. ¬†One participant commented that they waited 1-2 months for Apple to approve it. ¬†Apple has also stopped approving ‘pointless’ apps. ¬†I asked Mark if he thinks the delays are worth the improvement in quality, to which he replied that it does lead to better applications. ¬†He also informed the audience that Apple wants you to use certain visual controls in particular manners to help build a consistent UI, and may reject applications based on improper usage. ¬†Apple sometimes comments on why applications are rejected but not always.

4.  J2ME Failure Review
Idea was to develop using Java and runs on a variety of devices. ¬†One of the major problems is Sun certified J2ME mobile phones that didn’t properly or fully implement the spec. ¬†Also, lead to splintering of code base and very inconsistent results across devices.

5.  Native vs Web applications
Web applications have greater reach since they can run on many devices, but have weaker performance and require the developer to self-promote them.

6.  App Generating Frameworks
Build mobile applications from predefined templates using a CMS system often entered in a web browser, such as MobileRoadie, but it is a paid service.  Builder frameworks (often open source) that generate mobile applications based on existing code including Appcelector, Rhomobile, PhoneGap.

Write once and run on many devices through generation.  They may have limited functionality since they use a subset of features available in the language.  Multi-touch is also very limited in Android over iOS.  HTML5 does support location-aware so it can help in application generation.

Conclusion
Mark ended the presentation with an open discussion asking people to share their own mobile development experience.  He pointed out that there a lot of pros and cons to using different mobile platforms and mobile devices, and you should consider the resources on hand when deciding how to proceed in development.