Как кешировать в Blackberry BrowserField - PullRequest
47 голосов
/ 07 октября 2011

Я создаю приложение Blackberry для отображения полноэкранного веб-просмотра определенного сайта.У меня есть работающее поле браузера, которое отображается правильно, но навигация от страницы к странице медленнее, чем у родного браузера.Похоже, что поле браузера не имеет встроенного кэша, что приводит к замедлению загрузки.Когда я добавляю следующий код для управления кэшем, сайт больше не отображается должным образом.

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 Ответ

3 голосов
/ 20 января 2012

Я смотрел на ваш код, и единственное, что я обнаружил, что с ним что-то не так, вы полностью игнорируете возможность возврата response.getLength(); меньше нуля (в CacheManagerImpl.createCache()).Хотя этого не произошло со мной на странице stackoverflow.com, некоторые страницы используют Transfer-Encoding: chunked, что означает, что Content-Length отсутствует.Это, однако, хорошо обрабатывается и не должно приводить к сбою кэша (это будет только менее эффективно).

Я предлагаю тестировать ваш код на небольших проблемах, по одному шагу за раз.Сначала создайте кэшируемую страницу, которая содержит только некоторый текст (например, «привет») без каких-либо тегов HTML.Это должно работать очень хорошо, и в случае, если это не так, не должно быть трудно определить, где данные теряются.Или попробуйте вручную создать элемент кэша, который не имеет срока действия и содержит веб-страницу без (внешней) таблицы стилей или изображений, и посмотрите, возможно ли даже передать его в BrowserField так, как вы это делаете.Затем постройте, добавьте изображение, добавьте таблицу стилей, чтобы вы могли исправить проблему.

Код написан очень хорошо, но на данный момент вам не возможно помочь, потому что нет очевидных недостатков.в коде, и вы не очень хорошо объясняете себя, неясно, как проявляется ошибка, если это происходит каждый раз или случайно ... Если бы у меня было устройство Blackberry, я мог бы, вероятно, попробовать запустить код для себя,но я не.

...