Bitmaps in Android ListView

loading bitmaps to display pictures while maintaining smooth Android ListView performance
- posted on October 24, 2014 by Vsevolod Geraskin in tutorials about android3 java4 code4

Photocalypse

A simple task such as displaying your gallery photos in a ListView component can lead to a significant loss of performance in your Android application, to the point where it would render the application unusable. Worse yet, the application might run out of memory and crash while loading bitmaps. Just think about user quickly scrolling through a list of hundreds of photos each the size of 3-10 megabytes. Thus, to display pictures in ListView is not straightforward and requires several performance considerations.

Caching Bitmaps

It doesn’t make sense for us to keep default ListView behavior which continuously frees up and loads our bitmaps when our users scroll back and forth through the list. Caching allows us to store bitmaps in memory and quickly retrieve them on consecutive views while scrolling. Thankfully, Android provides us with LRUCache that helps us cache our bitmaps. Thus, we can create a fairly simple class for our LRUCache.

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class BitmapCache {
	private static LruCache<Integer, Bitmap> MemoryCache = null;
	
	public static void InitBitmapCache() {
		// Get max available VM memory, exceeding this amount will throw an
		// OutOfMemory exception. Stored in kilobytes as LruCache takes an
		// int in its constructor
	
		if (MemoryCache==null) {
			final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
			// use 1/4th of the available memory for this memory cache.
			
			final int cacheSize = maxMemory / 4;
			MemoryCache = new LruCache<Integer, Bitmap>(cacheSize) {c
				@Override
				protected int sizeOf(Integer key, Bitmap bitmap) {
					// The cache size will be measured in kilobytes rather than
					// number of items.
					return bitmap.getByteCount() / 1024;
				}
			};
		}
	}
	
	public static void addBitmapToMemoryCache(Integer key, Bitmap bitmap) {
		MemoryCache.put(key, bitmap);
	}

	public static Bitmap getBitmapFromMemCache(Integer key) {
		return MemoryCache.get(key);
	}
}

The above code allows creation of LruCache with a quarter of available memory, and adding and retrieving bitmaps by key value, which is a position of an image in our ListView.

Resampling Bitmaps

Loading hundreds of full-sized image bitmaps would quickly cause our application to crash from lack of memory. Oftentimes, displaying image at fraction of the original size in a list is sufficient. The functions below help us resize the image and resample the bitmap before using it further.

//resample Bitmap to prevent out-of-memory crashes
private Bitmap decodeSampledBitmapFromString(int reqWidth, int reqHeight) {
	Bitmap bitmap;
	
	//decode File and set inSampleSize
	final BitmapFactory.Options options = new BitmapFactory.Options();
	options.inJustDecodeBounds = true;
	BitmapFactory.decodeFile(m_photoPath, options);
	options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
	
	// decode File with inSampleSize set
	options.inJustDecodeBounds = false;
	bitmap = BitmapFactory.decodeFile(m_photoPath, options);
	return bitmap;
}

//calculate bitmap sample sizes
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
	final int height = options.outHeight;
	final int width = options.outWidth;
	int inSampleSize = 1;
	
	if (height > reqHeight || width > reqWidth) {
		if (width > height) {
			inSampleSize = Math.round((float) height / (float) reqHeight);
		} else {
			inSampleSize = Math.round((float) width / (float) reqWidth);
		}
	}
	return inSampleSize;
}

Basically, calculateInSampleSize takes original image width or height and divides them by the target width or height. Returned inSampleSize then gives us a divisor to create processed bitmaps that are a fraction of the original size. Finally, decodeSampledBitmapFromString reads our bitmap from provided image path and returns resized smaller bitmap.

Loading Asynchronously

Initially, we want to load all our bitmaps in a separate thread or process, so we do not freeze our UI thread. To achieve that, we can use AsyncTask. In our case, this is also where we want to re-sample our bitmaps and place the images into cache. Therefore, our AsyncTask would look like this:

public class ImageLoaderTask extends AsyncTask<Integer, String, Bitmap> {
	...
	
	@Override
	protected Bitmap doInBackground(Integer... params) {
		//re-sample sample image in the background to 200x200
		Bitmap bitmap = decodeSampledBitmapFromString(200,200);
	}
	
	//set photoView and holder
	protected void onPostExecute(Bitmap bitmap) {
		if (bitmap != null) {
			BitmapCache.addBitmapToMemoryCache(m_position, bitmap);
			
			// to do: set imageView 
		}
	}
	
	private Bitmap decodeSampledBitmapFromString(int reqWidth, int reqHeight) {
		...
	}
	private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
		...
	}

}

Putting it all together

In the main activity, we would initialize our bitmap cache in onCreate() method:

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	
	...
	
	//initialize bitmap cache
	BitmapCache.InitBitmapCache();
}

Then, in our ListView adapter, we would add loadBitmap() function to either set ImageViews bitmaps from cache or to start a new AsyncTask.

public class GalleryAdapter extends BaseAdapter {
	...
	
	@Override
	public View getView(int position, View view, ViewGroup parent) {
		...
		loadBitmap(photo, position, holder);
	}
	
	// load Bitmap either from our cache or asynchronously
	public void loadBitmap(ImageView photo, Integer position, ViewHolder holder) {
		Bitmap bitmap = null;
		bitmap = BitmapCache.getBitmapFromMemCache(position);
		
		if (bitmap != null) {
			photo.setImageBitmap(bitmap);
		} else {
			new ImageLoaderTask(photo, m_uris.get(position).getPath(),
								m_adapter, position, holder).executeOnExecutor(
								AsyncTask.THREAD_POOL_EXECUTOR, (Integer[]) null);
		}
	}

Also, we could also do other things to optimize performance, such as implementing ViewHolder Pattern to recycle views in a static class. In our example, ViewHolder will hold two things: ImageView and its position in ListView.

// ViewHolder class
public static class ViewHolder {
	public ImageView picture;
	public int position;
}

Finally, Android has a limit of 128 simultaneous AsyncTasks and gives an error if we exceed that limit. This error is entirely possible when loading a thousand bitmaps in a ListView. One way to prevent the error from happening and to manage memory is to implement a static class to keep and control current AsyncTask count.

//static class to count number of concurrent asynctasks
public final class SyncCounter {
	private static int i = 0;

	public static synchronized void inc() {
		i++;
	}

	public static synchronized void dec() {
		i--;
	}

	public static synchronized int current() {
		return i;
	}
} 

Our AsyncTask class could then increment the count in the AsyncTask constructor and decrement in onPostExecute() method. We would check the current count against arbitrary maximum prior to launching new ImageLoaderTask(...) in List Adapter loadBitmap(...) method.

Complete Source

And, of course, all of this might not make sense without a working code, which you can access on GitHub in my gallery-in-listview repository.

comments


Thrive Pregnancy App

empowering pregnant women to make positive choices for their pregnancy
- posted on August 1, 2014 by Vsevolod Geraskin in projects about java4 android3

Problem Statement

Marginalized women in Vancouver need a portable health record that can empower and support them through their pregnancies. Many of these women have chaotic lives and face multiple challenges that would make a paper-based pregnancy passport difficult to retain. Despite these women’s financial situations, most have an Android smartphone.

Project Description

Thrive Pregnancy is an Android application that empowers women to make positive choices for their pregnancy. The project was initiated at Hacking Health Vancouver 2014 conference by two doctors from Youth Pregnancy and Parenting and Sheway Programs. Thrive Pregnancy application enables vulnerable pregnant women to access important pre-natal information and local support resources, record text-, voice-, and picture-based diary entries, appointments and test results, and keep track of needs and questions to discuss with their care providers.

After joining doctors and project team at the conference, we decided to complete the Android application pro bono over the next two months. During the project, I led a team of five people in a project management role and significantly contributed to Android development.

Thrive Pregnancy smartphone app includes:

  • Information for women to think about and discuss with their care provider relating to their prenatal care choices;
  • Information for women to think about their needs for their pregnancy;
  • Information about the care women can expect during pregnancy;
  • A place to keep notes and a record of appointments, tests and results;
  • A list of resources in Vancouver.
Thrive Pregnancy Screenshot Thrive Pregnancy Screenshot Thrive Pregnancy Screenshot

Project Outcomes

Thrive pregnancy App was well received by YPPP and Sheway staff and clients, and user acceptance testing feedback was very positive. The first production version is published on google play store: Thrive Pregnancy at Google App Store. Furthermore, the app was featured in Coach Healthcare Information Management journal. The development of the application is still ongoing.

comments


.NET POST Request

crafting .NET POST request using HTTPWebRequest object
- posted on July 11, 2014 by Vsevolod Geraskin in tutorials about .net6 http6 code4

Demo it now

Oftentimes, such as when hacking demos, we want our client side app talking to some form backend without spending too much time on it. One way to do it in a Microsoft environment is to do a quick POST request using HTTPWebRequest object. HTTPWebRequest does exactly what the name suggests and saves us a lot of time from having to write our own HTTP calls.

As simple as URL

During the first step, we would split our URL such as http://www.someserver.com/service.aspx?var1=val1.. into the address of the server and the body containing our POST variables. Of course, if we were creating a GET request, then we would not need to do that.

String url = "http://www.someserver.com/service.aspx?var1=value1&var2=value2";
string [] httpSend = url.Split('?');

Uri uri = new Uri(httpSend[0]);

Next, we would create HTTPWebRequest object using the above uri in the constructor and assign whichever HTTP header variables we need.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.AllowAutoRedirect = true;
request.KeepAlive = true;
request.UserAgent = "Past5 demo app";
request.Accept = "*/*";

Since we are POSTing to some form backend, we would also need to tell the server that we are submitting a form:

request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";

And finally, since we are sending a POST request, our URL GET variables will go into the body of our HTTP request.

byte[] content = Encoding.UTF8.GetBytes(httpSend[1]);
request.ContentLength = content.Length;
	
using (Stream stream = request.GetRequestStream()) {
	stream.Write(content, 0, content.Length);
	stream.Close();
}
                

The whole HTTP request

POST http://www.someserver.com/service.aspx HTTP/1.1
User-Agent: Past5 demo app
Accept: */*
Content-Type: application/x-www-form-urlencoded
Host: www.someserver.com
Content-Length: 23
Expect: 100-continue
Connection: Keep-Alive

var1=value1&var2=value2

P.S. And what about the response?

To obtain the response from the server, we would simply use HTTPWebResponse object and use StreamReader to get the body of the response.

HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
	
if (request.HaveResponse) String responseBody = reader.ReadToEnd();
	
...

comments


MIME Email with Attachment

creating MIME messages with attachment using Linux Shell
- posted on May 25, 2014 by Vsevolod Geraskin in tutorials about linux3 shell2 email2

Yes, we Shell!

Sometimes, we may want to have our own MIMEs (as in Mail Extensions messages, although bossing around an entourage of stripy shirt artists may be fun) for use with such programs as sendmail. While creating a simple text email using MIME is simple, adding attachments requires a bit of work and can be tricky. Thankfully, Linux Bash Shell gives us the right tools to get the job done.

In this example, let’s say we want to add a .jpg file as an attachment to our MIME message.

Step 1 - Specify attachment

We are using the file called test.jpg as an attachment.

[sev@linusaur sendmaildemo]$ attach='test.jpg'
[sev@linusaur sendmaildemo]$ echo $attach
test.jpg

Step 2 - Determine MIME type

Now, we need to determine what MIME file type we are dealing with. One command we can use is gnomevfs-info which gives us the relevant file information. At the same time, we can use pattern-matching command such as awk to find the specific information we are looking for.

[sev@linusaur sendmaildemo]$ mimetype=`gnomevfs-info -s $attach | awk '{FS=":"} /MIME type/ {gsub(/^[ \t]+|[ \t]+$/, "",$2); print $2}'`
[sev@linusaur sendmaildemo]$ echo $mimetype
image/jpeg

Step 3 - Encode attachment

MIME requires us to encode the attachment in base-64. We can use uuencode command to do that. After the encoding, we would need to remove first and last lines, which specify start and end of encoding. sed stream editor will help us easily remove those lines.

[sev@linusaur sendmaildemo]$ tempfile='attach.temp'
[sev@linusaur sendmaildemo]$ rm -f $tempfile
[sev@linusaur sendmaildemo]$ cat $attach|uuencode --base64 $attach>$tempfile
[sev@linusaur sendmaildemo]$ sed -i -e '1,1d' -e '$d' $tempfile
[sev@linusaur sendmaildemo]$ attachdata=`cat $tempfile`

At this point, we created attach.temp file containing our base-64 encoded jpg attachment. $attachdata variable contains text output of this file.

Step 4 - Create email boundary

A multi-part MIME message require boundaries between different parts of the message, and at the start and end of the message body. Boundary could be anything specified by boundary argument in Content-Type MIME header. In our case, we will use the first 32 characters of MD5 checksum of current time in seconds as a message boundary, using md5sum command.

[sev@linusaur sendmaildemo]$ boundary=`date +%s|md5sum`
[sev@linusaur sendmaildemo]$ boundary=${boundary:0:32}
[sev@linusaur sendmaildemo]$ echo $boundary
8360935bdba088f8dc47965c018db705

Step 5 - Create email message

Finally, we can create our MIME message.mail file and combine it with our boundary and attachment.

[sev@linusaur sendmaildemo]$ mailfile='message.mail'
[sev@linusaur sendmaildemo]$ rm -f $mailfile

[sev@linusaur sendmaildemo]$ echo "From: senderemail@email.com" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "To: recipemail@email.com" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Reply-To: senderemail@gmail.com" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Subject: Test Email" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Type: multipart/mixed; boundary=\""$boundary"\"" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "This is a MIME formatted message.  If you see this text it means that your" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "email software does not support MIME formatted messages." >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "--$boundary" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Type: text/plain; charset=ISO-8859-1; format=flowed" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Transfer-Encoding: 7bit" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Disposition: inline" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "This email was sent with $attach as attachment." >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "--$boundary" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "Content-Type: $mimetype; name=\"$attach\"" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Transfer-Encoding: base64" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "Content-Disposition: attachment; filename=\"$attach\";" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "$attachdata" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile 
[sev@linusaur sendmaildemo]$ echo "--$boundary--" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile
[sev@linusaur sendmaildemo]$ echo "" >> $mailfile

Source Code

For a more complete example which uses cron to schedule sendmail to send MIME messages, please check out my github repository.

comments