如何在Blackberry BrowserField中缓存

时间:2011-10-07 12:24:31

标签: java caching blackberry browserfield

我正在创建一个Blackberry应用程序来显示某个站点的全屏Web视图。我有一个正常显示的工作浏览器字段,但页面之间的导航速度比本机浏览器慢。 browserfield似乎没有内置缓存,导致加载时间变慢。当我添加以下代码来管理缓存时,网站不再正常显示。

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 (e.g., link clicks)
     */
    public void handleNavigationRequest(BrowserFieldRequest request) 
        throws Exception 
    {
        InputConnection ic = handleResourceRequest(request);
        browserField.displayContent(ic, request.getURL());
    }

    /**
     * Handle resource request 
     * (e.g., images, external css/javascript resources)
     */
    public InputConnection handleResourceRequest(BrowserFieldRequest request) 
        throws Exception 
    {
        // if requested resource is cacheable (e.g., 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;
    }
}

我们将非常感谢能为此提供的任何帮助。这真让我难过。感谢。

更新:看起来缓存只适用于Blackberry库的某个级别。我添加了逻辑来检查当前的软件级别,如果设备当前的软件级别支持,则打开缓存。这为我提供了一个很好的解决方案,但我仍然想知道是否有更好的方法使缓存能够与所有设备一起使用。

更新2 根据评论:网站不再正确显示与网站无关,无法显示正确的布局,图片和文字。它基本上给出了一个白色背景,链接和文本显示为项目符号列表,所有格式都被删除。

1 个答案:

答案 0 :(得分:3)

我一直在查看你的代码,而且我发现它唯一的错误就是你完全无视response.getLength();返回小于零的可能性(在CacheManagerImpl.createCache()中) 。虽然在stackoverflow.com页面上没有发生这种情况,但有些页面使用Transfer-Encoding: chunked,这意味着Content-Length不存在。但是,这样做得很好,并且不应该导致缓存失败(它只会降低效率)。

我建议您在较小的问题上测试代码,一次一步。首先,创建只包含一些文本(如“hello”)的可缓存页面,而不包含任何HTML标记。这应该可以很好地工作,如果没有,那么确定数据丢失的位置应该不难。或者尝试手动创建不会过期的缓存项,并且包含没有(外部)样式表或图像的网页,并查看是否可以按照您的方式将其传递给BrowserField。然后构建,添加图像,添加样式表,以便解决问题。

代码编写得非常好,但是在这一点上,它无法帮助你,因为代码中没有明显的缺陷而且你没有很好地解释自己,不清楚错误是如何表现出来的,如果它是每次或随机,...如果我有一个黑莓设备,我可能会尝试为自己运行代码,但我没有。