Логотип

Первый раз я запустил Eclipse еще весной, почитал книжки на английском, поставил SDK, немного поигрался и забросил. В начале зимы я купил себе первый смартфон на базе Android, но вновь вернутся к разработке меня подтолкнул недавний пост, в котором говорилось, что можно обойтись и знанием C#, с которым в отличии от Java я знаком. Мне было достаточно одного вечера, чтобы понять, что за связку Visual Studio и Monodroid я больше не сяду, позже я прочитал этот пост, где полностью согласился с автором.

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

Большинство разработчиков, получают html код страницы и перегоняют его в xml, что является неправильным подходом, так как html является «правильным» xml не всегда, вроде на хабре писали, что для браузера не обязателен тег html (современный браузер и без него должен отобразить страницу) или просто будут ошибки, тогда на помощь приходят библиотеки. Из них я выбрал HtmlCleaner.

Под катом я расскажу, как подключить эту библиотеку, а также напишем простой парсер stackoverflow.com.

Рассказывать как установить Android SDK, Eclipse и ADT Plugin я не буду, если эти слова Вам ничего не говорят, то посетите эти две ссылки:
Installing the SDK
ADT Plugin for Eclipse

Главная страница stackoverflow.com выглядит следующим образом:

Главная страница

Парсить я буду информацию, выделенную красными прямоугольниками.

Всё рассчитано на новичков, поэтому будет много картинок. На данном этапе у Вас должен быть полностью настроенный Eclipse, для создания проекта нажимаем File -> New -> Project… и выбираем Android Project, после чего заполним форму:

Новый проект

Пишу для своего устройства, поэтому выбрал версию 2.2, второй важный параметр — это package name, к��торый должен быть уникальным, принято, что это имя сайта наоборот, плюс название приложения. Тесты создавать не будет, поэтому смело нажимаем Finish. Создался проект, рекомендую Вам изучить какие файлы и где лежат, но по своему опыту скажу, что сразу я малость испугался, того количества файлов, которое появилось при первом запуске Eclipse.

Приступим к редактированию файла res\layout\main.xml, тут я удалю TextView и добавлю два элемента управления: Button и ListView, изменю идентификаторы, для кнопки установлю android:layout_width=«fill_parent» и android:text=«Получить данные». Готовый результат выглядит таким образом:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <Button android:id="@+id/parse"
   android:layout_height="wrap_content"
   android:layout_width="fill_parent"
    android:text="Получить данные">
  </Button>
  <ListView android:id="@+id/listViewData"
   android:layout_height="wrap_content"
   android:layout_width="match_parent">
  </ListView>
</LinearLayout>


* This source code was highlighted with Source Code Highlighter.


Это простейший интерфейс, в случае, если Вы сделаете приложение и решите опубликовать его в маркете, то обязательно его нужно изменить, поставить тот же фон через android:background="@drawable/Имя_файла_без_расширения" и т.д.

Для парсинга нам понадобиться скачать библиотеку htmlcleaner-2.2.jar, далее её следует подключить добавив в Build Paths. Хороший мануал как это сделать можно найти тут, если у Вас появились какие-то трудности.

Прежде всего нужно указать, что нашему приложению нужен интернет, иначе у Вас ничего не выйдет, добавим в файл AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

Теперь создадим класс HtmlHelper, который будет делать основную работу:

public class HtmlHelper {
  TagNode rootNode;

  //Конструктор
  public HtmlHelper(URL htmlPage) throws IOException
  {
    //Создаём объект HtmlCleaner
    HtmlCleaner cleaner = new HtmlCleaner();
    //Загружаем html код сайта
    rootNode = cleaner.clean(htmlPage);
  }

  List<TagNode> getLinksByClass(String CSSClassname)
  {
    List<TagNode> linkList = new ArrayList<TagNode>();

    //Выбираем все ссылки
    TagNode linkElements[] = rootNode.getElementsByName("a", true);
    for (int i = 0; linkElements != null && i < linkElements.length; i++)
    {
      //получаем атрибут по имени
      String classType = linkElements[i].getAttributeByName("class");
      //если атрибут есть и он эквивалентен искомому, то добавляем в список
      if (classType != null && classType.equals(CSSClassname))
      {
        linkList.add(linkElements[i]);
      }
    }

    return linkList;
  }
}



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

public class StackParser extends Activity {
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    //Находим кнопку
    Button button = (Button)findViewById(R.id.parse);
    //Регистрируем onClick слушателя
    button.setOnClickListener(myListener);
  }
  
  //Диалог ожидания
  private ProgressDialog pd;
  //Слушатель OnClickListener для нашей кнопки
  private OnClickListener myListener = new OnClickListener() {
    public void onClick(View v) {
      //Показываем диалог ожидания
      pd = ProgressDialog.show(StackParser.this, "Working...", "request to server", true, false);
      //Запускаем парсинг
      new ParseSite().execute("http://www.stackoverflow.com");
    }
  };
  
  private class ParseSite extends AsyncTask<String, Void, List<String>> {
    //Фоновая операция
    protected List<String> doInBackground(String... arg) {
      List<String> output = new ArrayList<String>();
      try
      {
        HtmlHelper hh = new HtmlHelper(new URL(arg[0]));
        List<TagNode> links = hh.getLinksByClass("question-hyperlink");

        for (Iterator<TagNode> iterator = links.iterator(); iterator.hasNext();)
        {
          TagNode divElement = (TagNode) iterator.next();
          output.add(divElement.getText().toString());
        }
      }
      catch(Exception e)
      {
        e.printStackTrace();
      }
      return output;
    }

    //Событие по окончанию парсинга
    protected void onPostExecute(List<String> output) {
      //Убираем диалог загрузки
      pd.dismiss();
      //Находим ListView
      ListView listview = (ListView) findViewById(R.id.listViewData);
      //Загружаем в него результат работы doInBackground
      listview.setAdapter(new ArrayAdapter<String>(StackParser.this,
          android.R.layout.simple_list_item_1 , output));
    }
  }
}



Если Вы всё делали со мной, то у Вас должна была получится, следующая иерархия файлов:

Файлы

А после запуска приложение должно выглядеть следующим образом:

cкриншоты приложения

Ссылка на скачивание: приложение

Вывод


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