Wednesday, May 7, 2014

Learn to accelerate.

הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/?p=16144

Hello again,
I am very excited because this is a brand new post modeled after a brand new lecture i recently gave at GDG Herzeliya, on our last event titled: Android framework to open source strategy.

In this post i would like to focus on hardware acceleration (would be referred in this post as HW accel. to spare me a few characters :P) which a new(-ish) and exciting tool which you MUST know because unlike other great tools, if you do not know the rules for this one it could cause damage rather than simply spare you of its benefits.

So let's get started:

Why?

Motivation.

HW. accel promises to deliver what most developers want most of all:

  • Better looking UIs.
  • Smoother animations.
  • Smaller memory footprints.
  • Less stress on the CPU.

How?

Hardware acceleration - prolog.

First thing's first, hardware acceleration availability is as follows:

  • API < 11 - Not available.
  • API >= 11 (HC 3.x) - Available, off.
  • API >= 14 (ICS 4.x) - Available, on.

The concept of HW. accel. suggests the usage of a hardware component which will ensure the following:
  • Specially tailored HW. design for a specific task or set of tasks (non-generic = faster).
  • Specially tailored APIs (non-generic = faster).
  • Separate pre-allocated resources.

In Android's case, when HW. accel is enabled we use the GPU to draw the Canvas for all the views (all views have Canvas objects linked to them) in the HW drawing model by utilizing OpenGL methods and functions directly.


HW drawing model?

There are 2 drawing models used by Android:

The Software drawing model:
  1. Invalidate View Hierarchy.
  2. Draw Hierarchy.

This is done immediately after Invalidate() is called and causes the calling View to be redrawn but also any affiliated View since the mechanism for figuring out the diff is minimal to non-existing.

The Hardware drawing model.
  1. Invalidate View Hierarchy.
  2. Record the display lists.
  3. Update the display lists.
  4. Draw Hierarchy.
The first 3 items are done immediately for the calling View (and only the calling View) but the 4th action is called on the G-sync (possible due to the display lists mechanism that saves the state), which means that the advantages that the HW drawing model has upon its software equivalent are:
  1. Not drawing immediately saves resources for the system.
  2. Drawing with the GPU frees the CPU to other things. 
  3. Drawing on the G - Sync means that it is also synchronized better with the screen and CPU for smoother graphics.
  4. The display lists mechanism saves the diff and state so only changed pixels are redrawn (even on the calling View).
  5. Only the calling View gets invalidated, which is more efficient (although many developers discover that they relied on this false positive behaviour and have UI bugs "appear" when they start using HW accel).

Shouldn't i always use hardware acceleration?

The short answer is no.

Since OpenGL imports are at the core of the Android HW accel. there are a few issues which might arise:
  • Imports take a lot of time and therefore Google started with support for the set of basic functions that power the standard Views so not everything that you're using is supported and this changes from API level to the next (for an example, the popular and useful clipRect(...) is not supported until API level 18
  • OpenGL impl. and func. differs ever so slightly between GPU models causing it to act different on different devices for no apparent reason.
  • OpenGL and HW. accel. requires extra RAM.

What's the worst that could happen?

  • Invisible UI elements,
  • Memory related crashes.
  • Badly rendered pixels.
All of which will most likely be only reproducible on specific devices and API levels.

What to do?

Make sure if you have a problem and fix it.... dah!

Find out if you have a problem.

If you're only using standard Views and/or your designated API level is 18 you have nothing to worry about, if that is not the case, there still might be no reason to worry, try the following steps:
  1. Check the following list to see if your operations are supported on your designated API levels here: http://developer.android.com/guide/topics/graphics/hardware-accel.html#drawing-support.
  2. Test your code with all that Android (and every 3rd party you can think of) offers you with great tools like; systrace, GLTracer and traceView and here's a great session on that by Mr. Ran Nachmany here.
  3. Test on as many physical devices that you can obtain (in Israel you can apply to use Google campus's device library here.

Fix problems (if applicable).

1. Have you tried turning it off and on again?

Try to identify where the problem might be and switch HW accel. off to verify whether this particular portion might work better without HW accel. while still getting being able to enjoy HW accel.'s perks where needed.
You can do that by utilizing the different control levels that Android suggests:

Application:



<application android:hardwareAccelerated="true" ...>

Activity:


<application android:hardwareAccelerated="true">
   <activity ... />
   <activity android:hardwareAccelerated="false" />
</application>


Window:


getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,           WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
);

Layer:


myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    Layers also requires you to distinguish between layer types:
    1. LAYER_TYPE_NONE - not backed by any off screen buffer, rendered in the SW model.
    2. LAYER_TYPE_HARDWARE - if HW accel. is on, layer is backed by HW texture buffer and rendered in the HW model, else it is the same as in the case of LAYER_TYPE_SOFTWARE.
    3. LAYER_TYPE_SOFTWARE - backed by a Bitmap as if it was a buffer, rendered in the SW model.
    The rule of thumb when it comes to layers is that LAYER_TYPE_HARDWARE is usually best for performance while LAYER_TYPE_SOFTWARE works best for compatibility between GPUs and API levels.

    2. Use what was already created and beware of unnecessary modifications.

    Alterations are expensive in HW accel.:
    • Objects such as Shapes/Circle/Paths create new texture mask instances whenever they're changed.
    • Bitmaps are reloaded to the GPU whenever they’re altered.
    For that reason it is highly important to pay extra attention to only edit what needs to be changed or use the EditMode design pattern, to which an example is here:
    private class PieView extends View {
          public PieView(Context context) {
              super(context);
              if (!isInEditMode()) {
                  setLayerType(View.LAYER_TYPE_HARDWARE, null);
              }
          }
    ...
    }

    It looks like the suggestion on how to have custom Views appear in Eclipse and actually it is the same for a similar reason, it means that while it is edited it will use the SW drawing model but afterwards it will be loaded and reused with the HW drawing model.

    *An important exception to that rule is any and all usages of alpha and alpha manipulations which are created in an off-screen buffer which should be dealt in a hardware layer type and does not create any new textures or instances when done by HW. accel.

    3. new is bad.

    Just like creating new instances is bad in other examples of repeditaly running code (like getView in ListViews) since it creates a lot of runtime memory "garbage" which in turn invokes the garbage collector repeatedly which is expensive and may cause severe performance issues so does creating such instances in rendering methods (such as onDraw) is imperative to avoid.
    Most common examples of unnecessarily created objects are Path and Paint, the simple pattern is to create them as global members and simply change their values when needed.

    4. Avoid overdraw.


    The rule of thumb is to never draw more than X2.5 of the number of available pixels (Important: "invisible pixels" in Bitmaps count, so maybe "the expert" had a point after all).
    If you're state is different you'll need to merge and/or remove layers or expect performance issues in general and especially with HW accel.

    5. Reduce the number of views.


    Less views means smaller display lists and less to draw (for HW and SW drawing models), the exception to that rule is if you're using only standard views and a reduction means to start using custom views, in that case, only do so if the reduction is very big.

    5. Monitor the state.

    There are 2 ways to tell whether the View you're working in is drawn by the HW or SW models:

    Please use them and when you do so, here's a tip i learned the hard way:
    Prefer Canvas.isHardwareAccelerated() especially within the drawing code since a view attached to a hardware accel. window can still be drawn in the SW model (like when it’s drawn to a bitmap for caching).

    And that's about it, i hope you learned something new and that the post was written well.

    Thank you.


    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/?p=16144

    Royi is a Google Developer Expert for Android in 2013, a mentor at Google's CampusTLV for Android and (last but not least) the Android clients group leader at Vidmind, an OTT TV and Video Platform Provider. www.vidmind.com

    Tuesday, March 18, 2014

    To provide and serve (content)

    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/archives/16066

    Hello again,
    As i stated in an earlier post, after my Hebrew blog posts started to get published on the wonderful iAndroid website i decided to transform this blog into the English edition.
    Slowly but surely i'll translate my earlier posts to English.

    For those of you who don't know, all of my posts until now (including this one) are derived from AndconLab; the developer code lab that  +Ran Nachmany+Amir Lazarovich and myself created last year for the great MultiScreenX convention we helped organize.

    In the last post we showed how to use ContentProviders briefly without really diving in, this is where this post comes in and it is also is modeled after a session i gave.

    What?

    You must be wondering why i'm taking a detour from my usual "best practices" posts and reverting to a seemingly simple, beginner-ish topic, well the answer to that is that although ContentProviders are available since API level 1 i come across developers more times than not that:
    This is what i aim to change, so let's get started.

    Why?

     The motivation for using ContentProviders could be broken apart to 8 major reasons:
    1. Non-volatile data storage.
    2. Mass data storage with low runtime memory overhead.
    3. Data indexing for ultra-rapid search.
    4. Possible access from multiple processes (Activities, Services, Applications).
    5. Offline capabilities.
    6. Short and easy(-ish) configuration time.
    7. Flexibility deriving from the fact that all data in all forms is saved in one repository.
    8. Platform support.
    Please notice that a subset of these 8 items can describe the tools that most developers use instead of ContentProviders so for instance:
    1. SharedPreferences will give you 1, 4, 5 and 8 but are lacking in flexibility and in search capabilities.
    2. Memory caching design patterns will grant you the capabilities embodied in 2,3,6 and 7 but are problematic offline.
    3. Cloud storage is a wonderful tool but will also not function when your end-user is offline.
    4. 3rd party ORM libraries such as greenDAO or ORMlite will possibly give you everything but item 8 and since they're essentially open-source libraries they update all the time, requiring more maintenance and possibly crippled in the future by security changes in Android.
    So as far as i'm aware ContentProviders is the only tool that gives you this complete package.

    How?

    Now comes the fun part :)
    Let's start with an application which has a data model, called Event, that looks like this:

     public class Event{ 
       //////////////////////////////////////////  
       // Members  
       //////////////////////////////////////////  
       @JsonProperty("id") private long mId;  
       @JsonProperty("name") private String mName;  
       @JsonProperty("description") private String mDescription;  
       private List<Lecture> mLectures;  
       @JsonProperty("logo_url") private String mLogoUrl;  
       @JsonProperty("website_url") private String mWebsiteUrl;  
       @JsonProperty("start_date") private String mStartDate;  
       @JsonProperty("end_date") private String mEndDate;  
       //////////////////////////////////////////  
       // Public  
       ////////////////////////////////////////// 
    
          //Do something!
     
       //////////////////////////////////////////  
       // Getters & Setters  
       ////////////////////////////////////////// 
    
         //Do something more. 

    All we have now are the members, getters and setters, now we'll add:


    • Support for serializing and deserializing the data so it could be manipulated by implementing the Serializable interface.
    • Column definition for our SQLite table.
    The result will look like this:


     public class Event implements Serializable {  
       public static final String TABLE_NAME = "events";  
       public static final String COLUMN_NAME_ID = "_id";  
       public static final String COLUMN_NAME_NAME = "name";  
       public static final String COLUMN_NAME_DESCRIPTION = "description";  
       public static final String COLUMN_NAME_LOGO_URL = "logo_url";  
       public static final String COLUMN_NAME_WEBSITE_URL = "website_url";  
       public static final String COLUMN_NAME_START_DATE = "start_date";  
       public static final String COLUMN_NAME_END_DATE = "end_date";  
       //////////////////////////////////////////  
       // Members  
       //////////////////////////////////////////  
       @JsonProperty("id") private long mId;  
       @JsonProperty("name") private String mName;  
       @JsonProperty("description") private String mDescription;  
       private List<Lecture> mLectures;  
       @JsonProperty("logo_url") private String mLogoUrl;  
       @JsonProperty("website_url") private String mWebsiteUrl;  
       @JsonProperty("start_date") private String mStartDate;  
       @JsonProperty("end_date") private String mEndDate;  
       //////////////////////////////////////////  
       // Public  
       //////////////////////////////////////////  
       public ContentValues getContentValues() {  
         ContentValues cv = new ContentValues();  
         cv.put(COLUMN_NAME_DESCRIPTION, mDescription);  
         cv.put(COLUMN_NAME_END_DATE, mEndDate);  
         cv.put(COLUMN_NAME_ID, mId);  
         cv.put(COLUMN_NAME_LOGO_URL, mLogoUrl);  
         cv.put(COLUMN_NAME_NAME, mName);  
         cv.put(COLUMN_NAME_START_DATE, mStartDate);  
         cv.put(COLUMN_NAME_WEBSITE_URL, mWebsiteUrl);  
         return cv;  
       }  
       //////////////////////////////////////////  
       // Public  
       //////////////////////////////////////////  
       //////////////////////////////////////////  
       // Getters & Setters  
       //////////////////////////////////////////  
    

    Next phase is to define the SQLite table's constructor and its rules, this is done by extending the SQLiteOpenHelper class and overriding some methods, like so:

     public class DatabaseHelper extends SQLiteOpenHelper{  
          public static final String DB_NAME = "db";  
          public static final int DB_VERSION = 7;  
          public static final String LECTURE_SPEAKER_PAIT_TABLE = "lecture_speaker_pair";  
          public static final String PAIR_LECTURE_ID = "lecture_id";  
          public static final String PAIR_SPEAKER_ID = "speaker_id";  
          public DatabaseHelper(Context context, String name, CursorFactory factory,  
                    int version) {  
               super(context, name, factory, version);  
          }  
          @Override  
          public void onCreate(SQLiteDatabase db) {  
               // create events table  
               StringBuilder sb = new StringBuilder();  
               try {  
               DBUtils.createTable(db, sb,   
                         Event.TABLE_NAME,  
                         Event.COLUMN_NAME_ID, "INTEGER PRIMARY KEY ",  
                         Event.COLUMN_NAME_NAME, "TEXT",  
                         Event.COLUMN_NAME_DESCRIPTION, "TEXT",  
                         Event.COLUMN_NAME_START_DATE, "TEXT",  
                         Event.COLUMN_NAME_END_DATE, "TEXT",  
                         Event.COLUMN_NAME_LOGO_URL, "TEXT",  
                         Event.COLUMN_NAME_WEBSITE_URL, "TEXT");  
          }  
          @Override  
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
               StringBuilder sb = new StringBuilder();  
               DBUtils.dropTable(db,sb , Event.TABLE_NAME);  
               onCreate(db);  
          }  
     }  

    Now, please pay attention to the following:

    • In the OnCreate we create a unique SQLite table specifically tailored to our Event model class and its columns are modeled after the members of that class.
    • In every table upgrade we delete the table and start from scratch, this is a naive solution and it's not that efficient but it's good enough to start with.
    • The name of the SQLite database is defined by us here:

    public static final String DB_NAME = "db"; 

    OK, so we have a database, some tables and an object, now it's time to implement some basic CRUD:

     public class DBUtils {  
          private static final String TAG = "DBUtils";  
          //select lecturerImage from assets where lectureVideoId='Y4UMzOWcgGQ';  
          /**  
           * Create DB table  
           *  
           * @param db    Reference to the underlying database  
           * @param sb    Clears any existing values before starting to append new values  
           * @param tableName The name of the DB table  
           * @param columns  Tuples of column names and their corresponding type and properties. This field must be even for that same  
           *         reason. I.e. "my_column", "INTEGER PRIMARY KEY AUTOINCREMENT", "my_second_column", "VARCHAR(255)"  
           */  
          public static void createTable(SQLiteDatabase db, StringBuilder sb, String tableName, String... columns) {  
               if (columns.length % 2 != 0) {  
                    throw new IllegalArgumentException(  
                              "Columns length should be even since each column is followed by its corresponding type and properties");  
               }  
               StringUtils.clearBuffer(sb);  
               // Prepare table  
               sb.append("CREATE TABLE ");  
               sb.append(tableName);  
               sb.append(" (");  
               // Parse all columns  
               int length = columns.length;  
               for (int i = 0; i < length; i += 2) {  
                    sb.append(columns[i]);  
                    sb.append(" ");  
                    sb.append(columns[i + 1]);  
                    if (i + 2 < length) {  
                         // Append comma only if this isn't the last column  
                         sb.append(", ");  
                    }  
               }  
               sb.append(");");  
               // Create table  
               db.execSQL(sb.toString());  
          }  
          /**  
           * Drop table if exists in given database  
           *  
           * @param db    Reference to the underlying database  
           * @param tableName The table name of which we try to drop  
           */  
          public static void dropTable(SQLiteDatabase db, String tableName) {  
               dropTable(db, new StringBuilder(), tableName);  
          }  
          /**  
           * Drop table if exists in given database  
           *  
           * @param db    Reference to the underlying database  
           * @param sb    Clears any existing values before starting to append new values  
           * @param tableName The table name of which we try to drop  
           */  
          public static void dropTable(SQLiteDatabase db, StringBuilder sb, String tableName) {  
               StringUtils.clearBuffer(sb);  
               sb.append("DROP TABLE IF EXISTS ");  
               sb.append(tableName);  
               // Drop table  
               db.execSQL(sb.toString());  
          }  
          /**  
           * Stores events and their lectures and speakers in db  
           * @param db - Writeable SQLITE DB  
           * @param events - events to be stored  
           * @return  
           */  
          public static boolean storeEvents(SQLiteDatabase db, List<Event> events) {  
               db.beginTransaction();  
               ContentValues cv;  
               List<Lecture> lectures;  
               List<Speaker> speakers;  
               long eventId;  
               for (Event event : events) {  
                    //store event  
                    cv = event.getContentValues();  
                    db.replace(Event.TABLE_NAME, null, cv);  
                    eventId = event.getId();  
                    //loop through all lectures  
                    lectures = event.getLectures();  
                    for (Lecture lecture : lectures) {  
                         //set event id  
                         lecture.setEventId(eventId);  
                         cv = lecture.getContentValues();  
                         db.replace(Lecture.TABLE_NAME, null, cv);  
                         //remove all speakers from this lecture  
                         clearLectureSpeakers(db, lecture);  
                         //loop through all the speakers  
                         speakers = lecture.getSpeakers();  
                         for (Speaker speaker : speakers) {  
                              //store speaker in db  
                              cv = speaker.getContentValues();  
                              db.replace(Speaker.TABLE_NAME, null, cv);  
                              //add speaker to this lecture  
                              addSpeakerToLecture(db, speaker, lecture);  
                         }  
                    }  
               }  
               db.setTransactionSuccessful();  
               db.endTransaction();  
               return true;  
          }  
     /**  
           * Fetch all events from DB  
           * @param db  
           * @return cursor holding id, name, description and logo url  
           */  
          public static Cursor getEventsCurosr(SQLiteDatabase db) {  
               String[] cols = new String[] {  
                         Event.COLUMN_NAME_ID,  
                         Event.COLUMN_NAME_NAME,  
                         Event.COLUMN_NAME_DESCRIPTION,  
                         Event.COLUMN_NAME_LOGO_URL  
               };  
               Cursor c;  
               c = db.query(Event.TABLE_NAME, cols, null, null, null, null, Event.COLUMN_NAME_START_DATE + " DESC");  
               return c;  
          }  
     {  

    As you can see we implemented the following:

    • Table creation.
    • Table deletion.
    • Saving a list of objects of type Event in the database.
    • Find the cursor for a location in the SQLite table, which will hold the id and name of the object it is pointing too in the database (could also hold an image or a url with very small modifications). 
    That's it; we have successfully defined and implement a flexible, durable and versatile mechanism for data storage with ContentProviders, for a usage example please check out my earlier posts or the codelab we created.


    I would like to conclude this post by saying thanks again to +Ran Nachmany and +Amir Lazarovich which wrote most of the code which I used in this post and to link you to the full source code for AndConLab here: https://github.com/RanNachmany/AndconLab

    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/archives/16066

    Royi is a Google Developer Expert for Android in 2013, a mentor at Google's CampusTLV for Android and (last but not least) the set top box team leader at Vidmind, an OTT TV and Video Platform Provider. www.vidmind.com

    Saturday, December 14, 2013

    Modern talking 2 - The guide to communicating with a server in Android applications, the journey continues

    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/archives/14998

    Hello again,
    As i stated in an earlier post, after my Hebrew blog posts started to get published on the wonderful iAndroid website i decided to transform this blog into the English edition.
    Slowly but surely i'll translate my earlier posts to English.

    For those of you who don't know, all of my posts until now (including this one) are derived from AndconLab; the developer code lab that  +Ran Nachmany+Amir Lazarovich and myself created last year for the great MultiScreenX convention we helped organize.

    In the last post we dealt with some basic explanations and examples as to how the actual client server communication action works and that is why i fibbed a bit and didn't give you the complete picture so :for starters, here's the full implementations of the TransactionJob

     private class TransactionJob implements Runnable {  
               private static final String TAG = "Service";  
               @Override  
               public void run() {  
                    try {  
                HttpGet httpGet = new HttpGet(GET_EVENTS);  
                      String responseBody = null;  
                      try {  
               responseBody = mHttpClient.execute(httpGet, mResponseHandler);  
                      } catch(Exception ex) {  
                        ex.printStackTrace();  
                      }  
             List<Event> events = null;  
                      if(responseBody != null) {  
               events = JacksonUtils.sReadValue(responseBody, new TypeReference<List<Event>>() {}, false);  
               if (events != null) {  
                 SQLiteDatabase db = new DatabaseHelper(CommunicationService.this.getApplicationContext(), DatabaseHelper.DB_NAME, null, DatabaseHelper.DB_VERSION).getWritableDatabase();  
                 DBUtils.storeEvents(db, events);  
                 //fire an intent  
                 Intent intent = new Intent();  
                    intent.setAction(RESULTS_ARE_IN);  
                    CommunicationService.this.sendBroadcast(intent);  
               }  
                      }  
              } catch (Exception e) {  
                   e.printStackTrace();  
              }  
               }  
          }  
    For those of you who are good (i.e. lazy) engineers and lack the strength to look for the differences, here they are, these 2 lines:

     SQLiteDatabase db = new DatabaseHelper(CommunicationService.this.getApplicationContext(), DatabaseHelper.DB_NAME, null, DatabaseHelper.DB_VERSION).getWritableDatabase();  
                 DBUtils.storeEvents(db, events);  
    

    Some of you are probably questioning my sanity or the amount of free time that i posses by asking questions like:
    • Who cares?
    • Why would that make any difference?
    • Sqlite databases? they are so slow to work with on Android's UI, why would i do such a thing?

    To answer some of these questions i must first take one step back.
    Normally in posts and tutorials which try to address this issue you see the tactical approach which is  normally an AsynckTask of sorts which fetches and parses the request.
    What i want to show you is an example of a strategic approach which could be summed up by; "Which battles must i sacrifice in order to win the war?"
    In this case the classic behavior i see in most cases is that the developers forgo the use of sophisticated mechanisms such as ContentProviders in order to speed up the process and shorten the time delay from request to display while forcing themselves to download the same data over and over again which might (in some cases, not all) make their application actually feel more sluggish and nonresponsive and while draining the user's battery (and wallet, if they are working on some 3G packages).

    So, if you except my premise and agree with my conclusion, lets get started by viewing the activity code and explain when i download the data and when i use what's already available to me:
     public class MainActivity extends SherlockActivity implements OnItemClickListener{  
          private ListView mList;  
          private ProgressDialog mProgressDialog;  
          private BroadcastReceiver mUpdateReceiver;  
          @Override  
          protected void onCreate(Bundle savedInstanceState) {  
               super.onCreate(savedInstanceState);  
               setContentView(R.layout.main_activity);  
               mList = (ListView) findViewById(R.id.list);  
               mList.setOnItemClickListener(this);  
          }  
          @Override  
          protected void onPause() {  
               super.onPause();  
               if (null != mUpdateReceiver) {  
                    unregisterReceiver(mUpdateReceiver);  
                    mUpdateReceiver = null;  
               }  
          }  
          @Override  
          protected void onResume() {  
               super.onResume();  
               mUpdateReceiver = new BroadcastReceiver() {  
                  @Override  
                  public void onReceive(Context context, Intent intent) {  
                      if (intent.getAction().equalsIgnoreCase(CommunicationService.RESULTS_ARE_IN)) {  
                          new lecturesLoader().execute((Void) null);  
                      }  
                      if (null != mProgressDialog)  
                          mProgressDialog.dismiss();  
                  }  
               };  
               final IntentFilter filter = new IntentFilter();  
               filter.addAction(CommunicationService.RESULTS_ARE_IN);  
               registerReceiver(mUpdateReceiver, filter);  
               new lecturesLoader().execute((Void)null);  
          }  
          @Override  
          public void onItemClick(AdapterView<?> list, View view, int position, long id) {  
               Intent i = new Intent(this,SingleLectureActivity.class);  
               i.putExtra(SingleLectureActivity.EXTRA_LECTURE_ID, id);  
               startActivity(i);  
          }  
       @Override  
       public boolean onCreateOptionsMenu(Menu menu) {  
         getSupportMenuInflater().inflate(R.menu.main, menu);  
         return true;  
       }  
       @Override  
       public boolean onOptionsItemSelected(MenuItem item) {  
         switch (item.getItemId()) {  
           case R.id.action_refresh:  
             refreshList(true);  
             return true;  
           default:  
             return super.onOptionsItemSelected(item);  
         }  
       } 
    
    private void refreshList(boolean showProgressbar) {
    if (showProgressbar) {
    mProgressDialog = ProgressDialog.show(this, getString(R.string.progress_dialog_starting_title), getString(R.string.progress_dialog_starting_message));
    }
    Intent i = new Intent(this,CommunicationService.class);
    startService(i);
    }
    ////////////////////////////////
    // Async task that queries the DB in background
    ////////////////////////////////
    private class lecturesLoader extends AsyncTask<Void, Void, Cursor> {
    private SQLiteDatabase db;
    @Override
    protected Cursor doInBackground(Void... params) {
    db = new DatabaseHelper(MainActivity.this.getApplicationContext(), DatabaseHelper.DB_NAME,null , DatabaseHelper.DB_VERSION).getReadableDatabase();
    return DBUtils.getAllLectures(db);
    }
    @Override
    protected void onPostExecute(Cursor result) {
    if (0 == result.getCount()) {
    //we don't have anything in our DB, force network refresh
    refreshList(true);
    }
    else {
    LecturesAdapter adapter = (LecturesAdapter) mList.getAdapter();
    if (null == adapter) {
    adapter = new LecturesAdapter(MainActivity.this.getApplicationContext(), result);
    mList.setAdapter(adapter);
    }
    else {
    adapter.changeCursor(result);
    }
    }
    db.close();
    }
    } }
    The workflow explanation to the code above is this:
    1. The application is initialized. 
    2. The Broadcast intent for the "RESULTS_ARE_IN" action is registered.
    3. The LectureLoader AsyncTask is initializes and checks whether or not he has saved data in the database and if so; the application uses the existing data and if not it calls the refreshList method  which starts the fetching and parsing Service i talked about in the last post(if the CommunicationService Service is initialized it will not interfere with the UI because it updates the database in the background and only then sends the intent which we talked about in item #2).
    And here's the data base handling code:

     public class DBUtils {  
          private static final String TAG = "DBUtils";  
          //select lecturerImage from assets where lectureVideoId='Y4UMzOWcgGQ';  
          /**  
           * Create DB table  
           *  
           * @param db    Reference to the underlying database  
           * @param sb    Clears any existing values before starting to append new values  
           * @param tableName The name of the DB table  
           * @param columns  Tuples of column names and their corresponding type and properties. This field must be even for that same  
           *         reason. I.e. "my_column", "INTEGER PRIMARY KEY AUTOINCREMENT", "my_second_column", "VARCHAR(255)"  
           */  
          public static void createTable(SQLiteDatabase db, StringBuilder sb, String tableName, String... columns) {  
               if (columns.length % 2 != 0) {  
                    throw new IllegalArgumentException(  
                              "Columns length should be even since each column is followed by its corresponding type and properties");  
               }  
               StringUtils.clearBuffer(sb);  
               // Prepare table  
               sb.append("CREATE TABLE ");  
               sb.append(tableName);  
               sb.append(" (");  
               // Parse all columns  
               int length = columns.length;  
               for (int i = 0; i < length; i += 2) {  
                    sb.append(columns[i]);  
                    sb.append(" ");  
                    sb.append(columns[i + 1]);  
                    if (i + 2 < length) {  
                         // Append comma only if this isn't the last column  
                         sb.append(", ");  
                    }  
               }  
               sb.append(");");  
               // Create table  
               db.execSQL(sb.toString());  
          }  
          /**  
           * Drop table if exists in given database  
           *  
           * @param db    Reference to the underlying database  
           * @param tableName The table name of which we try to drop  
           */  
          public static void dropTable(SQLiteDatabase db, String tableName) {  
               dropTable(db, new StringBuilder(), tableName);  
          }  
          /**  
           * Drop table if exists in given database  
           *  
           * @param db    Reference to the underlying database  
           * @param sb    Clears any existing values before starting to append new values  
           * @param tableName The table name of which we try to drop  
           */  
          public static void dropTable(SQLiteDatabase db, StringBuilder sb, String tableName) {  
               StringUtils.clearBuffer(sb);  
               sb.append("DROP TABLE IF EXISTS ");  
               sb.append(tableName);  
               // Drop table  
               db.execSQL(sb.toString());  
          }  
          /**  
           * Stores events and their lectures and speakers in db  
           * @param db - Writeable SQLITE DB  
           * @param events - events to be stored  
           * @return  
           */  
          public static boolean storeEvents(SQLiteDatabase db, List<Event> events) {  
               db.beginTransaction();  
               ContentValues cv;  
               List<Lecture> lectures;  
               List<Speaker> speakers;  
               long eventId;  
               for (Event event : events) {  
                    //store event  
                    cv = event.getContentValues();  
                    db.replace(Event.TABLE_NAME, null, cv);  
                    eventId = event.getId();  
                    //loop through all lectures  
                    lectures = event.getLectures();  
                    for (Lecture lecture : lectures) {  
                         //set event id  
                         lecture.setEventId(eventId);  
                         cv = lecture.getContentValues();  
                         db.replace(Lecture.TABLE_NAME, null, cv);  
                         //remove all speakers from this lecture  
                         clearLectureSpeakers(db, lecture);  
                         //loop through all the speakers  
                         speakers = lecture.getSpeakers();  
                         for (Speaker speaker : speakers) {  
                              //store speaker in db  
                              cv = speaker.getContentValues();  
                              db.replace(Speaker.TABLE_NAME, null, cv);  
                              //add speaker to this lecture  
                              addSpeakerToLecture(db, speaker, lecture);  
                         }  
                    }  
               }  
               db.setTransactionSuccessful();  
               db.endTransaction();  
               return true;  
          }  
          private static void addSpeakerToLecture(SQLiteDatabase db, Speaker speaker, Lecture lecture) {  
               ContentValues cv = new ContentValues();  
               cv.put(DatabaseHelper.PAIR_LECTURE_ID, lecture.getId());  
               cv.put(DatabaseHelper.PAIR_SPEAKER_ID, speaker.getId());  
               db.insert(DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE, null, cv);  
          }  
          private static void clearLectureSpeakers(SQLiteDatabase db, Lecture lecture) {  
               db.delete(DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE, DatabaseHelper.PAIR_LECTURE_ID + "=" + lecture.getId(), null);  
          }  
          /**  
           * Fetch all events from DB  
           * @param db  
           * @return cursor holding id, name, description and logo url  
           */  
          public static Cursor getEventsCurosr(SQLiteDatabase db) {  
               String[] cols = new String[] {  
                         Event.COLUMN_NAME_ID,  
                         Event.COLUMN_NAME_NAME,  
                         Event.COLUMN_NAME_DESCRIPTION,  
                         Event.COLUMN_NAME_LOGO_URL  
               };  
               Cursor c;  
               c = db.query(Event.TABLE_NAME, cols, null, null, null, null, Event.COLUMN_NAME_START_DATE + " DESC");  
               return c;  
          }  
          public static Cursor getLecturesByEventId(SQLiteDatabase db, long eventId) {  
               String[] cols = new String[] {  
                         Lecture.COLUMN_NAME_ID,  
                         Lecture.COLUMN_NAME_NAME,  
                         Lecture.COLUMN_NAME_DESCRIPTION,  
               };  
               Cursor c;  
               c = db.query(Lecture.TABLE_NAME, cols, Lecture.COLUMN_NAME_EVENT_ID +" = " +eventId, null, null, null, Lecture.COLUMN_NAME_NAME);  
               return c;       
          }  
          public static Cursor getAllLectures (SQLiteDatabase db) {  
               String[] cols = new String[] {  
                         Lecture.COLUMN_NAME_ID,  
                         Lecture.COLUMN_NAME_NAME,  
                         Lecture.COLUMN_NAME_DESCRIPTION,  
                         Lecture.COLUMN_NAME_DURATION  
               };  
               return db.query(Lecture.TABLE_NAME, cols, null, null, null, null, Lecture.COLUMN_NAME_EVENT_ID + " DESC");  
          }  
          /**  
           * Fetches a lecture from db  
           * @param db  
           * @param id  
           * @return Lecture object or null if no lecture found.   
           */  
          public static Lecture getLectureById (SQLiteDatabase db, long id) {  
               Cursor c = db.query(Lecture.TABLE_NAME, null, Lecture.COLUMN_NAME_ID + "=" + id, null, null, null, null);  
               Lecture lecture = new Lecture();  
               if (c.moveToNext()) {  
                    lecture.buildFromCursor(c);  
                    c.close();  
                    return lecture;  
               }  
               return null;  
          }  
          public static ArrayList<Speaker> getSpeakersByLectureId (SQLiteDatabase db, long id) {  
               ArrayList<Speaker> speakers = new ArrayList<Speaker>();  
               String select = "SELECT * FROM " + Speaker.TABLE_NAME +" WHERE " + Speaker.COLUMN_NAME_ID +" IN ("+  
                         " SELECT " + DatabaseHelper.PAIR_SPEAKER_ID + " FROM " + DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE + " WHERE " + DatabaseHelper.PAIR_LECTURE_ID + " = " +id +")";  
               Cursor c = db.rawQuery(select, null);  
               Speaker speaker;  
               while (c.moveToNext()) {  
                    speaker = new Speaker();  
                    speaker.buildFromCursor(c);  
                    speakers.add(speaker);  
               }  
               c.close();  
               return speakers;  
          }  
     }  
    

    And The database helper code:

     public class DatabaseHelper extends SQLiteOpenHelper{  
          public static final String DB_NAME = "db";  
          public static final int DB_VERSION = 7;  
          public static final String LECTURE_SPEAKER_PAIT_TABLE = "lecture_speaker_pair";  
          public static final String PAIR_LECTURE_ID = "lecture_id";  
          public static final String PAIR_SPEAKER_ID = "speaker_id";  
          public DatabaseHelper(Context context, String name, CursorFactory factory,  
                    int version) {  
               super(context, name, factory, version);  
          }  
          @Override  
          public void onCreate(SQLiteDatabase db) {  
               // create events table  
               StringBuilder sb = new StringBuilder();  
               try {  
               DBUtils.createTable(db, sb,   
                         Event.TABLE_NAME,  
                         Event.COLUMN_NAME_ID, "INTEGER PRIMARY KEY ",  
                         Event.COLUMN_NAME_NAME, "TEXT",  
                         Event.COLUMN_NAME_DESCRIPTION, "TEXT",  
                         Event.COLUMN_NAME_START_DATE, "TEXT",  
                         Event.COLUMN_NAME_END_DATE, "TEXT",  
                         Event.COLUMN_NAME_LOGO_URL, "TEXT",  
                         Event.COLUMN_NAME_WEBSITE_URL, "TEXT");  
               //create lectures table  
               DBUtils.createTable(db, sb,   
                         Lecture.TABLE_NAME,  
                         Lecture.COLUMN_NAME_ID, "INTEGER PRIMARY KEY ",  
                         Lecture.COLUMN_NAME_DESCRIPTION, "TEXT",  
                         Lecture.COLUMN_NAME_DURATION, "TEXT",  
                         Lecture.COLUMN_NAME_EVENT_ID, "INTEGER",  
                         Lecture.COLUMN_NAME_NAME, "TEXT",   
                         Lecture.COLUMN_NAME_SLIDES_URL, "TEXT",  
                         Lecture.COLUMN_NAME_VIDEO_URL, "TEXT",  
                         Lecture.COLUMN_NAME_YOUTUBE_ASSET_ID, "TEXT");  
               //create speakers table  
               DBUtils.createTable(db, sb,   
                         Speaker.TABLE_NAME,  
                         Speaker.COLUMN_NAME_ID, "INTEGER PRIMARY KEY",  
                         Speaker.COLUMN_FIRST_NAME, "TEXT",  
                         Speaker.COLUMN_LAST_NAME, "TEXT",  
                         Speaker.COLUMN_NAME_BIO, "TEXT",  
                         Speaker.COLUMN_NAME_IMAGE_URL, "TEXT");  
               //create lecture <-> speaker pair table  
               DBUtils.createTable(db, sb, LECTURE_SPEAKER_PAIT_TABLE,   
                         PAIR_LECTURE_ID, "TEXT" ,  
                         PAIR_SPEAKER_ID, "TEXT");  
               }  
               catch (Exception e) {  
                    e.printStackTrace();  
               }  
          }  
          @Override  
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
               StringBuilder sb = new StringBuilder();  
               DBUtils.dropTable(db,sb , Event.TABLE_NAME);  
               DBUtils.dropTable(db,sb , Lecture.TABLE_NAME);  
               DBUtils.dropTable(db,sb , Speaker.TABLE_NAME);  
               DBUtils.dropTable(db,sb , LECTURE_SPEAKER_PAIT_TABLE);  
               onCreate(db);  
          }  
     }  
    

    For further explanations about the database manipulation code i'm afraid you will have to wait for my next post which will cover everything you need to know about the Android ContentProviders

    I would like to conclude this post by saying thanks again to +Ran Nachmany and +Amir Lazarovich which wrote most of the code which I used in this post and to link you to the full source code for AndConLab here: https://github.com/RanNachmany/AndconLab

    I would also like to thank the great Jake Wharton who created the wonderful Action Bar Sherlock which we used here and in pretty much everything else.

    הפוסט זמין גם בעברית כאן - http://iandroid.co.il/dr-iandroid/archives/14998

    Royi is a Google Developer Expert for Android in 2013, a mentor at Google's CampusTLV for Android and (last but not least) the set top box team leader at Vidmind, an OTT TV and Video Platform Provider. www.vidmind.com