Статьи

Android Full App, часть 3: анализ XML-ответа

Это третья часть серии «Android Full Tutorial» . Полное приложение предназначено, чтобы обеспечить легкий способ выполнять поиск фильмов / актеров через Интернет. В первой части серии ( «Основной интерфейс действий» ) мы создали проект Eclipse и настроили базовый интерфейс для основной деятельности приложения. Во второй части ( «Использование HTTP API» ) мы использовали клиентскую библиотеку Apache HTTP для использования внешнего HTTP API и интеграции возможностей поиска API в наше приложение. В этой части мы рассмотрим, как анализировать XML-ответ, используя встроенные в Android возможности синтаксического анализа XML.

TMDb API поддерживает форматы XML и JSON для ответов HTTP. Мы собираемся использовать XML для нашего приложения. Давайте сначала посмотрим, как выглядят некоторые примеры ответов на поисковые запросы:

Ответы — это типичные XML-документы, которые можно анализировать с использованием стандартных процедур с использованием SAX или DOM . Спецификация SAX определяет основанный на событиях подход, при котором реализованные анализаторы сканируют данные XML и используют обработчики обратного вызова при достижении определенных частей документа. С другой стороны, спецификация DOM определяет древовидный подход к навигации по документу XML.

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

Прежде чем мы приступим к синтаксическому анализу XML, мы собираемся создать несколько классов моделей, которые будут сопоставлять элементы XML с классами Java. Просто взглянув на ответы XML, можно получить следующие классы моделей:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.javacodegeeks.android.apps.moviesearchapp.model;
 
import java.util.ArrayList;
 
public class Person {
     
    public String score;
    public String popularity;
    public String name;
    public String id;
    public String biography;
    public String url;
    public String version;
    public String lastModifiedAt;
    public ArrayList<Image> imagesList;
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.javacodegeeks.android.apps.moviesearchapp.model;
 
import java.util.ArrayList;
 
public class Movie {
     
    public String score;
    public String popularity;
    public boolean translated;
    public boolean adult;
    public String language;
    public String originalName;
    public String name;
    public String type;
    public String id;
    public String imdbId;
    public String url;
    public String votes;
    public String rating;
    public String certification;
    public String overview;
    public String released;
    public String version;
    public String lastModifiedAt;
    public ArrayList<Image> imagesList;
     
    public String retrieveThumbnail() {
        if (imagesList!=null && !imagesList.isEmpty()) {
            for (Image movieImage : imagesList) {
                if (movieImage.size.equalsIgnoreCase(Image.SIZE_THUMB) &&
                        movieImage.type.equalsIgnoreCase(Image.TYPE_POSTER)) {
                    return movieImage.url;
                }
            }
        }
        return null;
    }   
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package com.javacodegeeks.android.apps.moviesearchapp.model;
 
public class Image {
     
    public static final String SIZE_ORIGINAL = "original";
    public static final String SIZE_MID = "mid";
    public static final String SIZE_COVER = "cover";
    public static final String SIZE_THUMB = "thumb";
 
    public static final String TYPE_PROFILE = "profile";
    public static final String TYPE_POSTER = "poster";
     
    public String type;
    public String url;
    public String size;
    public int width;
    public int height;
     
}

Здесь нет ничего особенного, мы просто добавляем поля String для каждого элемента XML. Обратите внимание, что класс Image будет обычно использоваться как классом Person, так и классом Movie. Кроме того, класс Movie предоставляет метод retrieveThumbnail, который перебирает доступные изображения и возвращает метод размером «большой палец» и типа «плакат».

Мы приступаем к созданию класса с именем XmlParser, который использует SAX-подход для анализа XML-ответов. Класс использует два пользовательских обработчика (PersonHandler и MovieHandler) для выполнения анализа. Код для этого класса следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.javacodegeeks.android.apps.moviesearchapp.services;
 
import java.io.StringReader;
import java.util.ArrayList;
 
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
 
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
 
import com.javacodegeeks.android.apps.moviesearchapp.handlers.MovieHandler;
import com.javacodegeeks.android.apps.moviesearchapp.handlers.PersonHandler;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.model.Person;
 
public class XmlParser {
     
    private XMLReader initializeReader() throws ParserConfigurationException, SAXException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        // create a parser
        SAXParser parser = factory.newSAXParser();
        // create the reader (scanner)
        XMLReader xmlreader = parser.getXMLReader();
        return xmlreader;
    }
     
    public ArrayList<Person> parsePeopleResponse(String xml) {
         
        try {
             
            XMLReader xmlreader = initializeReader();
             
            PersonHandler personHandler = new PersonHandler();
 
            // assign our handler
            xmlreader.setContentHandler(personHandler);
            // perform the synchronous parse
            xmlreader.parse(new InputSource(new StringReader(xml)));
             
            return personHandler.retrievePersonList();
             
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
         
    }
     
    public ArrayList<Movie> parseMoviesResponse(String xml) {
         
        try {
             
            XMLReader xmlreader = initializeReader();
             
            MovieHandler movieHandler = new MovieHandler();
 
            // assign our handler
            xmlreader.setContentHandler(movieHandler);
            // perform the synchronous parse
            xmlreader.parse(new InputSource(new StringReader(xml)));
             
            return movieHandler.retrieveMoviesList();           
             
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
 
    }
 
}

В каждом методе мы сначала извлекаем ссылку на класс фабрики синтаксического анализатора SAX, используя статический метод newInstance SAXParserFactory . Этот метод возвращает соответствующую реализацию Android. Затем объект SAXParser создается с использованием метода newSAXParser , который создает новый экземпляр SAXParser, используя текущие настроенные заводские параметры. Класс SAXParser определяет API, который оборачивает класс реализации XMLReader . XMLReader — это интерфейс для чтения XML-документа с использованием обратных вызовов. Обратные вызовы обычно определяются через классы, расширяющие класс DefaultHandler , который является базовым классом по умолчанию для обработчиков событий SAX2. Мы предоставляем два обработчика, один для анализа ответов на поиск людей (PersonHandler) и один для анализа ответов на поиск фильмов (MovieHandler). Далее следует код для класса PersonHandler (класс MovieHandler практически одинаков, поэтому для краткости опущен — исходный код этого класса можно найти в доступном проекте Eclipse в конце учебника):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package com.javacodegeeks.android.apps.moviesearchapp.handlers;
 
import java.util.ArrayList;
 
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
 
import com.javacodegeeks.android.apps.moviesearchapp.model.Image;
import com.javacodegeeks.android.apps.moviesearchapp.model.Person;
 
public class PersonHandler extends DefaultHandler {
     
    private StringBuffer buffer = new StringBuffer();
     
    private ArrayList<Person> personList;
    private Person person;
    private ArrayList<Image> personImagesList;
    private Image personImage;
     
    @Override
    public void startElement(String namespaceURI, String localName,
            String qName, Attributes atts) throws SAXException {
         
        buffer.setLength(0);
         
        if (localName.equals("people")) {
            personList = new ArrayList<Person>();
        }
        else if (localName.equals("person")) {
            person = new Person();
        }
        else if (localName.equals("images")) {
            personImagesList = new ArrayList<Image>();
        }
        else if (localName.equals("image")) {
            personImage = new Image();
            personImage.type = atts.getValue("type");
            personImage.url = atts.getValue("url");
            personImage.size = atts.getValue("size");
            personImage.width = Integer.parseInt(atts.getValue("width"));
            personImage.height = Integer.parseInt(atts.getValue("height"));
        }
 
    }
     
    @Override
    public void endElement(String uri, String localName, String qName)throws SAXException {
         
        if (localName.equals("person")) {
            personList.add(person);
        }
        else if (localName.equals("score")) {
            person.score = buffer.toString();
        }
        else if (localName.equals("popularity")) {
            person.popularity = buffer.toString();
        }
        else if (localName.equals("name")) {
            person.name = buffer.toString();
        }
        else if (localName.equals("id")) {
            person.id = buffer.toString();
        }
        else if (localName.equals("biography")) {
            person.biography = buffer.toString();
        }
        else if (localName.equals("url")) {
            person.url = buffer.toString();
        }
        else if (localName.equals("version")) {
            person.version = buffer.toString();
        }
        else if (localName.equals("last_modified_at")) {
            person.lastModifiedAt = buffer.toString();
        }   
        else if (localName.equals("image")) {
            personImagesList.add(personImage);
        }   
        else if (localName.equals("images")) {
            person.imagesList = personImagesList;
        }
         
    }
     
    @Override
    public void characters(char[] ch, int start, int length) {
        buffer.append(ch, start, length);
    }
         
    public ArrayList<Person> retrievePersonList() {
        return personList;
    }
     
}

Используется стандартный подход к синтаксическому анализу SAX (описанный во многих интерактивных руководствах ), поэтому приведенный выше код должен выглядеть знакомым, если вы ранее анализировали XML-документы. Обратите внимание, что вместо параметра qName переменная localName содержит данные элемента.

В нашем классе мы определяем необходимые функции обратного вызова:

  • startElement : вызывается при обнаружении нового элемента. Мы инициализируем соответствующее поле там.
  • endElement : Вызывается, когда достигнут конец элемента. Соответствующее поле заполняется там.
  • символы : Вызывается, когда новый текст был найден внутри элемента. Внутренний буфер заполняется содержимым элемента.

Обратите внимание, что в ответе может быть найдено несколько элементов Person, а внутри каждого из них может быть найдено несколько изображений. В частности, для изображений соответствующая информация находится в атрибутах элементов, а не внутри текстового узла. Таким образом, соответствующий метод getValue используется для извлечения этой информации.

На этом этапе третья часть серии подошла к концу. В этой части мы подготовили инфраструктуру для выполнения XML-анализа ответов API с использованием подхода SAX. В следующих уроках мы будем использовать это для сопоставления ответов с нашими модельными классами. Вы можете скачать здесь созданный проект Eclipse.

Статьи по Теме :