Статьи

Сборка RSS Reader для Android

В этом уроке будет рассказано о создании программы чтения RSS на платформе Android (с упором на 3.0 +, так как она будет использовать фрагменты). Весь код доступен в виде полноценного работающего приложения для Android, которое можно сразу же загрузить, загрузить и запустить на совместимом устройстве / эмуляторе Android. Так что не стесняйтесь  взять это с GitHub,  прежде чем продолжить. 

В наши дни для мобильных приложений нет ничего необычного в том, чтобы иметь возможность использовать RSS-канал с другого сайта (или многих) для агрегирования контента. Или, может быть, вы просто хотите создать свое собственное приложение для Android сейчас, когда Google объявил, что оно будет Уходящий Читатель.

Те из вас, кто работал с RSS на других языках JVM, знают, что существует множество доступных библиотек, которые могут обрабатывать RSS — однако, потому что платформа Android фактически не содержит все основные классы Java, почти все библиотеки RSS. не были поддержаны.

Не бойтесь, так как Java-парсер SAX доступен, поэтому с небольшим количеством кода мы можем получить и запустить собственный парсер RSS в кратчайшие сроки!

В этом пошаговом руководстве будут рассмотрены основы быстрого запуска и запуска приложения для чтения RSS, а также некоторые детали фрагментарной системы Android для оптимизации планшетов, а также некоторые вещи, на которые следует обратить внимание (например, изменения в платформе, которые означают сеть). Операции не могут быть запущены в основном потоке, который требует некоторой настройки, если вы работали с более ранними версиями).

Весь код приложения также доступен на нашем GitHub, так что не стесняйтесь его раскошелиться и попробовать загрузить на свое Android-устройство / эмулятор.

Разбор RSS-канала:


Итак, для начала мы рассмотрим синтаксический анализ канала — если у вас есть опыт синтаксического анализа XML с использованием SAX в Java, вы поймете, насколько это просто.
Все, что нам нужно сделать, это сообщить парсеру, какие XML-узлы нам нужны и что с ними делать.

Если вы никогда ранее не реализовывали парсер SAX, есть три основных метода, которые мы переопределим: 
  • startElement () — он вызывается анализатором каждый раз, когда обнаруживается новый узел XML
  • endElement () — он вызывается анализатором каждый раз, когда закрывается узел XML (например, & lt; / ..)
  • chars () — вызывается, когда между узлами находятся символы
public void startElement(String uri, String localName, String qName, Attributes atts) {
  chars = new StringBuffer();
}

public void characters(char ch[], int start, int length) {
  chars.append(new String(ch, start, length));
}

public void endElement(String uri, String localName, String qName) throws SAXException {
  if (localName.equalsIgnoreCase("title")){
currentArticle.setTitle(chars.toString());
} else if (localName.equalsIgnoreCase("description")){
currentArticle.setDescription(chars.toString());
} else if (localName.equalsIgnoreCase("published")){
currentArticle.setPubDate(chars.toString());
} else if (localName.equalsIgnoreCase("id")){
currentArticle.setGuid(chars.toString());
} else if (localName.equalsIgnoreCase("author")){
currentArticle.setAuthor(chars.toString());
} else if (localName.equalsIgnoreCase("content")){
currentArticle.setEncodedContent(chars.toString());
} else if (localName.equalsIgnoreCase("entry")){
} 

// Check if looking for article, and if article is complete
if (localName.equalsIgnoreCase("entry")) {
articleList.add(currentArticle);
currentArticle = new Article();

// Lets check if we've hit our limit on number of articles
articlesAdded++;
if (articlesAdded >= ARTICLES_LIMIT){
throw new SAXException();
}
  }
}

Поскольку мы действительно заботимся только о получении данных с конечных узлов, наш метод startElement () остается пустым. Элемент chars () необходимо отслеживать, поскольку нет гарантии, когда он будет вызван (например, в таком узле, как hello world, этот метод может вызываться несколько раз между началом и концом), поэтому каждый раз, когда мы просто добавляем содержимое в StringBuffer — таким образом, мы можем быть уверены, что захватили все данные в узле. К тому времени, когда вызывается метод endElement (), мы знаем, что у нас есть содержимое самого узла, и нам просто нужно хранить данные. На этом этапе мы просто быстро добавили POJO с атрибутами, которые мы хотели захватить — строки, с которыми мы сопоставляем, — это имена узлов из RSS-канала ATOM (который использует Blogger) — если вы используете другой канал,просто взгляните на канал и обновите имена узлов соответствующим образом.

Используя наш канал в приложении для Android

Итак, это было легко, верно? После того, как этот синтаксический анализатор пройдёт (и вы сможете использовать этот код автономно в любом java-приложении на самом деле), у вас будет список объектов Java, которые содержат основные сведения о последних публикациях в блоге в фиде (заголовок, автор, дата создания, контент) и т. д.) Итак, теперь давайте рассмотрим использование его в приложении для Android.

Мы будем исходить из базового понимания Android SDK и концепции фрагментов, поэтому не будем возвращаться к основам с этим материалом.

Что мы сделаем, это создадим базовый ListFragment и класс RSSService, который мы будем использовать для заполнения списка. В нашем ListFragment мы просто скажем нашему сервису RSS заполнить список:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    refreshList();
}

private void refreshList(){
  rssService = new RssService(this);
  rssService.execute(BLOG_URL);
}

Просто, правда?

Давайте посмотрим, что делает наш полезный сервис RSS для нас.

public class RssService extends AsyncTask<String, Void, List<Article>> {

  private ProgressDialog progress;
private Context context;
private ArticleListFragment articleListFrag;

public RssService(ArticleListFragment articleListFragment) {
context = articleListFragment.getActivity();
articleListFrag = articleListFragment;
progress = new ProgressDialog(context);
progress.setMessage("Loading...");
}


protected void onPreExecute() {
Log.e("ASYNC", "PRE EXECUTE");
progress.show();
}

Первое, на что следует обратить внимание, это то, что этот класс расширяет Android-AsyncTask. Причина этого в том, что начиная с Android 3.0 вы больше не можете выполнять сетевые операции в главном потоке приложения, поэтому нашему классу придется выбирать некоторые RSS-каналы, которые мы собираемся выделить новую ветку.

Как вы можете видеть, конструктор просто устанавливает некоторую контекстную информацию, которую мы будем использовать позже, а затем строит диалоговое окно прогресса — это затем отображается в методе onPreExecute () — это позволяет нам показывать «загрузочный» вращающийся диск, пока мы выбираем данные.

Основной метод Android AsyncTask, который обрабатывает фактическую работу, которую вы хотите выполнять асинхронно, называется «doInBackground ()» — в нашем случае это просто — мы просто вызываем наш синтаксический анализатор SAX RSS и извлекаем данные нашего канала:

@Override
protected List<Article> doInBackground(String... urls) {
String feed = urls[0];
URL url = null;
try {

SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();

url = new URL(feed);
RssHandler rh = new RssHandler();

xr.setContentHandler(rh);
xr.parse(new InputSource(url.openStream()));

    ...

Наконец, мы переопределим метод «onPostExecute ()» класса async, чтобы использовать наш недавно полученный список для заполнения нашего ListFragment. Вы заметили, что когда мы переопределяли метод doInBackground () ранее, мы устанавливали возврат к списку статей (где Article — это мой простой POJO, содержащий информацию о моем посте в блоге RSS) — это должно соответствовать аргументу метода «onPostExecute ()» , который выглядит так:

protected  void onPostExecute(final List<Article>  articles) {
Log.e("ASYNC", "POST EXECUTE");
articleListFrag.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
for (Article a : articles){
Log.d("DB", "Searching DB for GUID: " + a.getGuid());
DbAdapter dba = new DbAdapter(articleListFrag.getActivity());
dba.openToRead();
Article fetchedArticle = dba.getBlogListing(a.getGuid());
dba.close();
if (fetchedArticle == null){
Log.d("DB", "Found entry for first time: " + a.getTitle());
dba = new DbAdapter(articleListFrag.getActivity());
dba.openToWrite();
dba.insertBlogListing(a.getGuid());
dba.close();
}else{
a.setDbId(fetchedArticle.getDbId());
a.setOffline(fetchedArticle.isOffline());
a.setRead(fetchedArticle.isRead());
}
}
ArticleListAdapter adapter = new ArticleListAdapter(articleListFrag.getActivity(), articles);
articleListFrag.setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
});
progress.dismiss();
}

На самом деле, все, что нам действительно нужно было сделать в этом методе, это передать наш новый List или статьи в ListFragment и уведомить его об изменении, как показано ниже:

ArticleListAdapter adapter = new ArticleListAdapter(articleListFrag.getActivity(), articles);
articleListFrag.setListAdapter(adapter);
adapter.notifyDataSetChanged();

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

Вот и все — есть еще много вещей, которые вы можете добавить в свое приложение для чтения RSS, таких как хранение постов для последующего чтения и поддержка нескольких каналов / типов каналов — но не стесняйтесь  раскошелиться на GitHub или просто загрузить на свое устройство Android чтобы насладиться всеми нашими обновлениями NerdAbility! 

Приложение работает в эмуляторе Andriod
Приложение RSS, запущенное в эмуляторе планшета Android