JavaFx 8 WebEngine - Кэширование изображений через каталог пользовательских данных?

Я новичок в JavaFx и пытался внедрить его браузер в свое приложение. Поскольку перезагрузка всех изображений при каждом новом запуске занимает довольно много времени, я хотел бы сохранить их в каком-нибудь каталоге кеша, но не смог этого сделать. Я пытался использовать setUserDataDirectory(...), но это приводит только к пустой папке с именем localstorage и созданию пустого файла .lock.

Затем я нашел этот трек SO, но, во-первых, мне еще не разрешено комментировать там, а во-вторых , похоже, это касается только JavaFx 2.2. Верно ли то, что было опубликовано там, для JavaFx 8? Если да: есть ли простой способ реализовать такой URLConnection кеш?

Большое спасибо за любую помощь :)


person MightyMalcolm    schedule 21.09.2015    source источник


Ответы (2)


Итак, после 2 дней изучения проблемы и изучения класса URLConnection я, наконец, смог придумать свою собственную (предположительно грубую) реализацию, которой я хотел бы поделиться здесь на случай, если кто-то знает так же мало, как я. натыкаться на эту ветку. Опять же, основная идея заключалась в том, чтобы помещать в кеш только определенные типы файлов, а не все. Я решил хранить файлы jpg, png, gif и js, так как думал, что они будут наиболее загруженными, хотя любой другой формат файла также должен быть возможен. Перво-наперво: browser.getEngine().setUserDataDirectory(...) определенно НЕ выполняет эту работу. Я до сих пор понятия не имею, для чего он нужен, но точно не для хранения файлов изображений %)

Вместо этого я в основном создал 5 классов:

  • CachedResource: состоит из массива byte[], содержащего необработанные данные ресурса и некоторую метаинформацию (поля заголовка, lastModified).
  • ResourceCache: Содержит все кэшированные объекты ресурсов.
  • MyHttpUrlConnection (расширяет sun.net.www.protocol.http.HttpURLConnection): класс-оболочка, который отвечает за извлечение файла, на который указывает его заданный URL-адрес. Он делает всю сетевую магию.
  • CachedUrlConnection (расширяет java.net.URLConnection): (почти) пустая реализация, которая уже имеет все необходимые нам данные и только ждет, пока система ее вызовет.
  • MyUrlConnectionHandler (расширяет sun.net.www.protocol.http.Handler): этот класс регистрируется при запуске приложения и решает, когда использовать какой URLConnection (см. ниже).

Классы ResourceCache, CachedResource и CachedUrlConnection довольно малы и просты в написании. Я разработал кеш ресурсов для сопоставления ресурса URL-адреса с соответствующим объектом CachedResource, таким образом: ConcurrentHashMap<URL, CachedResource> плюс геттер и функция addResource(...). Я добавил к нему некоторые другие вещи, такие как локальное хранение файлов, но это не по теме.

Затем я реализовал класс CachedUrlConnection следующим образом:

public class CachedUrlConnection extends URLConnection {

    private CachedResource resource;
    private ByteArrayInputStream inputStream;

    /* Constructors */
    public CachedUrlConnection(URL url, CachedResource resource) throws IOException {
        super(url);
        this.resource = resource;
        this.inputStream = new ByteArrayInputStream(resource.getByteData());
    }

    @Override
    public void connect() throws IOException {
        // No need to do anything.
    }

    /* Object Methods */

    /* Getters and Setters */
    @Override
    public String getHeaderField(int index) { ... }

    @Override
    public String getHeaderField(String key) { ... }

    @Override
    public Map<String, List<String>> getHeaderFields() { ... }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream; // <---- Here, the system can grab the data.
    }
}

При просмотре исходного кода URLConnection (например, здесь), вы быстро заметите, что большинство реализаций его методов являются фиктивными, которые либо возвращают null, либо выдают UnknownServiceException.

Это важно: я точно не знаю, что из этого вам нужно реализовать!

Чтобы это выяснить, я использовал класс MyHttpUrlConnection и добавил почти к каждой функции

System.out.println("function xyz called!");
super.xyz();

но я был ленив и не проверил их все. Пока вроде все работает нормально %)

Следующий класс был MyHttpUrlConnection. Я не уверен на 100%, действительно ли мне нужно было перезаписать класс HttpURLConnection, но я все равно это сделал, потому что у него есть конструктор protected, который будет неявно вызываться с новым sun.net.www.protocol.http.Handler. Этот обработчик, очевидно, не будет следовать нашей политике http, поэтому я просто хотел быть уверенным (см. исходный код строка 801). Таким образом, класс выглядит довольно пустым:

public class MyHttpUrlConnection extends HttpURLConnection {

    protected MyHttpUrlConnection(URL url, Handler handler) {
        this(url, null, handler);
    }

    public MyHttpUrlConnection(URL url, Proxy proxy) {
        this(url, proxy, new MyUrlConnectionHandler()); // <--- No way sneaking around^^
    }

    protected MyHttpUrlConnection(URL url, Proxy proxy, Handler handler) {
        super(url, proxy, handler);
    }

    public MyHttpUrlConnection(URL url, String host, int port) {
        this(url, new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port))); // Taken over from the HttpURLConnection sourcecode.
    }
}

Теперь самая важная часть: MyUrlConnectionHandler. Опять же, проверьте эту тему, куда ее поместить. Все, что нужно самому классу, это перезаписать функцию openConnection(URL, Proxy). Прежде чем опубликовать свой код, я расскажу вам, что он делает.

  1. Если данный URL-адрес является файлом jpg, png, ...:
  2. Используйте объект MyHttpUrlConnection, чтобы получить дату последней модификации ресурса на сервере. это должно вызывать только заголовок, а НЕ весь ресурс. Иначе мы бы ничего не выиграли. Авторы отправляются в эту тему. Однако я не совсем уверен, правильно ли я закрываю здесь URLConnection. Лучше перепроверьте, если сомневаетесь ;)
  3. Если в кеше нет ресурса ИЛИ ресурс в кеше устарел:
  4. Закройте мини-соединение и откройте «правильное», чтобы загрузить все это.
  5. Создайте новый объект CachedResource и добавьте его в кеш.
  6. Закройте и это новое соединение.
  7. Вернуть новый объект CachedUrlConnection, содержащий данные. Это может показаться немного глупым, поскольку у нас уже есть все, но функция должна возвращать URLConnection.
  8. Если есть какое-либо исключение или мы просто не работали с файлом jpg, png, ..., верните объект MyHttpUrlConnection «по умолчанию» для нормальной обработки URL-адреса.

И соответствующий код выглядит следующим образом. Обратите внимание, что я использовал Apache org.apache.commons.io.IOUtils:

@Override
protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {

    try {

        // Is this some resource that we'd like to cache?
        if (ResourceCache.isCachableURL(url)) {

            // Retrieve whatever is in the cache first.
            ResourceCache cache = ResourceCache.getInstance();
            CachedResource resource = cache.getCachedResource(url);

            // Open a connection to the server to at least check for the last-modified field.
            MyHttpUrlConnection conn = new MyHttpUrlConnection(url, this); // Don't use URL#openConnection to avoid looping!
            conn.setRequestMethod("HEAD");
            conn.connect();
            long lastModified = conn.getLastModified();

            // Did we get the last-modified value at all?
            if (lastModified == 0) {
                throw new Exception("No last-modified value could be read! \n\t" + url);
            }

            // Resource not cached or out of date?
            if (resource == null || resource.getLastModified() < lastModified) {

                conn = new MyHttpUrlConnection(url, this);
                conn.connect();
                InputStream input = conn.getInputStream();
                byte[] data = IOUtils.toByteArray(input);
                Map<String, List<String>> headerFields = conn.getHeaderFields();
                IOUtils.closeQuietly(input); 

                resource = new CachedResource(url.getFile(), data, headerFields, lastModified); // I use url.getFile() to store the file on my hard drive.
                cache.addCachedResource(url, resource);
            }

            return new CachedUrlConnection(url, resource);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    // Return the default HttpURLConnection in our wrapper class.
    return new MyHttpUrlConnection(url, proxy, this);
}

И последнее: на всякий случай НИКОГДА не используйте метод URL#openConnection внутри функции MyUrlConnectionHandler#openConnection. Записав это таким образом, становится довольно очевидным, почему, но сегодня мне потребовалось довольно много времени, чтобы понять, откуда взялся бесконечный цикл %) Вместо этого используйте конструктор и вызовите connect().

Я надеюсь, что это когда-нибудь кому-нибудь поможет, в остальном это было хорошим упражнением для меня ^^

person MightyMalcolm    schedule 23.09.2015

Насколько я знаю, это все еще в силе. Если вы хотите реализовать это только для конкретной цели, это не так сложно, но решение общего назначения будет более сложным.

person mipa    schedule 21.09.2015
comment
Ну, а пока я был бы счастлив, если бы мог хотя бы хранить тяжелые файлы изображений на своем жестком диске, а затем загружать их оттуда. Однако я понятия не имею, как это сделать. Можно ли расширить net.www.protocol.http.Handler или HttpURLConnection и просто немного изменить их функциональность в соответствии с моими потребностями? (На данный момент мне нужно только поддерживать http.) - person MightyMalcolm; 21.09.2015