Android cache html content

Displaying Cache data or Offline mode support in android application

Problems without caching
1. Fetching data over a network connection is an expensive operation. Sometimes due to poor network connectivity, it takes a lot of time to display result( which is a bad experience in terms of mobile application).

2. Sometimes requirement may come with offline support or to display previously fetched response when there is no internet connectivity.

Thanks to Caching mechanism. We can solve the above requirements if we cache some data.

In my project, I have used the dagger, so I have written all the code for caching in ApiModule class. If you want to skip then directly go to below link

How to enable cache
1. define cache memory size

2. Create a cache object ( provide application context )

Provide this cache object in OkHttpClient

Now we will create 2 interceptors ( onlineInterceptor and offlineInterceptor). Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls.

OnlineInterceptor which will be used when the device is connected to the network

OfflineInterceptor which will be used when the device is not connected to the network

Now its time to add cache interceptor to OkHttpClient.

Finally, add OkHttpClient to Retrofit

Using this retrofit instance, we can make an API call and the caching mechanism will work.

Few points to remember

Soon I will post more articles on android, core java.
till then happy learning… 🙂

MindOrks

Our community publishes stories worth reading on Android…

Источник

Кэшируем пагинацию в Android

Наверняка каждый Android разработчик работал со списками, используя RecyclerView. А также многие успели посмотреть как организовать пагинацию в списке, используя Paging Library из Android Architecture Components.

Все просто: устанавливаем PositionalDataSource, задаем конфиги, создаем PagedList и скармливаем все это вместе с адаптером и DiffUtilCallback нашему RecyclerView.

Но что если у нас несколько источников данных? Например, мы хотим иметь кэш в Room и получать данные из сети.

Кейс получается довольно кастомный и в интернете не так уж много информации на эту тему. Я постараюсь это исправить и показать как можно решить такой кейс.

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

Как бы выглядело решение без пагинации:

  • Обращение к кэшу (в нашем случае это БД)
  • Если кэш пуст — отправка запроса на сервер
  • Получаем данные с сервера
  • Отображаем их в листе
  • Пишем в кэш
  • Если кэш имеется — отображаем его в списке
  • Получаем актуальные данные с сервера
  • Отображаем их в списке○
  • Пишем в кэш

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

Алгоритм примерно следующий:

  • Получаем данные из кэша для первой страницы
  • Если кэш пуст — получаем данные сервера, отображаем их в списке и пишем в БД
  • Если кэш есть — загружаем его в список
  • Если доходим до конца БД, то запрашиваем данные с сервера, отображаем их
  • в списке и пишем в БД

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

В Google задумались над этим и создали решение, которое идет из коробки PagingLibrary — BoundaryCallback.

BoundaryCallback сообщает когда локальный источник данных “заканчивается” и уведомляет об этом репозиторий для загрузки новых данных.

На официальном сайте Android Dev есть ссылка на ​репозиторий​ с примером проекта, использующего список с пагинацией с двумя источниками данных: Network (Retrofit 2) + Database (Room). Для того, чтобы лучше понять как работает такая система попробуем разобрать этот пример, немного его упростим.

Читайте также:  Подключение модема для планшета с андроид

Начнем со слоя data. Создадим два DataSource.

В этом интерфейсе описаны запросы к API Reddit и классы модели (ListingResponse, ListingData, RedditChildrenResponse), в объекты которых будут сворачиваться ответы API.

И сразу сделаем модель для Retrofit и Room

Класс RedditDb.kt, который будет наследовать RoomDatabase.

Помним, что создавать класс RoomDatabase каждый раз для выполнения запроса к БД очень затратно, поэтому в реальном кейсе создавайте его единожды за все время жизни приложения!

И класс Dao с запросами к БД RedditPostDao.kt

Вы наверное заметили, что метод получения записей postsBySubreddit возвращает
DataSource.Factory. Это необходимо для создания нашего PagedList, используя
LivePagedListBuilder, в фоновом потоке. Подробнее об этом вы можете почитать в
уроке.

Отлично, слой data готов. Переходим к слою бизнес логики.Для реализации паттерна “Репозиторий” принято создавать интерфейс репозитория отдельно от его реализации. Поэтому создадим интерфейс RedditPostRepository.kt

И сразу вопрос — что за Listing? Это дата класс, необходимый для отображения списка.

Создаем реализацию репозитория MainRepository.kt

Давайте посмотрим что происходит в нашем репозитории.

Создаем инстансы наших датасорсов и интерфейсы доступа к данным. Для базы данных:

RoomDatabase и Dao, для сети: Retrofit и интерфейс апи.

Далее реализуем обязательный метод репозитория

который настраивает пагинацию:

  • Создаем SubRedditBoundaryCallback, наследующий PagedList.BoundaryCallback<>
  • Используем конструктор с параметрами и передадим все, что нужно для работы BoundaryCallback
  • Создаем триггер refreshTrigger для уведомления репозитория о необходимости обновить данные
  • Создаем и возвращаем Listing объект

В Listing объекте:

  • livePagedList
  • networkState — состояние сети
  • retry — callback для вызова повторного получения данных с сервера
  • refresh — тригер для обновления данных
  • refreshState — состояние процесса обновления

Реализуем вспомогательный метод

для записи ответа сети в БД. Он будет использоваться, когда нужно будет обновить список или записать новую порцию данных.

Реализуем вспомогательный метод

для тригера обновления данных. Тут все довольно просто: получаем данные с сервера, чистим БД, записываем новые данные в БД.

С репозиторием разобрались. Теперь давайте взглянем поближе на SubredditBoundaryCallback.

В классе, который наследует BoundaryCallback есть несколько обязательных методов:

Метод вызывается, когда БД пуста, здесь мы должны выполнить запрос на сервер для получения первой страницы.

Метод вызывается, когда “итератор” дошел до “дна” БД, здесь мы должны выполнить запрос на сервер для получения следующей страницы, передав ключ, с помощью которого сервер выдаст данные, следующие сразу за последней записью локального стора.

Метод вызывается, когда “итератор” дошел до первого элемента нашего стора. Для реализации нашего кейса можем проигнорировать реализацию этого метода.

Дописываем колбэк для получения данных и передачи их дальше

Дописываем метод записи полученных данных в БД

Что за хэлпер PagingRequestHelper? Это ЗДОРОВЕННЫЙ класс, который нам любезно предоставил Google и предлагает вынести его в библиотеку, но мы просто скопируем его в пакет слоя логики.

* The helper provides an API to observe combined request status, which can be reported back to the * application based on your business rules. * */ // THIS class is likely to be moved into the library in a future release. Feel free to copy it // from this sample. public class PagingRequestHelper < private final Object mLock = new Object(); private final Executor mRetryService; @GuardedBy("mLock") private final RequestQueue[] mRequestQueues = new RequestQueue[] ; @NonNull final CopyOnWriteArrayList
mListeners = new CopyOnWriteArrayList<>(); /** * Creates a new com.memebattle.pagingwithrepository.domain.util.PagingRequestHelper with the given <@link Executor>which is used to run * retry actions. * * @param retryService The <@link Executor>that can run the retry actions. */ public PagingRequestHelper(@NonNull Executor retryService) < mRetryService = retryService; >/** * Adds a new listener that will be notified when any request changes <@link Status state>. * * @param listener The listener that will be notified each time a request’s status changes. * @return True if it is added, false otherwise (e.g. it already exists in the list). */ @AnyThread public boolean addListener(@NonNull Listener listener) < return mListeners.add(listener); >/** * Removes the given listener from the listeners list. * * @param listener The listener that will be removed. * @return True if the listener is removed, false otherwise (e.g. it never existed) */ public boolean removeListener(@NonNull Listener listener) < return mListeners.remove(listener); >/** * Runs the given <@link Request>if no other requests in the given request type is already * running. *

Читайте также:  Андроид тихий звук вызова

* If run, the request will be run in the current thread. * * @param type The type of the request. * @param request The request to run. * @return True if the request is run, false otherwise. */ @SuppressWarnings(«WeakerAccess») @AnyThread public boolean runIfNotRunning(@NonNull RequestType type, @NonNull Request request) < boolean hasListeners = !mListeners.isEmpty(); StatusReport report = null; synchronized (mLock) < RequestQueue queue = mRequestQueues[type.ordinal()]; if (queue.mRunning != null) < return false; >queue.mRunning = request; queue.mStatus = Status.RUNNING; queue.mFailed = null; queue.mLastError = null; if (hasListeners) < report = prepareStatusReportLocked(); >> if (report != null) < dispatchReport(report); >final RequestWrapper wrapper = new RequestWrapper(request, this, type); wrapper.run(); return true; > @GuardedBy(«mLock») private StatusReport prepareStatusReportLocked() < Throwable[] errors = new Throwable[]< mRequestQueues[0].mLastError, mRequestQueues[1].mLastError, mRequestQueues[2].mLastError >; return new StatusReport( getStatusForLocked(RequestType.INITIAL), getStatusForLocked(RequestType.BEFORE), getStatusForLocked(RequestType.AFTER), errors ); > @GuardedBy(«mLock») private Status getStatusForLocked(RequestType type) < return mRequestQueues[type.ordinal()].mStatus; >@AnyThread @VisibleForTesting void recordResult(@NonNull RequestWrapper wrapper, @Nullable Throwable throwable) < StatusReport report = null; final boolean success = throwable == null; boolean hasListeners = !mListeners.isEmpty(); synchronized (mLock) < RequestQueue queue = mRequestQueues[wrapper.mType.ordinal()]; queue.mRunning = null; queue.mLastError = throwable; if (success) < queue.mFailed = null; queue.mStatus = Status.SUCCESS; >else < queue.mFailed = wrapper; queue.mStatus = Status.FAILED; >if (hasListeners) < report = prepareStatusReportLocked(); >> if (report != null) < dispatchReport(report); >> private void dispatchReport(StatusReport report) < for (Listener listener : mListeners) < listener.onStatusChange(report); >> /** * Retries all failed requests. * * @return True if any request is retried, false otherwise. */ public boolean retryAllFailed() < final RequestWrapper[] toBeRetried = new RequestWrapper[RequestType.values().length]; boolean retried = false; synchronized (mLock) < for (int i = 0; i

Со слоем бизнес логики закончили, можем переходить к реализации представления.
В слое представления у нас новая MVVM от Google на ViewModel и LiveData.

В методе onCreate инициализируем ViewModel, адаптер списка, подписываемся на изменение названия подписки и вызываем через модель запуск работы репозитория.

Если вы не знакомы с механизмами LiveData и ViewModel, то рекомендую ознакомиться с уроками.

В модели реализуем методы, которые будут дергать методы репозитория: retry и refesh.

Адаптер списка будет наследовать PagedListAdapter. Тут все также как и работе с пагинацией и одним источником данных.

И все те же ViewHolder ы для отображения записи и итема состояния загрузки данных из сети.

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

Все работает, супер!

И мой репозиторий, где я постарался немного упростить пример от Google.

На этом все. Если вы знаете другие способы как “закэшировать” пагинацию, то обязательно напишите в комменты.

Источник

Руководство по использованию кэша приложения

Перевод: Влад Мержевич

Доступность в оффлайне становится всё более важной для веб-приложений. Да, все браузеры имеют механизмы кэширования, но они ненадежны и работают не всегда ожидаемо. HTML5 устраняет некоторые из этих неприятностей с помощью интерфейса ApplicationCache.

Использование интерфейса кэша даёт вашему приложению три преимущества:

  • автономный просмотр — пользователи могут исследовать ваш сайт целиком, когда они находятся в оффлайне;
  • скорость — ресурсы кэшируются локально, поэтому загружаются быстрее;
  • снижение нагрузки на сервер — браузер загружает с сервера только изменённые ресурсы.

Кэш приложения (или AppCache) позволяет разработчику указать, какие файлы браузер должен кэшировать и сделать доступными для оффлайновых пользователей. Ваше приложение будет работать корректно, даже если пользователь нажимает кнопку «Обновить», находясь в автономном режиме.

Файл манифеста кэша

Файл манифеста кэша является простым текстовым файлом со списком ресурсов, которые браузер должен кэшировать для доступа в автономном режиме.

Справка по файлу манифеста

Чтобы разрешить кэш приложения включите атрибут manifest в тег .

Атрибут manifest должен быть на каждой странице вашего приложения, которую вы хотите кэшировать. Браузер не кэширует страницу, если она не содержит атрибут manifest (и если она явно не упомянута в списке файла манифеста). Это означает, что любая страница с атрибутом manifest посещаемая пользователем будет неявно добавляться в кэш приложения. Таким образом, нет необходимости перечислять все страницы в манифесте.

Атрибут manifest задаёт абсолютный или относительный URL, но абсолютный путь должен быть в рамках того же веб-приложения. Файл манифеста может иметь любое расширение, но требуется указать правильный MIME-тип (см.ниже).

Читайте также:  Двигающие обои для андроид

Файл манифеста должен отдаваться с MIME-типом text/cache-manifest . Возможно, вам придётся добавить пользовательский тип файла на веб-сервере или через настройку .htaccess. Например, чтобы настроить этот MIME-тип в Apache, добавьте в конфигурационный файл:

Или в файл app.yaml на Google App Engine:

Структура файла манифеста

Простой манифест может выглядеть примерно так:

В этом примере кэшируется четыре файла указанных в манифесте.

Следует отметить несколько вещей:

  • строка CACHE MANIFEST должна идти первой, и обязательна;
  • данные кэширования сайтов ограничены 5 Мб. Однако если вы пишете приложение для Chrome Web Store, использование unlimitedStorage снимает это ограничение;
  • если файл манифеста или ресурс, указанный в нём не может быть скачан, весь процесс обновления кэша провалится, браузер станет использовать старый кэш приложения.

Давайте рассмотрим более сложный пример:

Строки, начинающиеся с решётки (#), являются комментариями, но также могут служить и другой цели. Кэш приложения обновляется только при изменении файла манифеста. Так, например, при редактировании изображений или функций JavaScript, эти изменения не будут кэшированы повторно. Вы должны изменить файл манифеста, чтобы сообщить браузеру обновить файлы в кэше. Создание комментария с номером версии, контрольной суммой или датой это один из способов гарантировать пользователям, что они используют последнюю версию. Вы можете также программно обновлять кэш, когда новая версия будет готова, как описано в разделе про обновление кэша.

Манифест может иметь три различных раздела: CACHE, NETWORK и FALLBACK.

CACHE: Это стандартный раздел для записи. Файлы, перечисленные в этом блоке (или сразу после CACHE MANIFEST) будут явно кэшированы после того как они скачаны в первый раз.

NETWORK: Файлы, перечисленные в этом разделе, это ресурсы, которые требуют подключения к серверу. Все запросы к этим ресурсам идут в обход кэша, даже если пользователь находится в оффлайне. Можно использовать * для задания шаблона.

FALLBACK: Дополнительный раздел указывает резервные страницы, если ресурс недоступен. Первый URL является ресурсом, второй резервом. Оба адреса должны быть относительны и быть в том же месте, что и файл манифеста. Можно использовать * для задания шаблона.

Эти разделы могут быть перечислены в любом порядке, каждый раздел может встречаться больше одного раза.

Следующий манифест определяет «универсальные» страницы (offline.html), которые будут отображаться, когда пользователь пытается получить доступ к корню сайта в автономном режиме. Он также заявляет, что все другие ресурсы (например, на удаленном сайте) требуют подключения к Интернету.

HTML-файл, который ссылается на ваш файл манифеста, кэшируются автоматически. Нет необходимости включать его в манифест, однако рекомендуем сделать это.

Обновление кэша

Оффлайновое приложение остаётся в кэше, пока не произойдёт одно из следующих событий:

  • пользователь очищает хранилище данных в браузере для вашего сайта;
  • файл манифеста изменился. Обновление файла упомянутого в манифесте не означает, что браузер будет повторно кэшировать этот ресурс. Файл манифеста сам должен быть заменён;
  • кэш приложения обновился программно.

Статус кэша

Объект window.applicationCache выступает вашим программным доступом к кэшу браузера. Свойство status используется для проверки текущего состояния кэша.

Для программного обновления кэша вначале вызывается applicationCache.update(). Он будет пытаться обновить кэш пользователя (который требует, чтобы файл манифеста изменился). Наконец, когда applicationCache.status находится в состоянии UPDATEREADY, вызов applicationCache.swapCache() обновит старый кэш на новый.

Использование update() и swapCache() не служит обновлением ресурсов пользователю. Этот поток просто говорит браузеру проверить новый манифест, скачать указанное обновленное содержания и повторно заполнить кэш приложения. Таким образом, страница скачивается с сервера дважды, в первый раз заполняется новый кэш приложения, а во второй раз обновляется содержание страницы. Хорошая новость: вы можете избежать этого двойного скачивания. Для обновления у пользователя новой версии вашего сайта установите отслеживание события updateready во время скачивания страницы.

События AppCache

Как вы можете ожидать, дополнительные события следят за статусом кэша. Браузера запускает события во время процесса скачивания, обновления кэша приложения и возникновения ошибки. Следующий фрагмент прослушивает события для каждого типа события кэша:

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

Источник

Оцените статью