Saturday, June 29, 2013

AndroidQuery Future Direction

 

AndroidQuery (AQuery) is an open source light-weight library for doing asynchronous tasks and manipulating UI elements in Android. Our goal is to make Android coding simpler, easier, and more fun!

 

Update (11/02/2013)

Thanks for the feedback!

Many developers sent me requests to add a simple donation method, and here we go:

The funding will be used to improve documentation/website and future aq development.

Future Direction

Hello aq users!

My name is Peter Liu, the founder of AQuery.

I am writing this blog to get some input on how AQuery can move forward as a good development library/framework for the Android community.

Many of you might not know, AQuery is almost 2 years old! 

It started from a development framework that I wrote for my Android projects. It continue to grow as I re-factor features from my work projects and receive code contributions from the AQ community.

Re-factor and generalize features developed from my day work; review feature requests from the community; fix bugs; write test cases; answer development questions in the forum; release builds...

These are what I do to maintain the AQ project, 

and I enjoy every moment of it. 

"AndroidQuery is the best Android lib I've ever used. It's true."

"thanks for android-query it's one of the simplest yet powerful android
libs I've used. +1"

"Thank you for the continuous development of AQuery. I will not develop Android apps without it!"

There's nothing more satisfying than receiving thank you notes from fellow developers. :)

 

Current Status

In this past 2 years, the AQuery community has grown to thousands of developers (download/watch/fork from GoogleCode and GitHub).

As more people use it, more requests and ideas comes in to improve AQuery.

 

New Features

Below are some of the bigger features that I want to add in the future, depending on the actual demand from the AQuery community. These features are already developed and used in live production apps, but I couldn’t find the time and effort to re-factor them into AQ.

 

Simple Datastore (SQLite) Query and Store

This feature is implemented 2 years ago. It’s a non-intrusive way to query sql lite database with AQ-style callback.

The idea is to have a simplest way to asynchronously query a database in Android. Developers can still use their own way or other libs to manage the database.

Here’s is how it works:

Read
//getting a datastore that correspond to an SQL lite database
Datastore ds = aq.getDatastore("mydb");

 

//perform a db query on object class "Content"
private void query(){

Query query = new Query(Content.class).selection("purl = ? and type = ? and created < ?", new String[]{url, "Page", time})
.order("created desc").range(from, to).index("purl", "type", "created");
ds.select(query, this, "queryCb");

}

//callback handler
public void queryCb(Query query, List<Content> items, AjaxStatus status){

//do something with the objects

}
 
Write
 
private void store(){

//store object asynchronously
ds.store(single);
ds.store(list);

}
 
Data Object
 
public class Content implements Serializable, Entity, Cloneable{

...

@Override
public void to(ContentValues values) {


values.put("url", url);
values.put("type", type);
values.put("created", created);

}

@Override
public void from(ContentValues values) {

this.url = values.getAsString("url");
this.type = values.getAsString("type");
this.created = values.getAsLong("created");

}

...

}

 

Non-Http Async Tasks

 

Other than HTTP network requests, there are still many async tasks that can be simplified in Android.

Here’s a list of async callbacks that I have written but not re-factor into AQ.

Wifi Management

Asynchronous callbacks to scan, connect, disconnect Wifi networks.

Printing

Asynchronous callbacks to print receipts to different receipt printers.

Content Providers

Asynchronous callbacks to query other Android content providers such as the MediaStore.

PlayStore (Market)

Asynchronous callbacks to PlayStore API to make in-app purchase and query purchase history.

 

HTTP Async Message Queue

An asynchronous message queue for http requests that guarantee eventual delivery of the message. This feature is implemented for delivering important messages on a unstable/transient network connectivity, which describes pretty much any mobile devices without a physical network connection.

 

Personal Workload and Responsibilities

AQuery was started as a hobby with my spare time.

However, as time goes by, more ideas and features comes in, and as time goes by, the workload to maintain the AQuery project and the responsibility for my day work also increases significantly.

As a result, I found it very difficult to contribute as much as I wanted to the AQuery project at this stage.

 

My Proposal to the Android Community

There are tons of new improvements and features I want to develop and share with the Android community and I don’t want to stop here.

I want to push the project forward by hiring a full-time developer to maintain the project, with my daily supervision and continue along with my own contributions.

Here’s what will be improved:

  • better documentation
  • better website
  • more comprehensive test case and coverage
  • performance test
  • more time to work on community requests
  • more time to fix/investigate bugs
  • more time to merge community code contributions
  • more time to add new features (some listed in the blog)
  • promote aquery

 

Funding

For ways to fund the salary of a full time developer, I can think of these methods:

  • KickStarter
    • I never did these kind of campaigns, and I am not sure about the public’s interest in open-source software projects like AQuery
    • Will ask for 1 year funding
  • Paypal donation button
    • Ongoing
    • Hire a developer when fund can support 6 months salary.
  • Ask funding from other organizations
    • What organization to ask?

Please spend a minute to vote and express your support/concern on this issue. It will help me to decide what to do next with the project.

 

Feedback

Please leave a comment, or send direct feedback to tinyeeliu@gmail.com.

Thank you for your support.

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. :)