diff options
author | Jan Erik Hanssen <jhanssen@codeaurora.org> | 2010-07-28 14:43:06 -0700 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2010-08-05 12:51:16 -0400 |
commit | 4aa3a0d31d8c73e7f377867005ddc512431b9319 (patch) | |
tree | a73d4a2b457ed14e0cbbf82ae2753cc1e9a0561e | |
parent | 6cc9aa1566f4b0dba5838da8ad231e54d547fee6 (diff) | |
download | frameworks_base-4aa3a0d31d8c73e7f377867005ddc512431b9319.zip frameworks_base-4aa3a0d31d8c73e7f377867005ddc512431b9319.tar.gz frameworks_base-4aa3a0d31d8c73e7f377867005ddc512431b9319.tar.bz2 |
Adaptive request reordering
Reorders the network requests so that the resources that will
appear in the visible area of the web page are requested before
the ones that are not. This is to ensure that the visible part of
the web page gets rendered as fast as possible
Change-Id: I5f5ae930d767d3c21f33df1db9c034098f9eb17c
-rw-r--r-- | core/java/android/net/http/Connection.java | 65 | ||||
-rw-r--r-- | core/java/android/net/http/ConnectionThread.java | 36 | ||||
-rw-r--r-- | core/java/android/net/http/Request.java | 7 | ||||
-rw-r--r-- | core/java/android/net/http/RequestFeeder.java | 2 | ||||
-rw-r--r-- | core/java/android/net/http/RequestHandle.java | 2 | ||||
-rw-r--r-- | core/java/android/net/http/RequestQueue.java | 139 | ||||
-rw-r--r-- | core/java/android/webkit/BrowserFrame.java | 6 | ||||
-rw-r--r-- | core/java/android/webkit/HTML5VideoViewProxy.java | 2 | ||||
-rw-r--r-- | core/java/android/webkit/LoadListener.java | 37 | ||||
-rw-r--r-- | core/java/android/webkit/Network.java | 24 |
10 files changed, 293 insertions, 27 deletions
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java index 43fb5f1..5e5affc 100644 --- a/core/java/android/net/http/Connection.java +++ b/core/java/android/net/http/Connection.java @@ -47,11 +47,16 @@ abstract class Connection { */ static final int SOCKET_TIMEOUT = 60000; + private static final int MAX_PRIORITY = 1000; + private static final int SEND = 0; private static final int READ = 1; private static final int DRAIN = 2; private static final int DONE = 3; - private static final String[] states = {"SEND", "READ", "DRAIN", "DONE"}; + private static final int PRIO = 4; + private static final String[] states = {"SEND", "READ", "DRAIN", "DONE", "PRIO"}; + + private ConnectionThread mConnectionThread; Context mContext; @@ -110,6 +115,7 @@ abstract class Connection { mCanPersist = false; mHttpContext = new BasicHttpContext(null); + mConnectionThread = null; } HttpHost getHost() { @@ -140,6 +146,10 @@ abstract class Connection { return mCertificate; } + void setConnectionThread(ConnectionThread thread) { + mConnectionThread = thread; + } + /** * Close current network connection * Note: this runs in non-network thread @@ -157,6 +167,7 @@ abstract class Connection { */ void processRequests(Request firstRequest) { Request req = null; + Request peek = null; boolean empty; int error = EventHandler.OK; Exception exception = null; @@ -197,6 +208,36 @@ abstract class Connection { state = DRAIN; break; } + + // ### synchronize on mRequestFeeder instead of requeing newreq? + + if (req.mPriority == -1 || + req.mPriority > MAX_PRIORITY) { + /* + || pipe.size() + 1 == maxPipe + || req.mPriority == -1) { + */ + peek = mRequestFeeder.peekRequest(); + if (peek != null) { + int ppri = peek.mPriority; + if ((req.mPriority == -1 && ppri >= 0) + || (req.mPriority >= 0 && ppri < req.mPriority)) { + Request newreq = mRequestFeeder.getRequest(); + if (newreq != null) { + if (!newreq.equals(peek) + || peek.mPriority != ppri) { + mRequestFeeder.requeueRequest(newreq, false, true); + } else { + mConnectionThread.setNewRequest(newreq); + state = PRIO; + mRequestFeeder.requeueRequest(req, false, true); + break; + } + } + } + } + } + req.setConnection(this); /* Don't work on cancelled requests. */ @@ -251,27 +292,34 @@ abstract class Connection { pipe.addLast(req); } exception = null; - state = clearPipe(pipe) ? DONE : SEND; + if (clearPipe(pipe)) + state = DONE; + else if (state != PRIO) + state = SEND; minPipe = maxPipe = 1; break; } pipe.addLast(req); - if (!mCanPersist) state = READ; + if (!mCanPersist && state != PRIO) state = READ; break; } + case PRIO: case DRAIN: case READ: { empty = !mRequestFeeder.haveRequest(mHost); int pipeSize = pipe.size(); - if (state != DRAIN && pipeSize < minPipe && + if (state != PRIO && state != DRAIN && pipeSize < minPipe && !empty && mCanPersist) { state = SEND; break; } else if (pipeSize == 0) { /* Done if no other work to do */ - state = empty ? DONE : SEND; + if (state == PRIO) + state = DONE; + else + state = empty ? DONE : SEND; break; } @@ -312,7 +360,8 @@ abstract class Connection { mHttpContext.removeAttribute(HTTP_CONNECTION); clearPipe(pipe); minPipe = maxPipe = 1; - state = SEND; + if (state != PRIO) + state = SEND; } break; } @@ -335,7 +384,7 @@ abstract class Connection { tReq = (Request)pipe.removeLast(); if (HttpLog.LOGV) HttpLog.v( "clearPipe() adding back " + mHost + " " + tReq); - mRequestFeeder.requeueRequest(tReq); + mRequestFeeder.requeueRequest(tReq, true, false); empty = false; } if (empty) empty = !mRequestFeeder.haveRequest(mHost); @@ -406,7 +455,7 @@ abstract class Connection { } else { if (req.mFailCount < RETRY_REQUEST_LIMIT) { // requeue - mRequestFeeder.requeueRequest(req); + mRequestFeeder.requeueRequest(req, true, false); req.mFailCount++; } else { httpFailure(req, error, exception); diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java index 32191d2..9b0363e 100644 --- a/core/java/android/net/http/ConnectionThread.java +++ b/core/java/android/net/http/ConnectionThread.java @@ -40,6 +40,8 @@ class ConnectionThread extends Thread { private Context mContext; private RequestQueue.ConnectionManager mConnectionManager; private RequestFeeder mRequestFeeder; + private volatile HttpHost mCurrentHost; + private volatile Request mNewRequest; private int mId; Connection mConnection; @@ -54,15 +56,25 @@ class ConnectionThread extends Thread { mId = id; mConnectionManager = connectionManager; mRequestFeeder = requestFeeder; + mCurrentHost = null; + mNewRequest = null; } - void requestStop() { + public void requestStop() { synchronized (mRequestFeeder) { mRunning = false; mRequestFeeder.notify(); } } + public HttpHost getCurrentHost() { + return mCurrentHost; + } + + public void setNewRequest(Request req) { + mNewRequest = req; + } + /** * Loop until app shutdown. Runs connections in priority * order. @@ -87,7 +99,11 @@ class ConnectionThread extends Thread { Request request; /* Get a request to process */ - request = mRequestFeeder.getRequest(); + if (mNewRequest != null) { + request = mNewRequest; + mNewRequest = null; + } else + request = mRequestFeeder.getRequest(); /* wait for work */ if (request == null) { @@ -103,13 +119,18 @@ class ConnectionThread extends Thread { mCurrentThreadTime = SystemClock .currentThreadTimeMillis(); } + // Make sure the connection does not start to drain before the first request has been processed } } else { if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " + request.mHost + " " + request ); - mConnection = mConnectionManager.getConnection(mContext, - request.mHost); + // ### this should possibly have some kind of lock to prevent the requestqueue from seeing the host as busy when it's not + mCurrentHost = request.mHost; + synchronized (this) { + mConnection = mConnectionManager.getConnection(mContext, request.mHost); + mConnection.setConnectionThread(this); + } mConnection.processRequests(request); if (mConnection.getCanPersist()) { if (!mConnectionManager.recycleConnection(mConnection)) { @@ -118,7 +139,12 @@ class ConnectionThread extends Thread { } else { mConnection.closeConnection(); } - mConnection = null; + synchronized (this) { + mConnection.setConnectionThread(null); + mConnection = null; + } + + mCurrentHost = null; if (mCurrentThreadTime > 0) { long start = mCurrentThreadTime; diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java index 8c0d503..0cca90b 100644 --- a/core/java/android/net/http/Request.java +++ b/core/java/android/net/http/Request.java @@ -71,6 +71,9 @@ class Request { /** True if request has been cancelled */ volatile boolean mCancelled = false; + /** Priority */ + volatile int mPriority = -1; + int mFailCount = 0; // This will be used to set the Range field if we retry a connection. This @@ -111,13 +114,15 @@ class Request { Request(String method, HttpHost host, HttpHost proxyHost, String path, InputStream bodyProvider, int bodyLength, EventHandler eventHandler, - Map<String, String> headers) { + Map<String, String> headers, + int pri) { mEventHandler = eventHandler; mHost = host; mProxyHost = proxyHost; mPath = path; mBodyProvider = bodyProvider; mBodyLength = bodyLength; + mPriority = pri; if (bodyProvider == null && !"POST".equalsIgnoreCase(method)) { mHttpRequest = new BasicHttpRequest(method, getUri()); diff --git a/core/java/android/net/http/RequestFeeder.java b/core/java/android/net/http/RequestFeeder.java index 34ca267..739db27 100644 --- a/core/java/android/net/http/RequestFeeder.java +++ b/core/java/android/net/http/RequestFeeder.java @@ -29,6 +29,7 @@ interface RequestFeeder { Request getRequest(); Request getRequest(HttpHost host); + Request peekRequest(); /** * @return true if a request for this host is available @@ -39,4 +40,5 @@ interface RequestFeeder { * Put request back on head of queue */ void requeueRequest(Request request); + void requeueRequest(Request request, boolean commit, boolean notif); } diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java index 103fd94..07533ca 100644 --- a/core/java/android/net/http/RequestHandle.java +++ b/core/java/android/net/http/RequestHandle.java @@ -456,6 +456,6 @@ public class RequestHandle { mRequest = mRequestQueue.queueRequest( mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler, mBodyProvider, - mBodyLength).mRequest; + mBodyLength, -1, false).mRequest; } } diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java index a31639f..50ea7f5 100644 --- a/core/java/android/net/http/RequestQueue.java +++ b/core/java/android/net/http/RequestQueue.java @@ -36,6 +36,9 @@ import android.text.TextUtils; import android.util.Log; import java.io.InputStream; +import java.util.Comparator; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -57,6 +60,7 @@ public class RequestQueue implements RequestFeeder { private final Context mContext; private final ActivePool mActivePool; private final ConnectivityManager mConnectivityManager; + private final HashSet<HttpHost> mPriorities; private HttpHost mProxyHost = null; private BroadcastReceiver mProxyChangeReceiver; @@ -213,6 +217,7 @@ public class RequestQueue implements RequestFeeder { mContext = context; mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32); + mPriorities = new HashSet<HttpHost>(); mActivePool = new ActivePool(connectionCount); mActivePool.startup(); @@ -221,6 +226,58 @@ public class RequestQueue implements RequestFeeder { context.getSystemService(Context.CONNECTIVITY_SERVICE); } + public synchronized boolean setRequestPriority(WebAddress uri, int priority) { + // ### this lookup won't work if a proxy is being used + HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme); + if (mPending.containsKey(host)) { + LinkedList<Request> reqList = mPending.get(host); + // ### O(n) lookup, certainly not ideal + ListIterator iter = reqList.listIterator(0); + while (iter.hasNext()) { + Request request = (Request)iter.next(); + if (request.mPath.equals(uri.mPath)) { + request.mPriority = priority; + mPriorities.add(host); + return true; + } + } + } + return false; + } + + private void commitPrioritiesForList(LinkedList<Request> reqList) { + Collections.sort(reqList, new Comparator<Object>() { + public int compare(Object o1, Object o2) { + int r1 = ((Request)o1).mPriority; + int r2 = ((Request)o2).mPriority; + + if (r1 == r2) + return 0; + else if (r1 == -1) + return 1; + else if (r2 == -1) + return -1; + else if (r1 < r2) + return -1; + return 1; + } + }); + } + + public synchronized void commitRequestPriorities() { + if (mPriorities.isEmpty()) + return; + Iterator iter = mPriorities.iterator(); + while (iter.hasNext()) { + HttpHost host = (HttpHost)iter.next(); + if (mPending.containsKey(host)) { + LinkedList<Request> reqList = mPending.get(host); + commitPrioritiesForList(reqList); + } + } + mPriorities.clear(); + } + /** * Enables data state and proxy tracking */ @@ -297,9 +354,17 @@ public class RequestQueue implements RequestFeeder { String url, String method, Map<String, String> headers, EventHandler eventHandler, InputStream bodyProvider, int bodyLength) { + return queueRequest(url, method, headers, eventHandler, + bodyProvider, bodyLength, -1, false); + } + + public RequestHandle queueRequest( + String url, String method, + Map<String, String> headers, EventHandler eventHandler, + InputStream bodyProvider, int bodyLength, int pri, boolean commit) { WebAddress uri = new WebAddress(url); return queueRequest(url, uri, method, headers, eventHandler, - bodyProvider, bodyLength); + bodyProvider, bodyLength, pri, commit); } /** @@ -316,7 +381,7 @@ public class RequestQueue implements RequestFeeder { public RequestHandle queueRequest( String url, WebAddress uri, String method, Map<String, String> headers, EventHandler eventHandler, - InputStream bodyProvider, int bodyLength) { + InputStream bodyProvider, int bodyLength, int pri, boolean commit) { if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri); @@ -331,9 +396,9 @@ public class RequestQueue implements RequestFeeder { // set up request req = new Request(method, httpHost, mProxyHost, uri.mPath, bodyProvider, - bodyLength, eventHandler, headers); + bodyLength, eventHandler, headers, pri); - queueRequest(req, false); + queueRequest(req, false, commit); mActivePool.mTotalRequest++; @@ -359,12 +424,18 @@ public class RequestQueue implements RequestFeeder { public Request getRequest(HttpHost host) { return getRequest(); } + public Request peekRequest() { + return mRequest; + } public boolean haveRequest(HttpHost host) { return mRequest != null; } public void requeueRequest(Request r) { mRequest = r; } + public void requeueRequest(Request r, boolean commit, boolean notif) { + requeueRequest(r); + } } public RequestHandle queueSynchronousRequest(String url, WebAddress uri, @@ -378,7 +449,7 @@ public class RequestQueue implements RequestFeeder { HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme); Request req = new Request(method, host, mProxyHost, uri.mPath, - bodyProvider, bodyLength, eventHandler, headers); + bodyProvider, bodyLength, eventHandler, headers, 0); // Open a new connection that uses our special RequestFeeder // implementation. @@ -444,16 +515,55 @@ public class RequestQueue implements RequestFeeder { HttpLog.v(dump.toString()); } + private Map.Entry<HttpHost, LinkedList<Request>> priorityList() { + int curPri = -1; + int entryPri; + Map.Entry<HttpHost, LinkedList<Request>> ret = null; + if (!mPending.isEmpty()) { + Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = mPending.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next(); + if (ret == null) { + ret = entry; + } + entryPri = entry.getValue().getFirst().mPriority; + if (entryPri != -1 && (curPri == -1 || curPri > entryPri)) { + ret = entry; + curPri = entryPri; + } + } + } + return ret; + } + + public synchronized Request peekRequest() { + Request ret = null; + + Map.Entry<HttpHost, LinkedList<Request>> entry = priorityList(); + if (entry != null && !entry.getValue().isEmpty()) { + if (entry.getValue().getFirst().mPriority != -1) + ret = entry.getValue().getFirst(); + } + + return ret; + } + /* * RequestFeeder implementation */ public synchronized Request getRequest() { Request ret = null; - if (!mPending.isEmpty()) { - ret = removeFirst(mPending); + Map.Entry<HttpHost, LinkedList<Request>> entry = priorityList(); + if (entry != null) { + LinkedList<Request> reqList = entry.getValue(); + ret = reqList.removeFirst(); + if (reqList.isEmpty()) { + mPending.remove(entry.getKey()); + } } if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret); + return ret; } @@ -485,7 +595,13 @@ public class RequestQueue implements RequestFeeder { * Put request back on head of queue */ public void requeueRequest(Request request) { - queueRequest(request, true); + requeueRequest(request, true, true); + } + + public void requeueRequest(Request request, boolean commit, boolean notif) { + queueRequest(request, true, commit); + if (notif) + mActivePool.startConnectionThread(); } /** @@ -495,7 +611,7 @@ public class RequestQueue implements RequestFeeder { mActivePool.shutdown(); } - protected synchronized void queueRequest(Request request, boolean head) { + protected synchronized void queueRequest(Request request, boolean head, boolean commit) { HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost; LinkedList<Request> reqList; if (mPending.containsKey(host)) { @@ -509,6 +625,11 @@ public class RequestQueue implements RequestFeeder { } else { reqList.add(request); } + if (commit && request.mPriority != -1) { + commitPrioritiesForList(reqList); + } else if (!commit && request.mPriority != -1) { + mPriorities.add(host); + } } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 071f87c..ec0858c 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -660,7 +660,9 @@ class BrowserFrame extends Handler { boolean userGesture, boolean synchronous, String username, - String password) { + String password, + int priority, + boolean commit) { PerfChecker checker = new PerfChecker(); if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { @@ -734,6 +736,8 @@ class BrowserFrame extends Handler { LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url, loaderHandle, synchronous, isMainFramePage, mainResource, userGesture, postDataIdentifier, username, password); + loadListener.setPriority(priority); + loadListener.setShouldCommit(commit); mCallbackProxy.onLoadResource(url); diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index eda2e72..20b987c 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -345,7 +345,7 @@ class HTML5VideoViewProxy extends Handler // Start the download. Called on WebCore thread. public void start() { retainQueue(); - mRequestHandle = mRequestQueue.queueRequest(mUrl, "GET", null, this, null, 0); + mRequestHandle = mRequestQueue.queueRequest(mUrl, "GET", null, this, null, 0, -1, false); } // Cancel the download if active and release the queue. Called on WebCore thread. public void cancelAndReleaseQueue() { diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index e6fa405..3cdd86b 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -131,6 +131,9 @@ class LoadListener extends Handler implements EventHandler { private final String mUsername; private final String mPassword; + private int mPriority = -1; + private boolean mCommit = true; + // ========================================================================= // Public functions // ========================================================================= @@ -183,6 +186,22 @@ class LoadListener extends Handler implements EventHandler { mNativeLoader = 0; } + public void setPriority(int pri) { + mPriority = pri; + } + + public int priority() { + return mPriority; + } + + public void setShouldCommit(boolean commit) { + mCommit = commit; + } + + public boolean shouldCommit() { + return mCommit; + } + /* * This message handler is to facilitate communication between the network * thread and the browser thread. @@ -1242,6 +1261,24 @@ class LoadListener extends Handler implements EventHandler { } /** + * native callback + * Propagates the priority to the network stack + */ + void setPriority(String url, int pri) { + Network net = Network.getInstance(mContext); + net.setPriority(url, pri); + } + + /** + * native callback + * Commits the priorities set on the network stack + */ + void commitPriorities() { + Network net = Network.getInstance(mContext); + net.commitPriorities(); + } + + /** * Cancel a request. * FIXME: This will only work if the request has yet to be handled. This * is in no way guarenteed if requests are served in a separate thread. diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java index 598f20d..accbebb 100644 --- a/core/java/android/webkit/Network.java +++ b/core/java/android/webkit/Network.java @@ -17,6 +17,8 @@ package android.webkit; import android.content.Context; +import android.net.WebAddress; +import android.net.ParseException; import android.net.http.*; import android.os.*; import android.util.Log; @@ -142,6 +144,25 @@ class Network { mRequestQueue = new RequestQueue(context); } + public boolean setPriority(String url, int priority) { + WebAddress uri = null; + if (URLUtil.isNetworkUrl(url)) { + String nurl = URLUtil.stripAnchor(url); + try { + uri = new WebAddress(nurl); + } catch (ParseException e) { + e.printStackTrace(); + } + } + if (uri != null) + return mRequestQueue.setRequestPriority(uri, priority); + return false; + } + + public void commitPriorities() { + mRequestQueue.commitRequestPriorities(); + } + /** * Request a url from either the network or the file system. * @param url The url to load. @@ -189,7 +210,8 @@ class Network { loader.loadSynchronousMessages(); } else { handle = q.queueRequest(url, loader.getWebAddress(), method, - headers, loader, bodyProvider, bodyLength); + headers, loader, bodyProvider, bodyLength, + loader.priority(), loader.shouldCommit()); // FIXME: Although this is probably a rare condition, normal network // requests are processed in a separate thread. This means that it // is possible to process part of the request before setting the |