如何caching在黑莓BrowserField
我正在创build一个黑莓应用程序来显示某个网站的全屏Web视图。 我有一个可正常显示的工作浏览器字段,但是页面之间的导航比本地浏览器的导航慢。 browserfield似乎没有内置的caching,导致加载时间变慢。 当我添加以下代码来pipe理caching时,网站不再正常显示。
BrowserFieldScreen.java:
import net.rim.device.api.browser.field2.*; import net.rim.device.api.script.ScriptEngine; import net.rim.device.api.system.*; import net.rim.device.api.ui.*; import net.rim.device.api.ui.component.*; import net.rim.device.api.ui.container.*; import org.w3c.dom.Document; class BrowserFieldScreen extends MainScreen { BrowserField browserField; LoadingScreen load = new LoadingScreen();; public BrowserFieldScreen() { browserField = new BrowserField(); browserField.getConfig().setProperty( BrowserFieldConfig.JAVASCRIPT_ENABLED, Boolean.TRUE); browserField.getConfig().setProperty( BrowserFieldConfig.NAVIGATION_MODE, BrowserFieldConfig.NAVIGATION_MODE_POINTER); browserField.getConfig().setProperty( BrowserFieldConfig.CONTROLLER, new CacheProtocolController(browserField)); browserField.requestContent("http://www.stackoverflow.com"); add(browserField); } }
CacheProtocolController.java:
import javax.microedition.io.HttpConnection; import javax.microedition.io.InputConnection; import net.rim.device.api.browser.field2.BrowserField; import net.rim.device.api.browser.field2.BrowserFieldRequest; import net.rim.device.api.browser.field2.ProtocolController; public class CacheProtocolController extends ProtocolController{ // The BrowserField instance private BrowserField browserField; // CacheManager will take care of cached resources private CacheManager cacheManager; public CacheProtocolController(BrowserField browserField) { super(browserField); this.browserField = browserField; } private CacheManager getCacheManager() { if ( cacheManager == null ) { cacheManager = new CacheManagerImpl(); } return cacheManager; } /** * Handle navigation requests (eg, link clicks) */ public void handleNavigationRequest(BrowserFieldRequest request) throws Exception { InputConnection ic = handleResourceRequest(request); browserField.displayContent(ic, request.getURL()); } /** * Handle resource request * (eg, images, external css/javascript resources) */ public InputConnection handleResourceRequest(BrowserFieldRequest request) throws Exception { // if requested resource is cacheable (eg, an "http" resource), // use the cache if (getCacheManager() != null && getCacheManager().isRequestCacheable(request)) { InputConnection ic = null; // if requested resource is cached, retrieve it from cache if (getCacheManager().hasCache(request.getURL()) && !getCacheManager().hasCacheExpired(request.getURL())) { ic = getCacheManager().getCache(request.getURL()); } // if requested resource is not cached yet, cache it else { ic = super.handleResourceRequest(request); if (ic instanceof HttpConnection) { HttpConnection response = (HttpConnection) ic; if (getCacheManager().isResponseCacheable(response)) { ic = getCacheManager().createCache(request.getURL(), response); } } } return ic; } // if requested resource is not cacheable, load it as usual return super.handleResourceRequest(request); } }
CacheManager.java:
import javax.microedition.io.HttpConnection; import javax.microedition.io.InputConnection; import net.rim.device.api.browser.field2.BrowserFieldRequest; public interface CacheManager { public boolean isRequestCacheable(BrowserFieldRequest request); public boolean isResponseCacheable(HttpConnection response); public boolean hasCache(String url); public boolean hasCacheExpired(String url); public InputConnection getCache(String url); public InputConnection createCache(String url, HttpConnection response); public void clearCache(String url); }
CacheManagerImpl.java:
import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.Hashtable; import javax.microedition.io.HttpConnection; import javax.microedition.io.InputConnection; import net.rim.device.api.browser.field2.BrowserFieldRequest; import net.rim.device.api.browser.field2.BrowserFieldResponse; import net.rim.device.api.io.http.HttpHeaders; public class CacheManagerImpl implements CacheManager { private static final int MAX_STANDARD_CACHE_AGE = 2592000; private Hashtable cacheTable; public CacheManagerImpl() { cacheTable = new Hashtable(); } public boolean isRequestCacheable(BrowserFieldRequest request) { // Only HTTP requests are cacheable if (!request.getProtocol().equals("http")) { return false; } // Don't cache the request whose method is not "GET". if (request instanceof HttpConnection) { if (!((HttpConnection) request).getRequestMethod().equals("GET")) { return false; } } // Don't cache the request with post data. if (request.getPostData() != null) { return false; } // Don't cache authentication request. if (request.getHeaders().getPropertyValue("Authorization") != null) { return false; } return true; } public boolean isResponseCacheable(HttpConnection response) { try { if (response.getResponseCode() != 200) { return false; } } catch (IOException ioe) { return false; } if (!response.getRequestMethod().equals("GET")) { return false; } if (containsPragmaNoCache(response)) { return false; } if (isExpired(response)) { return false; } if (containsCacheControlNoCache(response)) { return false; } if ( response.getLength() <= 0 ) { return false; } // additional checks can be implemented here to inspect // the HTTP cache-related headers of the response object return true; } private boolean isExpired(HttpConnection response) { try { // getExpiration() returns 0 if not known long expires = response.getExpiration(); if (expires > 0 && expires <= (new Date()).getTime()) { return true; } return false; } catch (IOException ioe) { return true; } } private boolean containsPragmaNoCache(HttpConnection response) { try { if (response.getHeaderField("pragma") != null && response.getHeaderField("pragma") .toLowerCase() .indexOf("no-cache") >= 0) { return true; } return false; } catch (IOException ioe) { return true; } } private boolean containsCacheControlNoCache(HttpConnection response) { try { String cacheControl = response.getHeaderField("cache-control"); if (cacheControl != null) { cacheControl = removeSpace(cacheControl.toLowerCase()); if (cacheControl.indexOf("no-cache") >= 0 || cacheControl.indexOf("no-store") >= 0 || cacheControl.indexOf("private") >= 0 || cacheControl.indexOf("max-age=0") >= 0) { return true; } long maxAge = parseMaxAge(cacheControl); if (maxAge > 0 && response.getDate() > 0) { long date = response.getDate(); long now = (new Date()).getTime(); if (now > date + maxAge) { // Already expired return true; } } } return false; } catch (IOException ioe) { return true; } } public InputConnection createCache(String url, HttpConnection response) { byte[] data = null; InputStream is = null; try { // Read data int len = (int) response.getLength(); if (len > 0) { is = response.openInputStream(); int actual = 0; int bytesread = 0 ; data = new byte[len]; while ((bytesread != len) && (actual != -1)) { actual = is.read(data, bytesread, len - bytesread); bytesread += actual; } } } catch (IOException ioe) { data = null; } finally { if (is != null) { try { is.close(); } catch (IOException ioe) { } } if (response != null) { try { response.close(); } catch (IOException ioe) { } } } if (data == null) { return null; } // Calculate expires long expires = calculateCacheExpires(response); // Copy headers HttpHeaders headers = copyResponseHeaders(response); // add item to cache cacheTable.put(url, new CacheItem(url, expires, data, headers)); return new BrowserFieldResponse(url, data, headers); } private long calculateCacheExpires(HttpConnection response) { long date = 0; try { date = response.getDate(); } catch (IOException ioe) { } if (date == 0) { date = (new Date()).getTime(); } long expires = getResponseExpires(response); // If an expire date has not been specified assumes the maximum time if ( expires == 0 ) { return date + (MAX_STANDARD_CACHE_AGE * 1000L); } return expires; } private long getResponseExpires(HttpConnection response) { try { // Calculate expires from "expires" long expires = response.getExpiration(); if (expires > 0) { return expires; } // Calculate expires from "max-age" and "date" if (response.getHeaderField("cache-control") != null) { String cacheControl = removeSpace(response .getHeaderField("cache-control") .toLowerCase()); long maxAge = parseMaxAge(cacheControl); long date = response.getDate(); if (maxAge > 0 && date > 0) { return (date + maxAge); } } } catch (IOException ioe) { } return 0; } private long parseMaxAge(String cacheControl) { if (cacheControl == null) { return 0; } long maxAge = 0; if (cacheControl.indexOf("max-age=") >= 0) { int maxAgeStart = cacheControl.indexOf("max-age=") + 8; int maxAgeEnd = cacheControl.indexOf(',', maxAgeStart); if (maxAgeEnd < 0) { maxAgeEnd = cacheControl.length(); } try { maxAge = Long.parseLong(cacheControl.substring(maxAgeStart, maxAgeEnd)); } catch (NumberFormatException nfe) { } } // Multiply maxAge by 1000 to convert seconds to milliseconds maxAge *= 1000L; return maxAge; } private static String removeSpace(String s) { StringBuffer result= new StringBuffer(); int count = s.length(); for (int i = 0; i < count; i++) { char c = s.charAt(i); if (c != ' ') { result.append(c); } } return result.toString(); } private HttpHeaders copyResponseHeaders(HttpConnection response) { HttpHeaders headers = new HttpHeaders(); try { int index = 0; while (response.getHeaderFieldKey(index) != null) { headers.addProperty(response.getHeaderFieldKey(index), response.getHeaderField(index)); index++; } } catch (IOException ioe) { } return headers; } public boolean hasCache(String url) { return cacheTable.containsKey(url); } public boolean hasCacheExpired(String url) { Object o = cacheTable.get(url); if (o instanceof CacheItem) { CacheItem ci = (CacheItem) o; long date = (new Date()).getTime(); if (ci.getExpires() > date) { return false; } else { // Remove the expired cache item clearCache(url); } } return true; } public void clearCache(String url) { cacheTable.remove(url); } public InputConnection getCache(String url) { Object o = cacheTable.get(url); if (o instanceof CacheItem) { CacheItem ci = (CacheItem) o; return new BrowserFieldResponse(url, ci.getData(), ci.getHttpHeaders()); } return null; } }
CacheItem.java:
import net.rim.device.api.io.http.HttpHeaders; public class CacheItem { private String url; private long expires; private byte[] data; private HttpHeaders httpHeaders; public CacheItem(String url, long expires, byte[] data, HttpHeaders httpHeaders) { this.url = url; this.expires = expires; this.data = data; this.httpHeaders = httpHeaders; } public String getUrl() { return url; } public long getExpires() { return expires; } public byte[] getData() { return data; } public HttpHeaders getHttpHeaders() { return httpHeaders; } }
任何帮助,可以给这个将不胜感激。 这真的让我难住。 谢谢。
更新:它看起来像caching只适用于黑莓图书馆的一定水平。 我添加了逻辑来检查当前的软件级别,并打开caching(如果它受到设备当前软件级别的支持)。 这为我提供了一个很好的解决方法,但是我仍然想知道caching是否有更好的方法来处理所有设备。
更新2基于评论:该网站不再正确显示适合网站不显示正确的布局,图像和文字。 它基本上给一个白色的背景,链接和文本显示为项目符号列表,所有的格式都被删除。
我一直在看你的代码,唯一我发现有错误的是,你完全忽略了response.getLength();
的可能性response.getLength();
返回小于零(在CacheManagerImpl.createCache()
)。 虽然这并没有发生在我stackoverflow.com页面上,一些页面使用Transfer-Encoding: chunked
,这意味着Content-Length
不存在。 然而,这是处理好,不应该导致caching失败(它只会是不太有效)。
我build议在一个小问题上testing你的代码。 首先,创build只包含一些文本的caching页面(如“hello”),不包含任何HTML标签。 这应该工作得很好,如果没有,就不难确定数据丢失的位置。 或者尝试手动创build不会过期的caching项目,并且包含没有(外部)样式表和图像的网页,并查看是否可以按照您的方式将其传递给BrowserField
。 然后build立,添加一个图像,添加一个样式表,所以你可以将问题转移。
代码写得非常好,但在这一点上,不可能帮助你,因为代码中没有明显的缺陷,而且你没有很好地解释自己,不清楚错误是如何performance出来的,每次或随机,…如果我有一个黑莓手机,我可以尝试运行自己的代码,但我不知道。