Native Application Optimizations
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
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_generator
- GC.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.
Anonymous (not verified)