OSDN Git Service

Add a new WebCoreWorker thread to handle the tasks
authorGrace Kloba <klobag@google.com>
Mon, 15 Feb 2010 10:15:37 +0000 (02:15 -0800)
committerGrace Kloba <klobag@google.com>
Mon, 1 Mar 2010 18:36:15 +0000 (10:36 -0800)
which should not block either UI or WebKit. It handles
local file access, cache access and trim cache.

Move createCache, saveCache and most of getCache out
of WebCore thread so that slow IO and database will
not affect loading performance. getCache can be still
called from WebCore thread in the uncommon cases
like redirect and POST validation.

Move cache ticker from WebCore thread to WebViewWorkerThread.

Move setCookie from WebCore thread to WebViewWorkerThread.

Remove the unreferenced files in the cache directory
while trim cache.

Confirmed with our SQL expert, Vasu, there is no need
to wrap clearCache with end/startTransaction any more.

http://b/issue?id=2414792
http://b/issue?id=2475242

api/current.xml
core/java/android/webkit/BrowserFrame.java
core/java/android/webkit/CacheManager.java
core/java/android/webkit/FrameLoader.java
core/java/android/webkit/LoadListener.java
core/java/android/webkit/StreamLoader.java
core/java/android/webkit/WebViewCore.java
core/java/android/webkit/WebViewDatabase.java
core/java/android/webkit/WebViewWorker.java [new file with mode: 0644]

index 3b90f38..89af2ac 100644 (file)
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 </method>
index 3f1672a..1c0d55f 100644 (file)
@@ -407,7 +407,8 @@ class BrowserFrame extends Handler {
                         }
                     }
                 }
-                CacheManager.trimCacheIfNeeded();
+                WebViewWorker.getHandler().sendEmptyMessage(
+                        WebViewWorker.MSG_TRIM_CACHE);
                 break;
             }
 
index 647556b..1c59c10 100644 (file)
@@ -25,10 +25,11 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 
@@ -200,9 +201,9 @@ public final class CacheManager {
             // the cache database. The directory could be recreated
             // because the system flushed all the data/cache directories
             // to free up disk space.
-            WebViewCore.endCacheTransaction();
-            mDataBase.clearCache();
-            WebViewCore.startCacheTransaction();
+            // delete rows in the cache database
+            WebViewWorker.getHandler().sendEmptyMessage(
+                    WebViewWorker.MSG_CLEAR_CACHE);
             return true;
         }
         return false;
@@ -223,7 +224,6 @@ public final class CacheManager {
      * 
      * @param disabled true to disable the cache
      */
-    // only called from WebCore thread
     static void setCacheDisabled(boolean disabled) {
         if (disabled == mDisabled) {
             return;
@@ -243,7 +243,7 @@ public final class CacheManager {
         return mDisabled;
     }
 
-    // only called from WebCore thread
+    // only called from WebViewWorkerThread
     // make sure to call enableTransaction/disableTransaction in pair
     static boolean enableTransaction() {
         if (++mRefCount == 1) {
@@ -253,12 +253,9 @@ public final class CacheManager {
         return false;
     }
 
-    // only called from WebCore thread
+    // only called from WebViewWorkerThread
     // make sure to call enableTransaction/disableTransaction in pair
     static boolean disableTransaction() {
-        if (mRefCount == 0) {
-            Log.e(LOGTAG, "disableTransaction is out of sync");
-        }
         if (--mRefCount == 0) {
             mDataBase.endCacheTransaction();
             return true;
@@ -266,15 +263,15 @@ public final class CacheManager {
         return false;
     }
 
-    // only called from WebCore thread
-    // make sure to call startCacheTransaction/endCacheTransaction in pair
-    public static boolean startCacheTransaction() {
+    // only called from WebViewWorkerThread
+    // make sure to call startTransaction/endTransaction in pair
+    static boolean startTransaction() {
         return mDataBase.startCacheTransaction();
     }
 
-    // only called from WebCore thread
-    // make sure to call startCacheTransaction/endCacheTransaction in pair
-    public static boolean endCacheTransaction() {
+    // only called from WebViewWorkerThread
+    // make sure to call startTransaction/endTransaction in pair
+    static boolean endTransaction() {
         boolean ret = mDataBase.endCacheTransaction();
         if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
             mTrimCacheCount = 0;
@@ -283,6 +280,26 @@ public final class CacheManager {
         return ret;
     }
 
+    // only called from WebCore Thread
+    // make sure to call startCacheTransaction/endCacheTransaction in pair
+    /**
+     * @deprecated
+     */
+    @Deprecated
+    public static boolean startCacheTransaction() {
+        return false;
+    }
+
+    // only called from WebCore Thread
+    // make sure to call startCacheTransaction/endCacheTransaction in pair
+    /**
+     * @deprecated
+     */
+    @Deprecated
+    public static boolean endCacheTransaction() {
+        return false;
+    }
+
     /**
      * Given a url, returns the CacheResult if exists. Otherwise returns null.
      * If headers are provided and a cache needs validation,
@@ -291,13 +308,11 @@ public final class CacheManager {
      * 
      * @return the CacheResult for a given url
      */
-    // only called from WebCore thread
     public static CacheResult getCacheFile(String url,
             Map<String, String> headers) {
         return getCacheFile(url, 0, headers);
     }
 
-    // only called from WebCore thread
     static CacheResult getCacheFile(String url, long postIdentifier,
             Map<String, String> headers) {
         if (mDisabled) {
@@ -368,14 +383,12 @@ public final class CacheManager {
      * @hide - hide createCacheFile since it has a parameter of type headers, which is
      * in a hidden package.
      */
-    // only called from WebCore thread
     public static CacheResult createCacheFile(String url, int statusCode,
             Headers headers, String mimeType, boolean forceCache) {
         return createCacheFile(url, statusCode, headers, mimeType, 0,
                 forceCache);
     }
 
-    // only called from WebCore thread
     static CacheResult createCacheFile(String url, int statusCode,
             Headers headers, String mimeType, long postIdentifier,
             boolean forceCache) {
@@ -435,12 +448,10 @@ public final class CacheManager {
      * Save the info of a cache file for a given url to the CacheMap so that it
      * can be reused later
      */
-    // only called from WebCore thread
     public static void saveCacheFile(String url, CacheResult cacheRet) {
         saveCacheFile(url, 0, cacheRet);
     }
 
-    // only called from WebCore thread
     static void saveCacheFile(String url, long postIdentifier,
             CacheResult cacheRet) {
         try {
@@ -489,7 +500,6 @@ public final class CacheManager {
      * 
      * @return true if it succeeds
      */
-    // only called from WebCore thread
     static boolean removeAllCacheFiles() {
         // Note, this is called before init() when the database is
         // created or upgraded.
@@ -499,7 +509,10 @@ public final class CacheManager {
             mClearCacheOnInit = true;
             return true;
         }
-        // delete cache in a separate thread to not block UI.
+        // delete rows in the cache database
+        WebViewWorker.getHandler().sendEmptyMessage(
+                WebViewWorker.MSG_CLEAR_CACHE);
+        // delete cache files in a separate thread to not block UI.
         final Runnable clearCache = new Runnable() {
             public void run() {
                 // delete all cache files
@@ -517,8 +530,6 @@ public final class CacheManager {
                 } catch (SecurityException e) {
                     // Ignore SecurityExceptions.
                 }
-                // delete database
-                mDataBase.clearCache();
             }
         };
         new Thread(clearCache).start();
@@ -528,15 +539,13 @@ public final class CacheManager {
     /**
      * Return true if the cache is empty.
      */
-    // only called from WebCore thread
     static boolean cacheEmpty() {
         return mDataBase.hasCache();
     }
 
-    // only called from WebCore thread
     static void trimCacheIfNeeded() {
         if (mDataBase.getCacheTotalSize() > CACHE_THRESHOLD) {
-            ArrayList<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
+            List<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
             int size = pathList.size();
             for (int i = 0; i < size; i++) {
                 File f = new File(mBaseDir, pathList.get(i));
@@ -544,9 +553,34 @@ public final class CacheManager {
                     Log.e(LOGTAG, f.getPath() + " delete failed.");
                 }
             }
+            // remove the unreferenced files in the cache directory
+            final List<String> fileList = mDataBase.getAllCacheFileNames();
+            if (fileList == null) return;
+            String[] toDelete = mBaseDir.list(new FilenameFilter() {
+                public boolean accept(File dir, String filename) {
+                    if (fileList.contains(filename)) {
+                        return false;
+                    } else {
+                        return true;
+                    }
+                }
+            });
+            if (toDelete == null) return;
+            size = toDelete.length;
+            for (int i = 0; i < size; i++) {
+                File f = new File(mBaseDir, toDelete[i]);
+                if (!f.delete()) {
+                    Log.e(LOGTAG, f.getPath() + " delete failed.");
+                }
+            }
         }
     }
 
+    static void clearCache() {
+        // delete database
+        mDataBase.clearCache();
+    }
+
     private static boolean checkCacheRedirect(int statusCode) {
         if (statusCode == 301 || statusCode == 302 || statusCode == 307) {
             // as 303 can't be cached, we do not return true
index b13c405..7903632 100644 (file)
@@ -110,7 +110,9 @@ class FrameLoader {
                 return false;
             }
             mNetwork = Network.getInstance(mListener.getContext());
-            return handleHTTPLoad();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
+            return true;
         } else if (handleLocalFile(url, mListener, mSettings)) {
             return true;
         }
@@ -142,24 +144,33 @@ class FrameLoader {
         }
         if (URLUtil.isAssetUrl(url)) {
             // load asset in a separate thread as it involves IO
-            new FileLoader(url, loadListener, FileLoader.TYPE_ASSET, true)
-                    .enqueue();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_ADD_STREAMLOADER,
+                    new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
+                            true)).sendToTarget();
             return true;
         } else if (URLUtil.isResourceUrl(url)) {
             // load resource in a separate thread as it involves IO
-            new FileLoader(url, loadListener, FileLoader.TYPE_RES, true)
-                    .enqueue();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_ADD_STREAMLOADER,
+                    new FileLoader(url, loadListener, FileLoader.TYPE_RES,
+                            true)).sendToTarget();
             return true;
         } else if (URLUtil.isFileUrl(url)) {
             // load file in a separate thread as it involves IO
-            new FileLoader(url, loadListener, FileLoader.TYPE_FILE, settings
-                    .getAllowFileAccess()).enqueue();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_ADD_STREAMLOADER,
+                    new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
+                            settings.getAllowFileAccess())).sendToTarget();
             return true;
         } else if (URLUtil.isContentUrl(url)) {
             // Send the raw url to the ContentLoader because it will do a
             // permission check and the url has to match.
             // load content in a separate thread as it involves IO
-            new ContentLoader(loadListener.url(), loadListener).enqueue();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_ADD_STREAMLOADER,
+                    new ContentLoader(loadListener.url(), loadListener))
+                    .sendToTarget();
             return true;
         } else if (URLUtil.isDataUrl(url)) {
             // load data in the current thread to reduce the latency
@@ -172,8 +183,8 @@ class FrameLoader {
         }
         return false;
     }
-    
-    private boolean handleHTTPLoad() {
+
+    boolean handleHTTPLoad() {
         if (mHeaders == null) {
             mHeaders = new HashMap<String, String>();
         }
@@ -229,7 +240,9 @@ class FrameLoader {
         CacheLoader cacheLoader =
                 new CacheLoader(mListener, result);
         mListener.setCacheLoader(cacheLoader);
-        cacheLoader.load();
+        // Load the cached file in a separate thread
+        WebViewWorker.getHandler().obtainMessage(
+                WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
     }
 
     /*
index cdc6608..8bacee4 100644 (file)
@@ -101,7 +101,6 @@ class LoadListener extends Handler implements EventHandler {
     private boolean  mCancelled;  // The request has been cancelled.
     private boolean  mAuthFailed;  // indicates that the prev. auth failed
     private CacheLoader mCacheLoader;
-    private CacheManager.CacheResult mCacheResult;
     private boolean  mFromCache = false;
     private HttpAuthHeader mAuthHeader;
     private int      mErrorID = OK;
@@ -301,6 +300,12 @@ class LoadListener extends Handler implements EventHandler {
      */
     public void headers(Headers headers) {
         if (DebugFlags.LOAD_LISTENER) Log.v(LOGTAG, "LoadListener.headers");
+        // call db (setCookie) in the non-WebCore thread
+        if (mCancelled) return;
+        ArrayList<String> cookies = headers.getSetCookie();
+        for (int i = 0; i < cookies.size(); ++i) {
+            CookieManager.getInstance().setCookie(mUri, cookies.get(i));
+        }
         sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
     }
 
@@ -316,11 +321,6 @@ class LoadListener extends Handler implements EventHandler {
         if (mCancelled) return;
         mHeaders = headers;
 
-        ArrayList<String> cookies = headers.getSetCookie();
-        for (int i = 0; i < cookies.size(); ++i) {
-            CookieManager.getInstance().setCookie(mUri, cookies.get(i));
-        }
-
         long contentLength = headers.getContentLength();
         if (contentLength != Headers.NO_CONTENT_LENGTH) {
             mContentLength = contentLength;
@@ -454,12 +454,19 @@ class LoadListener extends Handler implements EventHandler {
             if (!mFromCache && mRequestHandle != null
                     && (!mRequestHandle.getMethod().equals("POST")
                             || mPostIdentifier != 0)) {
-                mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
-                        headers, mMimeType, mPostIdentifier, false);
-            }
-            if (mCacheResult != null) {
-                mCacheResult.encoding = mEncoding;
+                WebViewWorker.CacheCreateData data = new WebViewWorker.CacheCreateData();
+                data.mListener = this;
+                data.mUrl = mUrl;
+                data.mMimeType = mMimeType;
+                data.mStatusCode = mStatusCode;
+                data.mPostId = mPostIdentifier;
+                data.mHeaders = headers;
+                WebViewWorker.getHandler().obtainMessage(
+                        WebViewWorker.MSG_CREATE_CACHE, data).sendToTarget();
             }
+            WebViewWorker.CacheEncoding ce = new WebViewWorker.CacheEncoding();
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_UPDATE_CACHE_ENCODING, ce).sendToTarget();
         }
         commitHeadersCheckRedirect();
     }
@@ -649,7 +656,10 @@ class LoadListener extends Handler implements EventHandler {
                 // ask for it, so make sure we have a valid CacheLoader
                 // before calling it.
                 if (mCacheLoader != null) {
-                    mCacheLoader.load();
+                    // Load the cached file in a separate thread
+                    WebViewWorker.getHandler().obtainMessage(
+                            WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
+                            .sendToTarget();
                     mFromCache = true;
                     if (DebugFlags.LOAD_LISTENER) {
                         Log.v(LOGTAG, "LoadListener cache load url=" + url());
@@ -708,8 +718,10 @@ class LoadListener extends Handler implements EventHandler {
                     Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " +
                             "and usable: " + url());
                 }
-                // Load the cached file
-                mCacheLoader.load();
+                // Load the cached file in a separate thread
+                WebViewWorker.getHandler().obtainMessage(
+                        WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
+                        .sendToTarget();
                 mFromCache = true;
                 return true;
             }
@@ -934,12 +946,9 @@ class LoadListener extends Handler implements EventHandler {
      * WebCore.
      */
     void downloadFile() {
-        // Setting the Cache Result to null ensures that this
-        // content is not added to the cache
-        if (mCacheResult != null) {
-            CacheManager.cleanupCacheFile(mCacheResult);
-            mCacheResult = null;
-        }
+        // remove the cache
+        WebViewWorker.getHandler().obtainMessage(
+                WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
 
         // Inform the client that they should download a file
         mBrowserFrame.getCallbackProxy().onDownloadStart(url(), 
@@ -1098,24 +1107,15 @@ class LoadListener extends Handler implements EventHandler {
             if (c == null) break;
 
             if (c.mLength != 0) {
-                if (mCacheResult != null) {
-                    mCacheResult.contentLength += c.mLength;
-                    if (mCacheResult.contentLength > CacheManager.CACHE_MAX_SIZE) {
-                        CacheManager.cleanupCacheFile(mCacheResult);
-                        mCacheResult = null;
-                    } else {
-                        try {
-                            mCacheResult.outStream
-                                    .write(c.mArray, 0, c.mLength);
-                        } catch (IOException e) {
-                            CacheManager.cleanupCacheFile(mCacheResult);
-                            mCacheResult = null;
-                        }
-                    }
-                }
                 nativeAddData(c.mArray, c.mLength);
+                WebViewWorker.CacheData data = new WebViewWorker.CacheData();
+                data.mListener = this;
+                data.mChunk = c;
+                WebViewWorker.getHandler().obtainMessage(
+                        WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget();
+            } else {
+                c.release();
             }
-            c.release();
             checker.responseAlert("res nativeAddData");
         }
     }
@@ -1125,18 +1125,16 @@ class LoadListener extends Handler implements EventHandler {
      * cancellation or errors during the load.
      */
     void tearDown() {
-        if (mCacheResult != null) {
-            if (getErrorID() == OK) {
-                CacheManager.saveCacheFile(mUrl, mPostIdentifier, mCacheResult);
-            } else {
-                CacheManager.cleanupCacheFile(mCacheResult);
-            }
-
-            // we need to reset mCacheResult to be null
-            // resource loader's tearDown will call into WebCore's
-            // nativeFinish, which in turn calls loader.cancel().
-            // If we don't reset mCacheFile, the file will be deleted.
-            mCacheResult = null;
+        if (getErrorID() == OK) {
+            WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
+            data.mListener = this;
+            data.mUrl = mUrl;
+            data.mPostId = mPostIdentifier;
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
+        } else {
+            WebViewWorker.getHandler().obtainMessage(
+                    WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
         }
         if (mNativeLoader != 0) {
             PerfChecker checker = new PerfChecker();
@@ -1194,10 +1192,8 @@ class LoadListener extends Handler implements EventHandler {
             mRequestHandle = null;
         }
 
-        if (mCacheResult != null) {
-            CacheManager.cleanupCacheFile(mCacheResult);
-            mCacheResult = null;
-        }
+        WebViewWorker.getHandler().obtainMessage(
+                WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
         mCancelled = true;
 
         clearNativeLoader();
@@ -1258,14 +1254,16 @@ class LoadListener extends Handler implements EventHandler {
             }
 
             // Cache the redirect response
-            if (mCacheResult != null) {
-                if (getErrorID() == OK) {
-                    CacheManager.saveCacheFile(mUrl, mPostIdentifier,
-                            mCacheResult);
-                } else {
-                    CacheManager.cleanupCacheFile(mCacheResult);
-                }
-                mCacheResult = null;
+            if (getErrorID() == OK) {
+                WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
+                data.mListener = this;
+                data.mUrl = mUrl;
+                data.mPostId = mPostIdentifier;
+                WebViewWorker.getHandler().obtainMessage(
+                        WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
+            } else {
+                WebViewWorker.getHandler().obtainMessage(
+                        WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
             }
 
             // This will strip the anchor
index 4c32997..7bcd50d 100644 (file)
@@ -20,8 +20,6 @@ import android.content.Context;
 import android.net.http.EventHandler;
 import android.net.http.Headers;
 import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
 
 import java.io.IOException;
@@ -61,11 +59,6 @@ abstract class StreamLoader implements Handler.Callback {
     // Handler which will be initialized in the thread where load() is called.
     private Handler mHandler;
 
-    // Handler which will be used to load StreamLoader in a separate thread
-    private static StreamQueueHandler sStreamQueueHandler;
-
-    private static final Object sStreamQueueLock = new Object();
-
     /**
      * Constructor. Although this class calls the LoadListener, it only calls
      * the EventHandler Interface methods. LoadListener concrete class is used
@@ -97,26 +90,6 @@ abstract class StreamLoader implements Handler.Callback {
     abstract protected void buildHeaders(Headers headers);
 
     /**
-     * Calling this method to load this StreamLoader in a separate
-     * "StreamLoadingThread".
-     */
-    final void enqueue() {
-        synchronized (sStreamQueueLock) {
-            if (sStreamQueueHandler == null) {
-                HandlerThread thread = new HandlerThread(
-                        StreamQueueHandler.THREAD_NAME,
-                        android.os.Process.THREAD_PRIORITY_DEFAULT +
-                        android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
-                thread.start();
-                sStreamQueueHandler = new StreamQueueHandler(thread.getLooper());
-            }
-        }
-
-        sStreamQueueHandler.obtainMessage(StreamQueueHandler.MSG_ADD_LOADER,
-                this).sendToTarget();
-    }
-
-    /**
      * Calling this method starts the load of the content for this StreamLoader.
      * This method simply creates a Handler in the current thread and posts a
      * message to send the status and returns immediately.
@@ -228,22 +201,4 @@ abstract class StreamLoader implements Handler.Callback {
         }
         mLoadListener.endData();
     }
-
-    private static class StreamQueueHandler extends Handler {
-        private static final String THREAD_NAME = "StreamLoadingThread";
-
-        private static final int MSG_ADD_LOADER = 101;
-
-        StreamQueueHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_ADD_LOADER) {
-                StreamLoader loader = (StreamLoader) msg.obj;
-                loader.load();
-            }
-        }
-    }
 }
index 4606bc6..71f69fe 100644 (file)
@@ -587,13 +587,6 @@ final class WebViewCore {
         private static final int INITIALIZE = 0;
         private static final int REDUCE_PRIORITY = 1;
         private static final int RESUME_PRIORITY = 2;
-        private static final int CACHE_TICKER = 3;
-        private static final int BLOCK_CACHE_TICKER = 4;
-        private static final int RESUME_CACHE_TICKER = 5;
-
-        private static final int CACHE_TICKER_INTERVAL = 60 * 1000; // 1 minute
-
-        private static boolean mCacheTickersBlocked = true;
 
         public void run() {
             Looper.prepare();
@@ -619,28 +612,6 @@ final class WebViewCore {
                                 Process.setThreadPriority(
                                         Process.THREAD_PRIORITY_DEFAULT);
                                 break;
-
-                            case CACHE_TICKER:
-                                if (!mCacheTickersBlocked) {
-                                    CacheManager.endCacheTransaction();
-                                    CacheManager.startCacheTransaction();
-                                    sendMessageDelayed(
-                                            obtainMessage(CACHE_TICKER),
-                                            CACHE_TICKER_INTERVAL);
-                                }
-                                break;
-
-                            case BLOCK_CACHE_TICKER:
-                                if (CacheManager.endCacheTransaction()) {
-                                    mCacheTickersBlocked = true;
-                                }
-                                break;
-
-                            case RESUME_CACHE_TICKER:
-                                if (CacheManager.startCacheTransaction()) {
-                                    mCacheTickersBlocked = false;
-                                }
-                                break;
                         }
                     }
                 };
@@ -1092,23 +1063,15 @@ final class WebViewCore {
                             Process.setThreadPriority(mTid,
                                     Process.THREAD_PRIORITY_BACKGROUND);
                             pauseTimers();
-                            if (CacheManager.disableTransaction()) {
-                                WebCoreThread.mCacheTickersBlocked = true;
-                                sWebCoreHandler.removeMessages(
-                                        WebCoreThread.CACHE_TICKER);
-                            }
+                            WebViewWorker.getHandler().sendEmptyMessage(
+                                    WebViewWorker.MSG_PAUSE_CACHE_TRANSACTION);
                             break;
 
                         case RESUME_TIMERS:
                             Process.setThreadPriority(mTid, mSavedPriority);
                             resumeTimers();
-                            if (CacheManager.enableTransaction()) {
-                                WebCoreThread.mCacheTickersBlocked = false;
-                                sWebCoreHandler.sendMessageDelayed(
-                                        sWebCoreHandler.obtainMessage(
-                                        WebCoreThread.CACHE_TICKER),
-                                        WebCoreThread.CACHE_TICKER_INTERVAL);
-                            }
+                            WebViewWorker.getHandler().sendEmptyMessage(
+                                    WebViewWorker.MSG_RESUME_CACHE_TRANSACTION);
                             break;
 
                         case ON_PAUSE:
@@ -1851,16 +1814,6 @@ final class WebViewCore {
                 .obtainMessage(WebCoreThread.RESUME_PRIORITY));
     }
 
-    static void startCacheTransaction() {
-        sWebCoreHandler.sendMessage(sWebCoreHandler
-                .obtainMessage(WebCoreThread.RESUME_CACHE_TICKER));
-    }
-
-    static void endCacheTransaction() {
-        sWebCoreHandler.sendMessage(sWebCoreHandler
-                .obtainMessage(WebCoreThread.BLOCK_CACHE_TICKER));
-    }
-
     static void pauseUpdatePicture(WebViewCore core) {
         // Note: there is one possible failure mode. If pauseUpdatePicture() is
         // called from UI thread while WEBKIT_DRAW is just pulled out of the
@@ -1992,9 +1945,10 @@ final class WebViewCore {
         sendUpdateTextEntry();
         // as CacheManager can behave based on database transaction, we need to
         // call tick() to trigger endTransaction
-        sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER);
-        sWebCoreHandler.sendMessage(sWebCoreHandler
-                .obtainMessage(WebCoreThread.CACHE_TICKER));
+        WebViewWorker.getHandler().removeMessages(
+                WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
+        WebViewWorker.getHandler().sendEmptyMessage(
+                WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
         contentDraw();
     }
 
index 110e4f8..a870931 100644 (file)
@@ -19,6 +19,7 @@ package android.webkit;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 import java.util.Map.Entry;
 
@@ -234,6 +235,13 @@ public class WebViewDatabase {
             }
 
             if (mCacheDatabase != null) {
+                // use read_uncommitted to speed up READ
+                mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
+                // as only READ can be called in the non-WebViewWorkerThread,
+                // and read_uncommitted is used, we can turn off database lock
+                // to use transaction.
+                mCacheDatabase.setLockingEnabled(false);
+
                 // use InsertHelper for faster insertion
                 mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase,
                         "cache");
@@ -548,19 +556,33 @@ public class WebViewDatabase {
     }
 
     //
-    // cache functions, can only be called from WebCoreThread
+    // cache functions
     //
 
+    // only called from WebViewWorkerThread
     boolean startCacheTransaction() {
         if (++mCacheTransactionRefcount == 1) {
+            if (!Thread.currentThread().equals(
+                    WebViewWorker.getHandler().getLooper().getThread())) {
+                Log.w(LOGTAG, "startCacheTransaction should be called from "
+                        + "WebViewWorkerThread instead of from "
+                        + Thread.currentThread().getName());
+            }
             mCacheDatabase.beginTransaction();
             return true;
         }
         return false;
     }
 
+    // only called from WebViewWorkerThread
     boolean endCacheTransaction() {
         if (--mCacheTransactionRefcount == 0) {
+            if (!Thread.currentThread().equals(
+                    WebViewWorker.getHandler().getLooper().getThread())) {
+                Log.w(LOGTAG, "endCacheTransaction should be called from "
+                        + "WebViewWorkerThread instead of from "
+                        + Thread.currentThread().getName());
+            }
             try {
                 mCacheDatabase.setTransactionSuccessful();
             } finally {
@@ -684,7 +706,7 @@ public class WebViewDatabase {
         return size;
     }
 
-    ArrayList<String> trimCache(long amount) {
+    List<String> trimCache(long amount) {
         ArrayList<String> pathList = new ArrayList<String>(100);
         Cursor cursor = mCacheDatabase.rawQuery(
                 "SELECT contentlength, filepath FROM cache ORDER BY expires ASC",
@@ -727,6 +749,20 @@ public class WebViewDatabase {
         return pathList;
     }
 
+    List<String> getAllCacheFileNames() {
+        ArrayList<String> pathList = null;
+        Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache",
+                null);
+        if (cursor != null && cursor.moveToFirst()) {
+            pathList = new ArrayList<String>(cursor.getCount());
+            do {
+                pathList.add(cursor.getString(0));
+            } while (cursor.moveToNext());
+        }
+        cursor.close();
+        return pathList;
+    }
+
     //
     // password functions
     //
diff --git a/core/java/android/webkit/WebViewWorker.java b/core/java/android/webkit/WebViewWorker.java
new file mode 100644 (file)
index 0000000..c488150
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.net.http.Headers;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * WebViewWorker executes in a separate thread other than UI and WebViewCore. To
+ * avoid blocking UI or WebKit's execution, the caller can send a message to
+ * WebViewWorker.getHandler() and it will be handled in the WebViewWorkerThread.
+ */
+final class WebViewWorker extends Handler {
+
+    private static final String THREAD_NAME = "WebViewWorkerThread";
+
+    private static WebViewWorker sWorkerHandler;
+
+    private static Map<LoadListener, CacheManager.CacheResult> mCacheResultMap
+            = new HashMap<LoadListener, CacheManager.CacheResult>();
+
+    /**
+     * Package level class to be used while creating a cache entry.
+     */
+    static class CacheCreateData {
+        LoadListener mListener;
+        String mUrl;
+        String mMimeType;
+        int mStatusCode;
+        long mPostId;
+        Headers mHeaders;
+    }
+
+    /**
+     * Package level class to be used while saving a cache entry.
+     */
+    static class CacheSaveData {
+        LoadListener mListener;
+        String mUrl;
+        long mPostId;
+    }
+
+    /**
+     * Package level class to be used while updating a cache entry's encoding.
+     */
+    static class CacheEncoding {
+        LoadListener mListener;
+        String mEncoding;
+    }
+
+    /**
+     * Package level class to be used while appending data to a cache entry.
+     */
+    static class CacheData {
+        LoadListener mListener;
+        ByteArrayBuilder.Chunk mChunk;
+    }
+
+    static synchronized WebViewWorker getHandler() {
+        if (sWorkerHandler == null) {
+            HandlerThread thread = new HandlerThread(THREAD_NAME,
+                    android.os.Process.THREAD_PRIORITY_DEFAULT
+                            + android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+            thread.start();
+            sWorkerHandler = new WebViewWorker(thread.getLooper());
+        }
+        return sWorkerHandler;
+    }
+
+    private WebViewWorker(Looper looper) {
+        super(looper);
+    }
+
+    // trigger transaction once a minute
+    private static final int CACHE_TRANSACTION_TICKER_INTERVAL = 60 * 1000;
+
+    private static boolean mCacheTickersBlocked = true;
+
+    // message ids
+    static final int MSG_ADD_STREAMLOADER = 101;
+    static final int MSG_ADD_HTTPLOADER = 102;
+    static final int MSG_CREATE_CACHE = 103;
+    static final int MSG_UPDATE_CACHE_ENCODING = 104;
+    static final int MSG_APPEND_CACHE = 105;
+    static final int MSG_SAVE_CACHE = 106;
+    static final int MSG_REMOVE_CACHE = 107;
+    static final int MSG_TRIM_CACHE = 108;
+    static final int MSG_CLEAR_CACHE = 109;
+    static final int MSG_CACHE_TRANSACTION_TICKER = 110;
+    static final int MSG_PAUSE_CACHE_TRANSACTION = 111;
+    static final int MSG_RESUME_CACHE_TRANSACTION = 112;
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch(msg.what) {
+            case MSG_ADD_STREAMLOADER: {
+                StreamLoader loader = (StreamLoader) msg.obj;
+                loader.load();
+                break;
+            }
+            case MSG_ADD_HTTPLOADER: {
+                FrameLoader loader = (FrameLoader) msg.obj;
+                loader.handleHTTPLoad();
+                break;
+            }
+            case MSG_CREATE_CACHE: {
+                CacheCreateData data = (CacheCreateData) msg.obj;
+                CacheManager.CacheResult cache = CacheManager.createCacheFile(
+                        data.mUrl, data.mStatusCode, data.mHeaders,
+                        data.mMimeType, data.mPostId, false);
+                if (cache != null) {
+                    mCacheResultMap.put(data.mListener, cache);
+                } else {
+                    mCacheResultMap.remove(data.mListener);
+                }
+                break;
+            }
+            case MSG_UPDATE_CACHE_ENCODING: {
+                CacheEncoding data = (CacheEncoding) msg.obj;
+                CacheManager.CacheResult cache = mCacheResultMap
+                        .get(data.mListener);
+                if (cache != null) {
+                    cache.encoding = data.mEncoding;
+                }
+                break;
+            }
+            case MSG_APPEND_CACHE: {
+                CacheData data = (CacheData) msg.obj;
+                CacheManager.CacheResult cache = mCacheResultMap
+                        .get(data.mListener);
+                if (cache != null) {
+                    cache.contentLength += data.mChunk.mLength;
+                    if (cache.contentLength > CacheManager.CACHE_MAX_SIZE) {
+                        CacheManager.cleanupCacheFile(cache);
+                        mCacheResultMap.remove(data.mListener);
+                    } else {
+                        try {
+                            cache.outStream.write(data.mChunk.mArray, 0,
+                                    data.mChunk.mLength);
+                        } catch (IOException e) {
+                            CacheManager.cleanupCacheFile(cache);
+                            mCacheResultMap.remove(data.mListener);
+                        }
+                    }
+                }
+                data.mChunk.release();
+                break;
+            }
+            case MSG_SAVE_CACHE: {
+                CacheSaveData data = (CacheSaveData) msg.obj;
+                CacheManager.CacheResult cache = mCacheResultMap
+                        .get(data.mListener);
+                if (cache != null) {
+                    CacheManager.saveCacheFile(data.mUrl, data.mPostId, cache);
+                    mCacheResultMap.remove(data.mListener);
+                }
+                break;
+            }
+            case MSG_REMOVE_CACHE: {
+                LoadListener listener = (LoadListener) msg.obj;
+                CacheManager.CacheResult cache = mCacheResultMap.get(listener);
+                if (cache != null) {
+                    CacheManager.cleanupCacheFile(cache);
+                    mCacheResultMap.remove(listener);
+                }
+                break;
+            }
+            case MSG_TRIM_CACHE: {
+                CacheManager.trimCacheIfNeeded();
+                break;
+            }
+            case MSG_CLEAR_CACHE: {
+                CacheManager.clearCache();
+                break;
+            }
+            case MSG_CACHE_TRANSACTION_TICKER: {
+                if (!mCacheTickersBlocked) {
+                    CacheManager.endTransaction();
+                    CacheManager.startTransaction();
+                    sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
+                            CACHE_TRANSACTION_TICKER_INTERVAL);
+                }
+                break;
+            }
+            case MSG_PAUSE_CACHE_TRANSACTION: {
+                if (CacheManager.disableTransaction()) {
+                    mCacheTickersBlocked = true;
+                    removeMessages(MSG_CACHE_TRANSACTION_TICKER);
+                }
+                break;
+            }
+            case MSG_RESUME_CACHE_TRANSACTION: {
+                if (CacheManager.enableTransaction()) {
+                    mCacheTickersBlocked = false;
+                    sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
+                            CACHE_TRANSACTION_TICKER_INTERVAL);
+                }
+                break;
+            }
+        }
+    }
+}