Создание мобильных Web-приложений с применением HTML 5: Часть 2. Организация локального хранилища для мобильных Web-приложений с помощью HTML 5

Улучшите скорость своего мобильного приложения с помощью унифицированного локального хранилища данных

Одним из самых полезных новшеств в HTML 5 является стандартизация локального хранилища. Наконец-то Web-разработчики могут прекратить попытки втиснуть все данные на стороне клиента в 4-КБ cookie-файл. Теперь с помощью простого API можно хранить большие объемы данных в клиентской системе. Это идеальный механизм кэширования, который позволяет значительно улучшить быстродействие приложения – критический фактор для мобильных Web-приложений, которые опираются на гораздо более медленные соединения, чем их настольные собратья. Во второй части цикла статей об HTML 5 мы рассмотрим локальное хранилище, процесс его отладки и разнообразные способы его использования для совершенствования мобильных Web-приложений.

Майкл Галпин, инженер по программному обеспечению, Vitria Technology

Майкл Галпин (Michael Galpin) имеет учёную степень по математике в Калифорнийском Технологическом институте. Он является Java-разработчиком с конца 90-х гг. и работает инженером по программному обеспечению в Vitria Technology, в Саннивейл, Калифорния.



29.06.2010 (Впервые опубликовано 10.02.2012)

02 июня 2010 г.: добавлены ссылки на часть 3 этого цикла в разделах "Об этом цикле статей", "Заключение" и "Ресурсы".

08 июня 2010 г.: добавлены ссылки на часть 4 этого цикла в разделах "Об этом цикле статей", "Заключение" и "Ресурсы".

29 июня 2010 г.: добавлены ссылки на часть 5 этого цикла в разделах "Об этом цикле статей", "Заключение" и "Ресурсы".

Об этом цикле статей

HTML 5 – чрезвычайно популярная технология, и тому есть уважительная причина. Она обещает стать технологической переломной точкой в процессе переноса настольных приложений в браузер. Она перспективна для традиционных браузеров, но еще больше возможностей обещает браузерам мобильным. Более того, разработчики самых популярных мобильных браузеров уже освоили и реализовали многие важные элементы спецификации HTML 5.

В этом цикле из пяти статей мы рассмотрим некоторые из этих новых технологий, которые входят в HTML 5 и могут оказать огромное влияние на разработку мобильных Web-приложений. В каждой статье мы будем разрабатывать какое-нибудь действующее мобильное Web-приложение, демонстрирующее функции HTML 5, которое можно использовать с современными мобильными Web-браузерами вроде тех, что работают на устройствах iPhone и Android.


Предварительные замечания

Часто используемые сокращения

  • API: Application Programming Interface – интерфейс программирования приложений
  • CSS: Cascading stylesheet – каскадная таблица стилей
  • DOM: Document Object Model – объектная модель документов
  • HTML: Hypertext Markup Language – язык гипертекстовой разметки
  • HTTP: Hypertext Transfer Protocol – протокол передачи гипертекстовых файлов
  • JSON: JavaScript Object Notation – система обозначений (нотация) объектов JavaScrip
  • JSONP: JSON with padding – JSON с грунтованием
  • SDK: Software Developer Kit – пакет ПО разработчика
  • UI: User Interface – пользовательский интерфейс
  • URL: Uniform Resource Locator – унифицированный указатель ресурсов
  • W3C: World Wide Web Consortium

В этой статье мы разработаем Web-приложение с использованием новейших Web-технологий. Большая часть кода – это просто HTML, JavaScript и CSS – основные технологии любого Web-разработчика. Главное, что вам понадобится – это браузеры для тестирования. Большая часть кода из этой статьи будет работать с последними версиями настольных браузеров – с некоторыми заметными исключениями. Конечно, приложение нужно будет протестировать и на мобильных браузерах, так что понадобятся последние SDK iPhone и Android. В этой статье используются iPhone SDK 3.1.3 и Android SDK 2.1. См. ссылки в разделе Ресурсы.


Служба 101 локального хранения данных

Web-разработчики давно мечтают о возможности хранить данные на стороне клиента. Использование для этой цели cookie-файлов HTTP – явное издевательство. В 4 КБ, выделенные спецификацией HTTP, программисты как-то ухитряются втискивать неимоверное количество данных. Причина проста. Интерактивным Web-приложениям по разным причинам необходимо хранить те или иные данные, а хранение данных на сервере нередко оказывается неэффективным, небезопасным или неуместным. На протяжении многих лет предлагалось несколько альтернативных подходов к решению этой проблемы. В браузерах появлялись собственные API для хранения данных. Разработчики использовали расширенные возможности хранения данных, имеющиеся в Flash Player, обращаясь к нему через JavaScript. Google предложила плагин Gears для различных браузеров, который включает API хранения данных. Не удивительно, что в некоторых библиотеках JavaScript предпринимались попытки сгладить эти различия. Иными словами, эти библиотеки предоставляют простой API, а затем проверяют, какие имеются возможности для хранения данных (это может быть собственный API браузера или плагин, такой как Flash).

К счастью, в спецификации HTML 5, наконец, появился стандарт локального хранения данных, который реализован в широком спектре браузеров. На самом деле, этот стандарт стал одним из наиболее быстро освоенных и поддерживается последними версиями всех основных браузеров: Microsoft®, Internet Explorer®, Mozilla Firefox, Opera, Apple Safari и Google Chrome. Для разработчиков мобильных приложений еще важнее то, что он поддерживается браузерами на базе WebKit, таких как в iPhone, и в телефонах на платформе Android (версии 2.0 или более поздней версии), а также другими мобильными браузерами, такими как Mozilla Fennec. Теперь давайте рассмотрим эти API.

API Storage

API localStorage достаточно прост. На самом деле, в спецификации HTML 5 он реализует интерфейс хранения данных DOM Storage. Дело в том, что HTML 5 определяет два разных объекта, реализующих этот интерфейс, localStorage и sessionStorage. Объект sessionStorage представляет собой реализацию Storage, которая хранит данные только во время сеанса связи. Точнее, если не работает сценарий, способный получать доступ к sessionStorage, браузер может удалить данные sessionStorage. localStorage, напротив, действует на протяжении нескольких сеансов работы пользователя. Эти два объекта разделяют один и тот же API, поэтому я остановлюсь только на localStorage.

API Storage – это классическая структура данных на основе пары имя/значение. Чаще всего вам придется использовать методы getItem(имя) и setItem(имя, значение). Они работают именно так, как можно предположить: getItem возвращает значение, связанное с именем, или значение null, если ничего нет, а setItem либо добавляет пару имя/значение к localStorage, либо заменяет существующее значение. Еще есть метод removeItem(имя), который, как следует из названия, удаляет пару имя/значение из localStorage, если она существует и ничего не делает. Наконец, есть два API для перебора всех пар имя/значение. Один из них – это свойство length, которое дает общее число хранящихся пар имя/значение. А метод key(index) возвращает имя таблицы всех имен, используемых в хранилище.

Эти простые API позволяют сделать многое. Примерами служат персонализация или отслеживание поведения пользователей. Все это важно для мобильных Web-сайтов, но есть намного более эффектное применение: кэширование. localStorage позволяет легко кэшировать данные с серверов на локальной машине клиента. Это исключает необходимость ожидания ответа от потенциально медлительных серверов и минимизирует количество данных, которые они должны передать. Теперь рассмотрим пример, демонстрирующий, как использовать localStorage для реализации такого кэширования.


Пример: кэширование с помощью локального хранилища данных

Пример основан на коде, который мы начали разрабатывать в первой части этого цикла. Тот пример демонострировал, как выполнить локальный поиск в Twitter, получив местоположение пользователя с помощью API геолокации. Отталкиваясь от него, упростим этот код и повысим быстродействие приложения. Для начала, отделим геолокацию, оставив только поиск в Twitter. В листинге 1 приведен код упрощенного приложения для поиска в Twitter.

Листинг 1. Простейший поиск в Twitter
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name = "viewport" content = "width = device-width"/>
<title>Basic Twitter Search</title>
<script type="text/javascript">
    function searchTwitter(){
        var query = "http://search.twitter.com/search.json?callback
=showResults&q=";
        query += $("kwBox").value;
        var script = document.createElement("script");
        script.src = query;
        document.getElementsByTagName("head")[0].appendChild(script);
    }
    // код ui удален для краткости
    function showResults(response){
        var tweets = response.results;
        tweets.forEach(function(tweet){
            tweet.linkUrl = "http://twitter.com/" + tweet.from_user 
+ "/status/" + tweet.id;
        });
        makeResultsTable(tweets);
    }
</script>
<!--  CSS deleted for brevity -->
</head>
<body>
    <div id="main">
        <label for="kwBox">Search Twitter:</label>
        <input type="text" id="kwBox"/>
        <input type="button" value="Go!" onclick="searchTwitter()"/>
    </div>
    <div id="results">
    </div>
</body>
</html>

В этом приложении используется поддержка JSONP в API поиска Twitter. Когда пользователь инициирует поиск, API вызывается путем динамического введения в страницу тега script с указанием имени функции обратного вызова. Это позволяет делать кросс-доменные вызовы с Web-страницы. Когда вызов возвращается, инициируется функция обратного вызова (showResults). К каждому твиту, возвращаемому Twitter, добавляется ссылка URL, а затем создается простая таблица, отображающая эти твиты. Чтобы ускорить процесс, можно кэшировать результаты поискового запроса и извлекать их из кэша всякий раз, когда пользователь отправляет запрос. Для начала рассмотрим, как применять localStorage для локального хранения твитов.

Локальное хранение

При простом поиске в Twitter API поиска создает массив твитов. Если эти твиты можно хранить локально и связать с операцией поиска по ключевым словам, которая их генерирует, то получится полезный кэш. Чтобы сохранить твиты, нужно всего лишь модифицировать функцию callback, которая будет вызываться при обращении к API поиска Twitter. В листинге 2 показаны измененные функции.

Листинг 2. Поиск и сохранение
function searchTwitter(){
    var keyword = $("kwBox").value;
    var query = "http://search.twitter.com/search.json?callback
=processResults&q=";
    query += keyword;
    var script = document.createElement("script");
    script.src = query;
    document.getElementsByTagName("head")[0].appendChild(script);
}
function processResults(response){
    var keyword = $("kwBox").value;
    var tweets = response.results;
    tweets.forEach(function(tweet){
        saveTweet(keyword, tweet);
        tweet.linkUrl = "http://twitter.com/" + tweet.from_user + "/status/" + tweet.id;
    });
    makeResultsTable();
    addTweetsToResultsTable(tweets);
}
function saveTweet(keyword, tweet){
    // проверка, поддерживает ли браузер localStorage
    if (!window.localStorage){
        return;
    }
    if (!localStorage.getItem("tweet" + tweet.id)){
        localStorage.setItem("tweet" + tweet.id, JSON.stringify(tweet));
    }
    var index = localStorage.getItem("index::" + keyword);
    if (index){
        index = JSON.parse(index);
    } else {
        index = [];
    }
    if (!index.contains(tweet.id)){
        index.push(tweet.id);
        localStorage.setItem("index::"+keyword, JSON.stringify(index));
    } 
}

Начнем с первой функции, searchTwitter. Она вызывается, когда поиск инициирован пользователем. Единственное, что изменилось по сравнению с листингом 1, это функция callback. Вместо того чтобы просто показать твиты, когда они возвращаются, их нужно обработать (сохранить и отобразить). Для этого определим новую callback-функцию processResults. Мы берем каждый твит и вызываем saveTweet. Еще мы передаем ключевое слово, которое использовалось для получения результатов поиска. Это нужно для того, чтобы связать твиты с ключевыми словами.

В функции saveTweet начнем с проверки того, что localStorage действительно поддерживается браузером. Как упоминалось выше, localStorage широко поддерживается в настольных и мобильных браузерах, но при использовании новой функции это всегда полезно проверить. Если она не поддерживается, то мы просто выходим из функции. Очевидно, что ничего не будет сохранено, но и никакие ошибки генерироваться не будут – в этом случае у приложения просто не будет кэша. Если localStorage поддерживается, сначала проверим, не сохранен ли уже данный твит. Если он не хранится, то сохраним его локально, используя setItem. Затем извлечем объект индекса, который соответствует ключевому слову. Это просто массив идентификаторов твитов, связанных с ключевым словом. Если идентификатор твита еще не входит в индекс, добавим его и обновим индекс.

Обратите внимание, что при сохранении и загрузке JSON в листинге 3 мы использовали JSON.stringify и JSON.parse. Объект JSON (точнее, window.JSON) входит в спецификацию HTML 5 как стандартный объект, который всегда присутствует. Метод stringify превращает любой объект JavaScript в упорядоченную строку, а метод parse выполняет обратное действие. Он восстанавливает объект JavaScript из представления в виде упорядоченной строки. Это необходимо, поскольку localStorage хранит только строки. Однако стандартный объект JSON обладает не столь широкой поддержкой, как localStorage. Например, он отсутствует в последней версии браузера Mobile Safari на iPhone (на момент написания этой статьи версия 3.1.3). В последних версиях браузера Android он поддерживается. Это можно легко проверить, и если он отсутствует – загрузить дополнительный файл JavaScript. Тот же объект JSON, который используется стандартно, можно получить на Web-сайте json.org (см. раздел Ресурсы). Чтобы увидеть, как эти упорядоченные строки выглядят локально, можно использовать различные инструменты браузера для проверки содержания localStorage данного сайта. На рисунке 1 показаны некоторые сохраненные твиты, просматриваемые с помощью инструментов разработчика Chrome.

Рисунок 1. Локально сохраненные твиты
Снимок с экрана списка локального кэша твитов (с полями ключа и значения)

И Chrome, и Safari имеют встроенные инструменты для разработчиков, которые позволяют просматривать любые данные, сохраненные в localStorage. Это может быть очень полезно для отладки приложений, использующих localStorage. Они показывают пары ключ/значение, которые хранятся локально в виде неформатированного текста. Научившись сохранять твиты, поступающие из API поиска Twitter, так что их можно использовать в качестве кэша, нужно научиться считывать их из localStorage. Вот как это делается.

Быстрая загрузка локальных данных

В листинге 2 вы видели некоторые примеры чтения localStorage с использованием метода getItem. Теперь, когда пользователь инициирует поиск, можно проверить кэш-хит и сразу же загрузить сохраненные результаты. Конечно, все равно нужно будет обратиться к API поиска Twitter и добавить к сохраненным результатам новые. Однако теперь запросы можно делать эффективнее, запрашивая только те результаты, которых еще нет в кэше. В листинге 3 приведен обновленный код поиска.

Листинг 3. Предварительный локальный поиск
function searchTwitter(){
    if ($("resultsTable")){
        $("resultsTable").innerHTML = ""; // clear results
    }
    makeResultsTable();
    var keyword = $("kwBox").value;
    var maxId = loadLocal(keyword);
    var query = "http://search.twitter.com/search.json?callback=processResults&q=";
    query += keyword;
    if (maxId){
        query += "&since_id=" + maxId;
    }
    var script = document.createElement("script");
    script.src = query;
    document.getElementsByTagName("head")[0].appendChild(script);
}
function loadLocal(keyword){
    if (!window.localStorage){
        return;
    }
    var index = localStorage.getItem("index::" + keyword);
    var tweets = [];
    var i = 0;
    var tweet = {};
    if (index){
        index = JSON.parse(index);
        for (i=0;i<index.length;i++){
            tweet = localStorage.getItem("tweet"+index[i]);
            if (tweet){
                tweet = JSON.parse(tweet);
                tweets.push(tweet);
            }
        }
    }
    if (tweets.length < 1){
        return 0;
    }
    tweets.sort(function(a,b){
        return a.id > b.id;
    });
    addTweetsToResultsTable(tweets);
    return tweets[0].id;
}

Первое, что бросается в глаза, это то, что при передаче поискового запроса мы сначала обращаемся к новой функции loadLocal. Эта функция возвращает целое число, которое представляет собой идентификатор твита, добавленного в кэш последним. Функция loadLocal принимает keyword и использует его для поиска соответствующих твитов в кэше localStorage. Если у вас есть maxId, используйте его для изменения запроса к Twitter, добавив параметр since_id. Тем самым вы инструктируете API Twitter, чтобы он возвращал только те твиты, которые появились после ID, указанного в этом параметре. Потенциально это может уменьшить количество результатов, возвращаемых из Twitter. Всякий раз, когда можно оптимизировать процесс обращения мобильных Web-приложенийк серверу, это может реально улучшить реакцию в медленных сетях мобильной связи. Теперь подробнее остановимся на функции loadLocal.

В ней используются структуры данных, сохраненные еще в листинге 2. Сначала загрузим индекс, связанный с ключевым словом, воспользовавшись методом getItem. Если индекс не найден, значит кэшированных твитов нет, так что показывать нечего, и никакой оптимизации запроса не получится (на это будет указывать возвращенное значение 0). Если индекс найден, вы получите список идентификаторов. Каждый из этих твитов хранится в локальном кэше, так что достаточно просто вновь применить метод getItem для загрузки каждого из них. Затем загруженные твиты сортируются. Воспользуемся функцией addTweetsToResultsTable для отображения твитов, а затем получим значение идентификатора последнего твита. В этом примере код, который получает новые твиты, непосредственно обращается к функциям обновления пользовательского интерфейса. Это приводит к зависимости между кодом для хранения и извлечения твитов и кодом для их отображения, так как для того и другого используется функция processResults. Альтернативой служит менее зависимый подход с использованием событий хранения.

События хранения

Расширим пример приложения, чтобы он показывал топ-10 поисковых терминов, которые чаще всего встречаются в сохраненных результатах. Они будут соответствовать поисковым запросам, которые пользователь делает чаще всего. В листинге 4 показана функция для вычисления этих топ-10 запросов и их отображения.

Листинг 4. Определение топ-10 поисковых запросов
function displayStats(){
    if (!window.localStorage){ return; }
    var i = 0;
    var key = "";
    var index = [];
    var cachedSearches = [];
    for (i=0;i<localStorage.length;i++){
        key = localStorage.key(i);
        if (key.indexOf("index::") == 0){
            index = JSON.parse(localStorage.getItem(key));
            cachedSearches.push ({keyword: key.slice(7), numResults: index.length});
        }
    }
    cachedSearches.sort(function(a,b){
        if (a.numResults == b.numResults){
            if (a.keyword.toLowerCase() < b.keyword.toLowerCase()){
                return -1;
            } else if (a.keyword.toLowerCase() > b.keyword.toLowerCase()){
                return 1;
            }
            return 0;
        }
        return b.numResults – a.numResults;
    }).slice(0,10).forEach(function(search){
        var li = document.createElement("li");
        var txt = document.createTextNode(search.keyword + " : " + search.numResults);
        li.appendChild(txt);
        $("stats").appendChild(li);
    });
}

Эта функция иллюстрирует другие API localStorage. Начнем с получения общего количества элементов, хранящихся в localStorage, а затем переберем их. Если элемент – это индекс, то вы анализируете объект и создаете другой, содержащий данные, которые нужно обработать: ключевое слово, связанное с индексом, и количество твитов в индексе. Эти данные хранятся в массиве cachedSearches. Затем отсортируем cachedSearches, расположив вверху поисковые запросы с наибольшим количеством результатов, а затем применим алфавитную сортировку без учета регистра, если два поисковых запроса имеют одно и то же число сохраненных результатов. Затем возьмем 10 верхних поисковых запросов, создадим HTML-код для каждого из них и добавим их в упорядоченный список. Вызовем эту функцию при первой загрузке страницы, как показано в листинге 5.

Листинг 5. Инициализация страницы
window.onload = function() {
    displayStats();
    document.body.setAttribute("onstorage", "handleOnStorage();");
}

Когда страница загрузится, первая строка вызывает функцию из листинга 4. При второй загрузке происходят более интересные вещи. Здесь устанавливается обработчик событий для события onstorage. Данное событие происходит всякий раз, когда заканчивается выполнение функции localStorage.setItem. Это позволяет пересчитать топ-10 поисковых запросов. Такой обработчик событий показан в листинге 6.

Листинг 6. Обработчик событий хранения
function handleOnStorage() {
    if (window.event && window.event.key.indexOf("index::") == 0){
        $("stats").innerHTML = "";
        displayStats();
    }
}

Событие onstorage будет связано с окном. У него есть несколько полезных свойств: key, oldValue и newValue. В дополнение к этим свойствам, которые не нуждаются в пояснении, есть также url (адрес страницы, изменившей значение) и source (окно, содержащее сценарий, изменивший значение). Эти два последних свойства полезны, если пользователь может работать с вашим приложением в нескольких окнах, вкладках или даже плавающих фреймах – все эти возможности не часто встречаются в мобильных приложениях. Возвращаясь к листингу 6, единственное действительно нужное свойство, это key. Мы используем его для того, чтобы увидеть, был ли изменен какой-нибудь индекс. Если да, то сбросим список топ-10 и вновь составим его, опять обратившись к функции displayStats. Преимуществом этого способа является то, что ни одной другой функции не нужно знать о существовании списка топ-10, так что он самодостаточен.

Выше я упоминал, что DOM Storage в целом, куда входят и localStorage, и sessionStorage, представляет собой элемент HTML 5, получивший широкое признание. Однако события хранения составляют исключение – по крайней мере на настольных браузерах. На момент написания статьи события хранения поддерживали только Safari 4+ и Internet Explorer 8+. В Firefox, Chrome и Opera они не поддерживаются. Однако в мобильном мире дело обстоит немного лучше. Последние версии iPhone и Android полностью поддерживают события хранения, и весь представленный здесь код прекрасно работает в этих бразуерах.


Заключение

Вы, как разработчик, можете чувствовать себя очень свободно, когда вдруг получите огромный объем дискового пространства на клиенте. Для опытных Web-разработчиков это открывает такие возможности, о которых они мечтали годами, не имея хорошего способа достичь их без разного рода ухищрений. Для разработчиков мобильных приложений это еще интереснее, так как открывает возможности для настоящего локального кэширования данных. Кроме существенного улучшения производительности приложения, локальное кэширование имеет ключевое значение для обеспечения другой новой захватывающей возможности мобильных Web-приложений: работы в автономном режиме. Это будет темой следующей статьи данного цикла.


Загрузка

ОписаниеИмяРазмер
Исходный код для статьиlocal.zip8 КБ

Ресурсы

Научиться

Получить продукты и технологии

  • Проект Modernizr: мощная утилита для поиска функций HTML 5, в том числе таких, как localStorage, Web Workers, applicationCache и другие.
  • Web-сайт разработчиков Android: загрузите Android SDK, познакомьтесь с материалами по API и следите за последними новостями о платформе Android.
  • SDK iPhone: загрузите последнюю версию SDK iPhone для разработки приложений, работающих на iPAD, iPhone и iPod Touch.
  • Проект Open Source Android: получите открытый исходный код мобильной платформы Android.
  • SDK Google App Engine: загрузите инструменты Java™ и Python для создания масштабируемых Web-приложений с использованием Google.
  • Объект JSON c Web-сайта json.org: получите тот же объект JSON, который используется стандартно.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source
ArticleID=792770
ArticleTitle=Создание мобильных Web-приложений с применением HTML 5: Часть 2. Организация локального хранилища для мобильных Web-приложений с помощью HTML 5
publish-date=06292010