Bug 891105: Use AsyncTaskLoaders for thumbnails. [r=lucasr]
authorSriram Ramasubramanian <sriram@mozilla.com>
Tue, 09 Jul 2013 16:24:38 -0700
changeset 143400 a0658f1018a65f5fd2d9d5c7e6d93b1e0182a822
parent 143399 fabc7deeeb21834fbae7b6bd0c5f2cfca982d09a
child 143401 f3f76c0ea2ce93ec8787afc7955c3d28ee1ecadf
push id25130
push userlrocha@mozilla.com
push dateWed, 21 Aug 2013 09:41:27 +0000
treeherdermozilla-central@b2486721572e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslucasr
bugs891105
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 891105: Use AsyncTaskLoaders for thumbnails. [r=lucasr]
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/TopBookmarksView.java
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -10,67 +10,74 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopBookmarksView.Thumbnail;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
 public class BookmarksPage extends HomeFragment {
     public static final String LOGTAG = "GeckoBookmarksPage";
 
     // Cursor loader ID for list of bookmarks.
     private static final int BOOKMARKS_LIST_LOADER_ID = 0;
 
     // Cursor loader ID for grid of bookmarks.
     private static final int TOP_BOOKMARKS_LOADER_ID = 1;
 
+    // Loader ID for thumbnails.
+    private static final int THUMBNAILS_LOADER_ID = 2;
+
     // Key for bookmarks folder id.
     private static final String BOOKMARKS_FOLDER_KEY = "folder_id";
 
+    // Key for thumbnail urls.
+    private static final String THUMBNAILS_URLS_KEY = "urls";
+
     // List of bookmarks.
     private BookmarksListView mList;
 
     // Grid of top bookmarks.
     private TopBookmarksView mTopBookmarks;
 
     // Adapter for list of bookmarks.
     private BookmarksListAdapter mListAdapter;
 
     // Adapter for grid of bookmarks.
     private TopBookmarksAdapter mTopBookmarksAdapter;
 
-    // Callback for loaders.
+    // Callback for cursor loaders.
     private CursorLoaderCallbacks mLoaderCallbacks;
 
+    // Callback for thumbnail loader.
+    private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         BookmarksListView list = (BookmarksListView) inflater.inflate(R.layout.home_bookmarks_page, container, false);
 
         mTopBookmarks = new TopBookmarksView(getActivity());
         list.addHeaderView(mTopBookmarks);
 
         return list;
@@ -114,16 +121,17 @@ public class BookmarksPage extends HomeF
                 bundle.putInt(BOOKMARKS_FOLDER_KEY, folderId);
                 getLoaderManager().restartLoader(BOOKMARKS_LIST_LOADER_ID, bundle, mLoaderCallbacks);
             }
         });
         mList.setAdapter(mListAdapter);
 
         // Create callbacks before the initial loader is started.
         mLoaderCallbacks = new CursorLoaderCallbacks();
+        mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
 
         // Reconnect to the loader only if present.
         final LoaderManager manager = getLoaderManager();
         manager.initLoader(BOOKMARKS_LIST_LOADER_ID, null, mLoaderCallbacks);
         manager.initLoader(TOP_BOOKMARKS_LOADER_ID, null, mLoaderCallbacks);
     }
 
     @Override
@@ -222,18 +230,28 @@ public class BookmarksPage extends HomeF
                     mListAdapter.swapCursor(c);
                     break;
                 }
 
                 case TOP_BOOKMARKS_LOADER_ID: {
                     mTopBookmarksAdapter.swapCursor(c);
 
                     // Load the thumbnails.
-                    if (c.getCount() > 0) {
-                        new LoadThumbnailsTask(getActivity(), mTopBookmarks).execute(c);
+                    if (c.getCount() > 0 && c.moveToFirst()) {
+                        final ArrayList<String> urls = new ArrayList<String>();
+                        do {
+                            final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
+                            urls.add(url);
+                        } while (c.moveToNext());
+
+                        if (urls.size() > 0) {
+                            Bundle bundle = new Bundle();
+                            bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
+                            getLoaderManager().restartLoader(THUMBNAILS_LOADER_ID, bundle, mThumbnailsLoaderCallbacks);
+                        }
                     }
                     break;
                 }
             }
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
@@ -252,51 +270,38 @@ public class BookmarksPage extends HomeF
                         break;
                     }
                 }
             }
         }
     }
 
     /**
-     * An AsyncTask to load the thumbnails from a cursor.
+     * An AsyncTaskLoader to load the thumbnails from a cursor.
      */
-    private static class LoadThumbnailsTask extends UiAsyncTask<Cursor, Void, Map<String, Thumbnail>> {
-        private final Context mContext;
-        private final TopBookmarksView mView;
+    private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
+        private Map<String, Thumbnail> mThumbnails;
+        private ArrayList<String> mUrls;
 
-        public LoadThumbnailsTask(Context context, TopBookmarksView view) {
-            super(ThreadUtils.getBackgroundHandler());
-            mContext = context;
-            mView = view;
+        public ThumbnailsLoader(Context context, ArrayList<String> urls) {
+            super(context);
+            mUrls = urls;
         }
 
         @Override
-        protected Map<String, Thumbnail> doInBackground(Cursor... params) {
-            // TopBookmarksAdapter's cursor.
-            final Cursor adapterCursor = params[0];
-            if (adapterCursor == null || !adapterCursor.moveToFirst()) {
-                return null;
-            }
-
-            final List<String> urls = new ArrayList<String>();
-            do {
-                final String url = adapterCursor.getString(adapterCursor.getColumnIndexOrThrow(URLColumns.URL));
-                urls.add(url);
-            } while (adapterCursor.moveToNext());
-
-            if (urls.size() == 0) {
+        public Map<String, Thumbnail> loadInBackground() {
+            if (mUrls == null || mUrls.size() == 0) {
                 return null;
             }
 
             final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
 
             // Query the DB for thumbnails.
-            final ContentResolver cr = mContext.getContentResolver();
-            final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, urls);
+            final ContentResolver cr = getContext().getContentResolver();
+            final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
 
             try {
                 if (cursor != null && cursor.moveToFirst()) {
                     do {
                         // Try to get the thumbnail, if cursor is valid.
                         String url = cursor.getString(cursor.getColumnIndexOrThrow(Thumbnails.URL));
                         final byte[] b = cursor.getBlob(cursor.getColumnIndexOrThrow(Thumbnails.DATA));
                         final Bitmap bitmap = (b == null || b.length == 0 ? null : BitmapUtils.decodeByteArray(b));
@@ -308,31 +313,92 @@ public class BookmarksPage extends HomeF
                 }
             } finally {
                 if (cursor != null) {
                     cursor.close();
                 }
             }
 
             // Query the DB for favicons for the urls without thumbnails.
-            for (String url : urls) {
+            for (String url : mUrls) {
                 if (!thumbnails.containsKey(url)) {
                     final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
                     if (bitmap != null) {
                         // Favicons.scaleImage can return several different size favicons,
                         // but will at least prevent this from being too large.
                         thumbnails.put(url, new Thumbnail(Favicons.getInstance().scaleImage(bitmap), false));
                     }
                 }
             }
 
             return thumbnails;
         }
 
         @Override
-        public void onPostExecute(Map<String, Thumbnail> thumbnails) {
-            // Check to see if the view is still attached.
-            if (mView.getHandler() != null) {
-                mView.updateThumbnails(thumbnails);
+        public void deliverResult(Map<String, Thumbnail> thumbnails) {
+            if (isReset()) {
+                mThumbnails = null;
+                return;
+            }
+
+            mThumbnails = thumbnails;
+
+            if (isStarted()) {
+                super.deliverResult(thumbnails);
+            }
+        }
+
+        @Override
+        protected void onStartLoading() {
+            if (mThumbnails != null) {
+                deliverResult(mThumbnails);
+            }
+
+            if (takeContentChanged() || mThumbnails == null) {
+                forceLoad();
+            }
+        }
+
+        @Override
+        protected void onStopLoading() {
+            cancelLoad();
+        }
+
+        @Override
+        public void onCanceled(Map<String, Thumbnail> thumbnails) {
+            mThumbnails = null;
+        }
+
+        @Override
+        protected void onReset() {
+            super.onReset();
+
+            // Ensure the loader is stopped.
+            onStopLoading();
+
+            mThumbnails = null;
+        }
+    }
+
+    /**
+     * Loader callbacks for the thumbnails on TopBookmarksView.
+     */
+    private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
+        @Override
+        public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
+            return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
+            if (mTopBookmarks != null) {
+                mTopBookmarks.updateThumbnails(thumbnails);
+            }
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
+            if (mTopBookmarks != null) {
+                mTopBookmarks.updateThumbnails(null);
             }
         }
     }
 }
--- a/mobile/android/base/home/TopBookmarksView.java
+++ b/mobile/android/base/home/TopBookmarksView.java
@@ -176,16 +176,20 @@ public class TopBookmarksView extends Gr
     }
 
     /**
      * Update the thumbnails returned by the db.
      *
      * @param thumbnails A map of urls and their thumbnail bitmaps.
      */
     public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
+        if (thumbnails == null) {
+            return;
+        }
+
         // If there's a layout scheduled on this view, wait for it to happen
         // by storing the thumbnails in a cache. If not, update them right away.
         if (isLayoutRequested()) {
             mThumbnailsCache = thumbnails;
             return;
         }
 
         final int count = getAdapter().getCount();