How to cache BlackBerry BrowserField

I am creating a Blackberry application to display a full-screen web view of a specific site. I have a working browser window that displays correctly, but moving from page to page is slower than with my own browser. It looks like there is no built-in cache in the browser field, which slows down loading time. When I add the following code to manage the cache, the site is no longer properly displayed.

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; } } 

Any help that could be directed at this would be greatly appreciated. It really puzzled me. Thank.

UPDATE: Caching seems to work only at a certain level of Blackberry libraries. I added logic to check the current Software level and enable caching if it is supported by the current device software level. This gives me a good job, but I would still like to know if there is a better way for caching to work with all devices.

UPDATE 2 Based on the comments: the site is no longer properly displayed for the site, without displaying the correct layouts, images and text. This basically gives a white background with links and displaying text as a bulleted list, all formatting is removed.

+47
java caching blackberry browserfield
Oct 07 '11 at 12:24
source share
1 answer

I am looking at your code, and the only thing I found there with this wrong is that you completely ignore the possibility of returning response.getLength(); less than zero (in CacheManagerImpl.createCache() ). Although this did not happen to me on stackoverflow.com, some pages use Transfer-Encoding: chunked , which means there is no Content-Length . This, however, handles well and should not lead to a cache failure (this will be less efficient).

I suggest testing your code for smaller problems, step by step. First, create a cached page that only contains text (such as "hello") without any HTML tags. This should work very well, and if it is not, it is easy to determine where the data is lost. Or try manually creating a cache element that does not expire and contains a web page without a (external) style sheet or image, and see if it is possible to transfer it to the BrowserField way you do it. Then draw, add an image, add a stylesheet to fix the problem.

The code is written very beautifully, but at the moment you are unable to help, because there are no obvious flaws in the code, and you are not explaining yourself very well, it is unclear how the error appears, if it is every time or random ... If I was a Blackberry device, I could try to run the code for myself, but I do not.

+3
Jan 20 '12 at 15:55
source share



All Articles