Friday, September 16, 2011

Simpler and Easier XML Parsing in Android

 

xml_5+android

 

XML Parsing Problem

Surprisingly, XML parsing is not as trivial as one might think in Android. XPath and XML Serialization is only supported in API level 8 or above, which drives many developers to uses third party libraries for XML related tasks.

AQuery XML Usage

One of the key feature of AQuery is asynchronous http fetch, which often is used to invoke REST type of services over the internet. These types of API/services, such as Facebook, Twitter, or Picasa (example provided in this post) usually serves results in XML and/or JSON formats for apps to consume.

For this purpose, AQuery wants to provide a simple, easy, and light-weight method to parse these XML responses. Such method must be independent on third party library and supports API level 4.

XmlDom

Here we introduce AQuery’s XmlDom. [source] [javadoc] [download]

XmlDom is a specialized class for simple and easy XML parsing, designed to be used in basic Android api 4+ runtime without any dependency, and integrated directly with AQuery’s ajax method.

Simple

Per AQuery’s philosophy, we want to make our tasks as simple as possible. Here’s an example that get an XML of featured images from Google’s Picasa service, parse the response, and display one of the featured image it in our app.

This is done with AQuery with just 10 lines of code.

public void xml_ajax(){        
    String url = "https://picasaweb.google.com/data/feed/base/featured?max-results=8";        
    aq.ajax(url, XmlDom.class, this, "picasaCb");        
}
 
public void picasaCb(String url, XmlDom xml, AjaxStatus status){
 
    List<XmlDom> entries = xml.tags("entry");        
    List<String> titles = new ArrayList<String>();
    
    String imageUrl = null;
    
    for(XmlDom entry: entries){
        titles.add(entry.text("title"));
        imageUrl = entry.tag("content", "type", "image/jpeg").attr("src");
    }
        
    aq.id(R.id.image).image(imageUrl);
    
}}
 
 
Constructors
 
 
XmlDom(String str)
 
XmlDom(byte[] data)
 
XmlDom(InputStream is)
 
XmlDom(Element element)
Methods

[javadoc]

tag(), tags()

//return a list of "entry" nodes
List<XmlDom> entries = xml.tags("entry");  
 
child(), children()
 
//child is similar to tag, except it only return child nodes immediately under the parent
XmlDom title = entry.child("title");
 
text()
 
//extract the text value of the title tag
String text = title.text();
 
attr()
 
//extract the src attribute of a content tag
String image = content.attr("src");
Serialization (beta)
//Convert a XmlDom object to a XML string
String rawXml = xml.toString();
Light Weight

[source]

There’s only 1 class with compressed size of ~3kb. Feel free to download the source and use it even without AQuery core lib.

API Demo

Checkout our API demo app to see this example in action.

AQuery

For more Android development goodies, check out our AQuery home page.

Saturday, July 30, 2011

Down Sample Images to avoid Out of Memory Issues in Android

 

Out of Memory

Memory on phones are scarce. One of the most common case of out of memory (OOM) in Android development is loading images, specially when the source of the images are unknown.

Potential OOM operations include:

  • Loading a user specified image from the web
    • We don’ know the size/dimension of the image until it’s downloaded
  • Loading image from the gallery
    • Phone cameras capture images with very high resolution
    • Images will need to be rescaled for normal use

Note that Android allocated the Bitmap images with memory from the overall system, which means that even your application itself have plenty of memory available, OOM errors can still occurs because all the apps share the same memory pool for images (that are decoded with the BitmapFactory).

Downsampling

Downsampling reduce the resolution of an image by taking value of adjacent pixels and average them (a running average) to lower number of pixels.

Since the images in question are too large to be display on the phone regardless, the image is will be shrunk anyway when it’s displayed with a lower width ImageView. When this happens, we are simply wasting memory with loading unnecessarily large raw data.

Therefore, it’s more efficient to down sample the image during the initial load process.

Here’s a piece of code that does exactly that:

   1: private static Bitmap getResizedImage(String path, byte[] data, int targetWidth){
   2:     
   3:     BitmapFactory.Options options = new BitmapFactory.Options();
   4:     options.inJustDecodeBounds = true;
   5:     
   6:     BitmapFactory.decodeFile(path, options);
   7:     
   8:     int width = options.outWidth;
   9:     
  10:     int ssize = sampleSize(width, targetWidth);
  11:    
  12:    
  13:     options = new BitmapFactory.Options();
  14:     options.inSampleSize = ssize;
  15:     
  16:     Bitmap bm = null;
  17:     try{
  18:         bm = decode(path, data, options);
  19:     }catch(OutOfMemoryError e){
  20:         AQUtility.report(e);
  21:     }
  22:     
  23:     
  24:     return bm;
  25:     
  26: }
  27:  
  28: private static int sampleSize(int width, int target){
  29:     
  30:     int result = 1;
  31:     
  32:     for(int i = 0; i < 10; i++){
  33:         
  34:         if(width < target * 2){
  35:             break;
  36:         }
  37:         
  38:         width = width / 2;
  39:         result = result * 2;
  40:         
  41:     }
  42:     
  43:     return result;
  44: }

 

AQuery

With the AQuery lib for Android, downsampling is even simpler.

Down sample from the Internet

   1: String url = "http://farm6.static.flickr.com/5035/5802797131_a729dac808_b.jpg";            
   2: aq.id(R.id.image1).image(imageUrl, true, true, 200, 0);

 

Down sample image from File

   1: File file = new File(path);        
   2: //load image from file, down sample to target width of 300 pixels  
   3: aq.id(R.id.avatar).image(file, 300);

Checkout the image loading documentation for more detail.

Feedback

We hope our guide help your Android development.

If you have issues or have other insights regarding Android image loading / optimization, please feel free to leave a comment!

Saturday, June 18, 2011

Android ProGuard Optimization Setup & Configuration

 

Optimizing the app APK is usually the last step of Android development. Google recommend using the open source ProGuard tool to optimize the apk for final delivery. Here we explore the benefits, issues, and suggest a proper configuration for optimizing your apk.

Benefit

  • Reduced APK size
  • Improve performance
  • Obfuscation

Drawback

  • Potential misconfiguration
  • Additional testing required
  • Stacktraces are difficult to read with obfuscated method names

The major drawback is that your app might crash when misconfigured.

Configuration for Android

There are many ways to configure the ProGuard tool for optimization. Google already provided a default configuration named “proguard.cfg” under the project root folder for newly created projects. However, this configuration is outdated due to the introduction of the Fragment API and the onClick XML attribute. Since the default configuration renames public method names, some java reflection might not work and, as a result, method/class not found exceptions might be thrown.

We have updated the configuration to accommodate the new Fragment classes (and the compatibility version) and tested several configuration with different level of optimization. The following table shows the resulted apk size when optimizing our AQuery demo app VikiSpot Reader:

Configuration Notes APK Size (kb) Size %
No Optimization Original APK. 323 100%
Safest Remove no classes and only rename private methods. 266 82.4%
Recommended Remove unused classes and only rename private methods. 205 63.5%
Risky Remove unused classes and rename most public/protected methods. 203 62.8%

Note: Size reduction % varies depends on your project structure and other factors.

The “Safest” configuration reduced our apk size by about 17%, whereas the “Recommended” configuration reduce the apk size by 36%.

The “Risky” configuration only reduce an additional 0.7% beyond the “Recommended” settings.

Therefore, we believe that the marginal benefit of renaming public and protected methods does not warrant its risk and maintenance overhead.

Recommended Configuration

Here’s a safe configuration that give most benefits. See source file.

   1: -optimizationpasses 5
   2: -dontusemixedcaseclassnames
   3: -dontskipnonpubliclibraryclasses
   4: -dontpreverify
   5: -verbose
   6: -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
   7:  
   8: -keep public class * extends android.app.Activity
   9: -keep public class * extends android.app.Application
  10: -keep public class * extends android.app.Service
  11: -keep public class * extends android.content.BroadcastReceiver
  12: -keep public class * extends android.content.ContentProvider
  13: -keep public class * extends android.app.backup.BackupAgentHelper
  14: -keep public class * extends android.preference.Preference
  15: -keep public class com.android.vending.licensing.ILicensingService
  16:  
  17: #keep all classes that might be used in XML layouts
  18: -keep public class * extends android.view.View
  19: -keep public class * extends android.app.Fragment
  20: -keep public class * extends android.support.v4.Fragment
  21:  
  22:  
  23: #keep all public and protected methods that could be used by java reflection
  24: -keepclassmembernames class * {
  25:     public protected <methods>;
  26: }
  27:  
  28: -keepclasseswithmembernames class * {
  29:     native <methods>;
  30: }
  31:  
  32: -keepclasseswithmembernames class * {
  33:     public <init>(android.content.Context, android.util.AttributeSet);
  34: }
  35:  
  36: -keepclasseswithmembernames class * {
  37:     public <init>(android.content.Context, android.util.AttributeSet, int);
  38: }
  39:  
  40:  
  41: -keepclassmembers enum * {
  42:     public static **[] values();
  43:     public static ** valueOf(java.lang.String);
  44: }
  45:  
  46: -keep class * implements android.os.Parcelable {
  47:   public static final android.os.Parcelable$Creator *;
  48: }
  49:  
  50: -dontwarn **CompatHoneycomb
  51: -dontwarn org.htmlcleaner.*

Line 17-20: We want to keep classes that can be referenced in XML layouts. Those classes include custom View and Fragment.

Line 23-26: We want to keep the method names for public/protected methods to avoid problems with onClick method names specified in XML.

Setup

Here we highlight the necessary steps to enable ProGuard optimization with Eclipse.

Enable ProGuard

Open the “default.properties” file under the project root.

Enter “proguard.config=proguard.cfg”, like this:

# Project target.
target=android-12
proguard.config=proguard.cfg

Note that the file comments says not to edit this file. Ignore that outdated comment.

Next, make sure your SDK location path has no space in it, like this:

android-sdk-path

There is a bug in the SDK that causes ProGuard to fail when there’s a space in the SDK path. If you are developing on Windows and installed the SDK to the default location, you most likely will need to fix the SDK path.

Configuration

Create or edit the file “proguard.cfg” under the project folder. We recommend using our updated recommended configuration to accommodate the new Fragments and onClick listeners.

Export APK

With the above setup, ProGuard will silently run when the APK is generated. Pay attention to the console and make sure there’s no ProGuard related errors.

For testing, you can export the APK to the project /bin folder.

Test APK

Install the APK to a test device. If you export the APK to the /bin folder, just run the project like normal development testing and the optimized APK will be deployed. Note that you might need to uninstall the development version of the app in your device, because the optimized APK will be signed and the dev app must be removed first.

Feedback

We hope our guide help your Android development.

If you have issues or have other insights regarding Android ProGuard optimization, please feel free to leave a comment!

Also, take a look at our open source framework Android Query for more Android goodies.

Sunday, May 29, 2011

Android Fragmentation and Build/Target API Level

Android is a great platform for mobile apps. However, Fragementation becomes a problem when Google’s releases API and new features at a lightning speed, that is, much faster than hardware vendors’ ability to patch their devices.
See Google’s official platform distribution.

 

Android Build Option Matrix

Here’s a table that shows the total accumulated device coverage for each API level. Device coverage should be one of the key factor for picking a target API for Android development.

Build API Level Distribution Device Coverage
1.5 3 1.3% 100%
1.6 4 2.0% 98.7%
2.1 7 15.2% 96.7%
2.2 8 55.9% 81.5%
2.3 9 0.6% 25.6%
2.33 10 23.7% 25%
3.0 11 0.4% 1.3%
3.1 12 0.7% 0.9%
3.2 13 0.2% 0.2%

Note: Base on data available on 8/01/2011.

Base of on the device coverage alone, 2.1 is the best choice to date. Note that platform 1.6 have similar coverage, but lacks the all important “AccountManager” feature.

So is 2.1 the best choices? Are we forced to develop on an API, that is, 7 iterations old? Can we build an app with the latest API and yet able to run on lower API devices?

Yes, there’s a way.

Work Around

There’s a well known work around among developers, but it’s tricky and must be exercised with care.
If you build an app with a higher level API, you can still deploy that to lower level devices, except that you must not make higher API calls when running on lower API runtime.

Configuration Setup:

In side the app AndroidManifest.xml:
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="12"/>
minSdkVersion - Specify min version to be the lowest API you want your app to run with.
targetSdkVersion - Specify the latest api version you want to compile your app with.
When you build the app, go ahead and build with the latest SDK.

Code Workaround Example:

   1: public void activityTransition(){
   2:  
   3:     if(android.os.Build.VERSION.SDK_INT >= 5){
   4:         overridePendingTransition(R.anime.enter, R.anime.exit);
   5:     }
   6:  
   7: }

In this example we check the current API level, if it’s greater or equal to 5, we go ahead and call overridePendingTransition, which is only available for API 5+.

However, If this is handled incorrectly, your app will crash. Some variety of runtime exception will be thrown.
 

AQuery

A simpler solution is just use a library. AQuery provides an easier way to handler these cross-api-levels calls. It abstract away all the details and make sure the operation fallback gracefully when a feature is not available.
Here are some examples.

Enable hardware acceleration:

   1: @Override
   2: protected void onCreate(Bundle savedInstanceState){
   3:         
   4:         //My API level 4 onCreate setup here... 
   5:         
   6:         AQuery aq = new AQuery(this);   
   7:         
   8:         //Enable hardware acceleration if the device has API 11 or above        
   9:         aq.hardwareAccelerated11();
  10:         
  11: }
Set activity transition animation:
   1: @Override
   2: protected void onCreate(Bundle savedInstanceState){
   3:         
   4:         AQuery aq = new AQuery(this);   
   5:         
   6:         //Override activity transition for device with API level 5      
   7:         aq.overridePendingTransition5(R.anim.slide_in_right, R.anim.slide_out_left)
   8:         
   9:         //API level 4 onCreate setup here... 
  10:         
  11: }

Handling cross-api-level calls is just one of the feature available from the open-source AQuery library. Read more about AQuery here.

Feedback

Any feedback is welcomed!

What aspect of Android development you want us to blog about?
Leave a comment below and subscribe to the blog. :)