Tips, tricks and techniques to make your native RhoElements Apps faster, more responsive and more efficient.

 

From our last two developer forums in Bangkok and Birmingham our engineers have delivered an informative presentation on improving your native application performance, concentrating on both code optimization and database access.  Whilst the slides from this presentation are available (https://launchpad.motorolasolutions.com/appforum/apac/1330-1430%20AppForumAPACNativeOptimisation.pdf)  I wanted to walk through some of the tips covered for those unable to attend the forums.

 

Introduction

 

Untitled.png

Here is a block diagram of a typical native RhoElements application.  A native application is such that it follows the MVC pattern of development, with your business logic stored as models on the device accessing data resident either on or off the device.  This blog will concentrate on those parts of the diagram coloured red and will assume your business logic is coded in Ruby.

 

Code Optimization

 

Any application developed for mobile devices must take the hardware constraints into account, this is true of any development framework and every mobile device.  Some of the key constraints are:

  • Device battery life
  • Available Memory


Power Consumption

 

The power consumption of your application will be affected by a number of factors but one of the most easily avoidable drains is polling. When writing your application you avoid polling for values from hardware where possible, instead you should register for callbacks to be notified when values change.  The RhoElements API is written almost exclusively to not require polling.

 

We can take callbacks a step further when we require the status of a server.  Polling a server to determine if our local copy of the database is up to date can be a very expensive, in terms of both Network load and battery life.  Network push notifications are an ideal work around to server polling and there are lots of platform specific push systems out there like iOS push or Google cloud messaging.  From RhoElements 2.2 onwards we ship with RhoConnect push, a new cross platform technology allowing you to do push without the need for a complex, managed solution involving Google or Apple.  You can check out the Webinar on the new RhoConnect push technology at https://developer.motorolasolutions.com/docs/DOC-1721

 

To further reduce your application's power consumption it can de-register callbacks and stop any other processing when it is sent into the background, this is acheived through the on_active_app and on_deactiveate_app overrides:

 

class AppApplication < Rho::RhoApplication
  def on_activate_app
    # restore all callbacks and js timers
  end
  def on_deactivate_app
    # remove all unnecessary callbacks and js timers
  end
end

 

For more information on the activate and deactivate callbacks see the documentation at http://docs.rhomobile.com/rhodes/application#appapplication-class

 

Memory

 

The following tips will ensure your application doesn't use excessive memory unnecessarily:

 

  • Don't use big global or class variables, all global and class variables will remain in memory until you nil them.  If you wish to maintain large persistent objects then they should be kept in a database and accessed through the database API, http://docs.rhomobile.com/rhodesapi/database-api/

 

  • Only load Ruby classes on demand as once loaded they remain in memory forever.  This will also decrease your application startup time.
def controller_action
require 'some_library'
#business logic
end

 

  • To improve application performance and stability the Ruby Garbage collector is disabled before each controller action and re-enabled after the controller action exits.  If you allocate / deallocate a lot of ruby objects in your controller action you should call it as follows:
def my_objects_generatorGC.enable
#Do some heavy operations
end



 

 

Performance Metrics

 

In order to analyse your application performance RhoElements offers metrics to detect inefficiencies and identify where you could speed up your code.

 

Logging

 

When written to the log file, logs will contain time information.  Using this time information you can get a quick and dirty idea of where your application performance is slow.

 

You can enable logging in your configuration file as follows:

MinSeverity = 1 #0 – trace, 1 – info, 3 - errors only
LogCategories = * # category1, category2
MaxLogFileSize=50000 # in kilobytes
LogMemPeriod=5000 #milliseconds

 

And in your application you can log with the following calls:

puts “Hello!” # will log with APP category
RhoLog.error("Some Category", "Some error message") 
RhoLog.info("Some Category", "Some info message")

 

Profiler

 

To better control and quantify your application's performance it is recommended to use the RhoProfiler, http://docs.rhomobile.com/rhodesapi/rhoprofiler-api

 

Say you had two functions whose performance you wanted to measure, you would use the RhoProfiler as follows:

RhoProfiler.create_counter('Counter1') RhoProfiler.start_counter('Counter1') 
function1() 
RhoProfiler.stop_counter('Counter1') 
#do something 
RhoProfiler.start_counter('Counter1') 
function2() 
RhoProfiler.stop_counter('Counter1') RhoProfiler.destroy_counter('Counter1')
# destroy_counter will log summary of function1 and function2 execution time

 

And that will write the resulting log as follows:

I 09/18/2012 23:27:20:311 00002ffc PROFILER| Counter1 (0:03:104) : STOP

 

 

 

Data Optimization

 

There are multiple ways of storing and manipulating data with RhoElements

  • PropertyBag (generated by default)
  • Data Schema
  • RhoConnect (to connect with data at the back end)

 

Data storage techniques are discussed in more detail at the documentation page for rhom, http://docs.rhomobile.com/rhodes/rhom.  Rhom is a mini database object mapper for RhoElements, providing high level interfaces to use a local database.

 

You may choose to use a PropertyBag or Fixed Schema for data storage

 

Property Bag

 

Simple to use, it doesn't require specifying attributes in the model definition file.

Data migrations are not necessary

Attributes can be added ore removed without modifying the database schema

For some applications, the database size may be significantly larger than for fixed schema.  This is because each attribute is indexed for fast lookup.

 

Fixed Schema

 

Smaller database size, indexes can be specified only on specific attributes.

You may use direct SQL queries if and when required.

Schema changes must be handled with data migrations

Database performance may be slower unless you specify proper indices

 

Tips

 

Pre-populating the database

 

If your application requires seeding of initial data then you can use RhoUtils.load_offline_data.

For example, in the rhodes/spec/framework_spec, we use load_offline_data to seed the device database for each test: 

Rho::RhoUtils.load_offline_data(
  ['client_info','object_values'], 'spec'
)

 

In this example, there is a ‘spec/fixtures’ directory which contains a client_info.txt and object_values.txt pipe-delimited files. For more information on this please see the online documentation at http://docs.rhomobile.com/rhodes/rhom#seeding-the-database

 

You might also consider creating the local database file beforehand, then including the .db file in your application at deployment.  You can use the emulator to achieve this and there is documentation online at http://docs.rhomobile.com/faq#how-to-pre-populate-client-database

 

Pagination

 

Often you'll be dealing with long lists of data that the user has to scroll through.  It is very inefficient (and slow) to try and load this entire list all at once, the recommended solution is to use pagination to display say 20 records at a time.  The rhom API offers a syntax identical to rails' syntax and is documented at http://docs.rhomobile.com/rhodes/rhom#rhom-paginate-example

Account.paginate(:page => 0) 
  #=> returns first 10 records
Account.paginate(:page => 1, :per_page => 20) 
  #=> returns records 21-40
Account.paginate(
  :page => 5, 
  :conditions => {'industry' => 'Technology'}, 
  :order => 'name'
) #=> you can have :conditions and :order as well

 

 

Tips for using the Property Bag model

 

Do not use SQL conditions in Model.find, use Advanced queries instead (http://docs.rhomobile.com/rhodes/rhom#advanced-queries):

 

#Let’s say we have the following SQL fragment condition:
Product.find( :all, :conditions => [ "LOWER(description) like ? or LOWER(title) like ?", query, query ], 
:select => ['title','description'] ) 

#Using advanced :conditions, this becomes:
Product.find( :all, :conditions => { 
  { :func => 'LOWER', :name => 'description', :op => 'LIKE' } => query, 
  { :func => 'LOWER', :name => 'title', :op => 'LIKE' } => query }, 
  :op => 'OR', 
  :select => ['title','description'] ) 

 

To modify an object in the database, prepare the data first and call update_attributes once.  Do not use save

props = {"name" => "ABC Inc.", “brand" => “Comp2"}
@product.update_attributes( props )

 

To insert or update multiple objects or models use a database transaction

 

db = ::Rho::RHO.get_src_db('Model') 
GC.enable()
db.start_transaction() 
begin 
  items.each do |item| 
    # create hash of attribute/value pairs 
    data = { :field1  => item['value1'], :field2 => item['value2'] } 
    # Creates a new Model object and saves it    
    new_item = Model.create(data) 
  end 
    db.commit() 
rescue 
    db.rollback() 
end

 

RhoConnect

 

For more information on RhoConnect then please see the user documentation at http://docs.rhomobile.com/rhoconnect/introduction, it's a large topic which I would not be able to adequately cover in this blog.

 

If your application solution uses RhoConnect to sync its data with the backend database you might consider using bulk sync, http://docs.rhomobile.com/rhoconnect/bulk-sync

 

Enabling bulk sync either in your configuration file or at runtime will cause the next synchronization with the back end to first be zipped up before being sent to the device. 

 

To enable bulk sync in your configuration add:

bulksync_state = 0

 

To enable bulk sync at runtime call:

Rho::RhoConfig.bulksync_state = '0‘

 

The following actions are performed for a bulk sync:

  • The RhoConnect server prepares the sqlite database, zipps it and sends it to the client, running on your mobile device.
  • The RhoConnect client receives the database, unzips it and replaces the database on the device, without the overhead of SQL inserts.