Статьи

Создание клиента Twitter для Android: получение обновлений с помощью службы

В этой серии мы создаем клиент Twitter для платформы Android с использованием библиотеки Twitter4J. В этом руководстве основное внимание будет уделено внедрению Сервиса для непрерывной загрузки новых твитов на домашнюю временную шкалу пользователя. Мы также будем использовать Broadcast Receiver для обновления интерфейса приложения, когда новые твиты станут доступны для отображения.


  1. Создание клиента Twitter для Android: настройка и обзор
  2. Создание клиента Twitter для Android: создание интерфейса
  3. Создание клиента Twitter для Android: создание базы данных временной шкалы
  4. Создание клиента Twitter для Android: получение обновлений с помощью службы
  5. Создание клиента Twitter для Android: твиты, ретвиты и ответы

Чтобы твиты временной шкалы пользователя автоматически отображались по мере их появления, мы будем использовать Службу для их получения с заданными интервалами. Эта служба будет работать с выбранной вами частотой, записывать в базу данных и отправлять трансляции, когда новые твиты будут найдены и сохранены. Основной класс Activity приложения будет получать широковещательные сообщения и запрашивать базу данных, обновляя интерфейс временной шкалы для отображения новых данных.

Создайте новый класс в своем проекте, назвав его «TimelineService» и изменив строку объявления начального класса следующим образом:

1
public class TimelineService extends Service {

Вам понадобится следующий импорт:

1
import android.app.Service;

Добавьте следующие переменные экземпляра в ваш класс для взаимодействия с Twitter:

1
2
3
4
5
6
    /**twitter authentication key*/
public final static String TWIT_KEY = «your key»;
    /**twitter secret*/
public final static String TWIT_SECRET = «your secret»;
    /**twitter object*/
private Twitter timelineTwitter;

Измените ключевые и секретные переменные, чтобы они соответствовали вашим собственным, как они использовались в предыдущих руководствах. Добавьте следующие переменные для обработки базы данных:

1
2
3
4
    /**database helper object*/
private NiceDataHelper niceHelper;
    /**timeline database*/
private SQLiteDatabase niceDB;

Наконец, добавьте следующее для общего использования в классе:

1
2
3
4
5
6
7
8
9
    /**shared preferences for user details*/
private SharedPreferences nicePrefs;
    /**handler for updater*/
private Handler niceHandler;
    /**delay between fetching new tweets*/
private static int mins = 5;//alter to suit
private static final long FETCH_DELAY = mins * (60*1000);
    //debugging tag
private String LOG_TAG = «TimelineService»;

Объект Handler предназначен для планирования обновлений на заданных частотах. Переменная «mins» и константа «FETCH_DELAY» позволяют вам установить частоту, с которой приложение будет получать новые обновления с домашней шкалы времени пользователя. Измените переменную «mins», чтобы отразить, сколько минут приложение должно ждать между обновлениями. Обновления будут загружаться только с этой частотой во время работы приложения. Когда пользователь выходит из приложения и оно уничтожается, оно останавливает непрерывную работу Службы. В следующий раз, когда пользователь запустит приложение, оно получит новые обновления, перезапустив Службу.

Вверху вашего файла класса добавьте следующий импорт для библиотеки Twitter4J:

1
2
3
4
5
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;

Со следующим списком для ресурсов Android:

1
2
3
4
5
6
7
8
import android.app.Service;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

И, наконец, следующее для класса Java List:

1
import java.util.List;

Чтобы реализовать наш класс Service, мы собираемся предоставить методы «onCreate» и «onStartCommand», когда создается экземпляр класса и запускается служба, метод «onDestroy» для его завершения и метод «onBind», который требуется хотя мы не будем его использовать. Мы также собираемся создать внутренний класс, в который мы будем получать обновления из Twitter через заданные интервалы.

Во-первых, давайте реализуем метод «onCreate»:

1
2
3
4
5
@Override
public void onCreate() {
    super.onCreate();
    //setup the class
}

Внутри метода мы создадим несколько переменных нашего экземпляра:

1
2
3
4
5
6
    //get prefs
nicePrefs = getSharedPreferences(«TwitNicePrefs», 0);
    //get database helper
niceHelper = new NiceDataHelper(this);
    //get the database
niceDB = niceHelper.getWritableDatabase();

Давайте также создадим экземпляр объекта Twitter4J, чтобы мы могли получать твиты:

01
02
03
04
05
06
07
08
09
10
11
12
13
    //get user preferences
String userToken = nicePrefs.getString(«user_token», null);
String userSecret = nicePrefs.getString(«user_secret», null);
  
    //create new configuration
Configuration twitConf = new ConfigurationBuilder()
    .setOAuthConsumerKey(TWIT_KEY)
    .setOAuthConsumerSecret(TWIT_SECRET)
    .setOAuthAccessToken(userToken)
    .setOAuthAccessTokenSecret(userSecret)
    .build();
    //instantiate new twitter
timelineTwitter = new TwitterFactory(twitConf).getInstance();

Нам нужно реализовать метод onBind, хотя мы на самом деле его не используем, поэтому добавьте его следующим образом:

1
2
3
4
@Override
public IBinder onBind(Intent intent) {
    return null;
}

Когда служба запускается, выполняется метод onStartCommand, а когда он уничтожается, вызывается метод onDestroy. Скоро мы реализуем оба этих метода, но сначала мы создадим внутренний класс для обработки извлечения обновлений через фиксированные промежутки времени. В объявлении класса «TimelineService» добавьте следующую схему класса:

1
2
3
4
5
6
7
/**
 * TimelineUpdater class implements the runnable interface
 */
class TimelineUpdater implements Runnable {
    //fetch updates
      
}

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

1
2
3
4
//run method
public void run() {
          
}

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

1
2
    //check for updates — assume none
boolean statusChanges = false;

Далее мы попытаемся извлечь данные из Интернета, поэтому нам нужно попробовать и перехватить блоки:

1
2
3
4
try {
    //fetch timeline
}
catch (Exception te) { Log.e(LOG_TAG, «Exception: » + te);

Внутри блока try извлеките временную шкалу пользователя в виде объекта List:

1
2
    //retrieve the new home timeline tweets as a list
List<Status> homeTimeline = timelineTwitter.getHomeTimeline();

Полученная временная шкала представляет собой список объектов состояния. Каждый объект Status содержит данные для одного обновления твита. Теперь нам нужно перебрать новые твиты и вставить их в базу данных:

01
02
03
04
05
06
07
08
09
10
//iterate through new status updates
for (Status statusUpdate : homeTimeline)
{
        //call the getValues method of the data helper class, passing the new updates
    ContentValues timelineValues = NiceDataHelper.getValues(statusUpdate);
        //if the database already contains the updates they will not be inserted
    niceDB.insertOrThrow(«home», null, timelineValues);
        //confirm we have new updates
    statusChanges = true;
}

Обратите внимание, что мы вызываем метод «getValues» класса NiceDataHelper, который является статическим. Метод «getValues» берет объекты статуса Twitter и извлекает соответствующие данные для нашей базы данных, т. Е. Идентификатор твита, текст, имя экрана, время и URL-адрес изображения профиля, которые содержатся в каждом экземпляре Status. Метод возвращает их как наборы значений, которые можно вставить в базу данных, что мы и делаем здесь. Поскольку появляются новые твиты, мы устанавливаем флаг statusChanges в значение true.

После блока catch мы отправляем широковещательную рассылку в основное действие, только если появятся новые твиты:

1
2
3
4
5
6
    //if we have new updates, send a Broadcast
if (statusChanges)
{
        //this should be received in the main timeline class
    sendBroadcast(new Intent(«TWITTER_UPDATES»));
}

Мы обработаем получение этого в основном классе Activity позже. Наконец, после этого оператора if, который все еще находится внутри метода «run», поручите Android повторно вызвать метод «run» после выбранной задержки:

1
2
    //delay fetching new updates
niceHandler.postDelayed(this, FETCH_DELAY);

В верхней части вашего класса TimelineService добавьте еще одну переменную экземпляра для этого нового класса:

1
2
/**updater thread object*/
private TimelineUpdater niceUpdater;

Теперь мы можем обработать метод «TimelineService», когда служба запускается. Вернувшись в класс Service (за пределами нового класса Runnable), добавьте метод onStartCommand:

01
02
03
04
05
06
07
08
09
10
11
12
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStart(intent, startId);
        //get handler
    niceHandler = new Handler();
        //create an instance of the updater class
    niceUpdater = new TimelineUpdater();
        //add to run queue
    niceHandler.post(niceUpdater);
        //return sticky
    return START_STICKY;
}

Здесь мы вызываем метод суперкласса и возвращаем стандартное целочисленное значение. Мы также создаем экземпляр Handler и новый класс Runnable. Обработчик добавляет Runnable в очередь процессов, чтобы выполнялся его метод «run».

Теперь все, что нам нужно для завершения класса Service, — это реализовать метод destroy:

1
2
3
4
5
6
7
@Override
public void onDestroy() {
    super.onDestroy();
        //stop the updating
    niceHandler.removeCallbacks(niceUpdater);
    niceDB.close();
}

Вернувшись в основной класс Activity приложения, теперь мы можем запустить Службу и получать полученные широковещательные сообщения. Добавьте следующую переменную экземпляра вверху класса «TwitNiceActivity»:

1
2
/**Broadcast receiver for when new updates are available*/
private BroadcastReceiver niceStatusReceiver;

Вам понадобится следующий оператор импорта:

1
import android.content.BroadcastReceiver;

Добавьте новый внутренний класс в ваш основной класс Activity для получения Broadcasts, убедившись, что вы добавляете его вне любого из методов, но все еще внутри объявления класса Activity:

1
2
3
4
5
6
/**
 * Class to implement Broadcast receipt for new updates
 */
class TwitterUpdateReceiver extends BroadcastReceiver {
  
}

Этот класс собирается сделать одну вещь: получать широковещательные рассылки — поэтому реализуйте внутри него метод onReceive:

1
2
3
4
@Override
public void onReceive(Context context, Intent intent) {
  
}

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

1
2
3
4
5
6
7
int rowLimit = 100;
if(DatabaseUtils.queryNumEntries(timelineDB, «home»)>rowLimit) {
    String deleteQuery = «DELETE FROM home WHERE «+BaseColumns._ID+» NOT IN » +
        «(SELECT «+BaseColumns._ID+» FROM home ORDER BY «+»update_time DESC » +
        «limit «+rowLimit+»)»;
    timelineDB.execSQL(deleteQuery);
}

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

Добавьте следующий класс в класс:

1
2
3
import android.provider.BaseColumns;
import android.database.DatabaseUtils;
import android.content.Context;

Теперь нам нужно запросить базу данных и обновить пользовательский интерфейс Views с помощью адаптера:

1
2
3
4
timelineCursor = timelineDB.query(«home», null, null, null, null, null, «update_time DESC»);
startManagingCursor(timelineCursor);
timelineAdapter = new UpdateAdapter(context, timelineCursor);
homeTimeline.setAdapter(timelineAdapter);

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


Давайте завершим метод «setupTimeline». После строки, в которой вы создали экземпляр класса UpdateAdapter в последний раз, до окончания блока try добавьте следующее, чтобы установить Adapter на временную шкалу:

1
2
    //this will make the app populate the new update data in the timeline view
homeTimeline.setAdapter(timelineAdapter);

Затем создайте экземпляр вашего класса Broadcast Receiver и зарегистрируйте его для получения обновлений от класса Service:

1
2
3
4
    //instantiate receiver class for finding out when new updates are available
niceStatusReceiver = new TwitterUpdateReceiver();
    //register for updates
registerReceiver(niceStatusReceiver, new IntentFilter(«TWITTER_UPDATES»));

Обратите внимание, что строка, которую мы предоставляем конструктору IntentFilter, здесь соответствует строке, которую мы используем при отправке широковещательной рассылки в классе Runline TimelineService.

Вам понадобится следующий импорт:

1
import android.content.IntentFilter;

Наконец, запустите Сервис, передав имя класса:

1
2
    //start the Service for updates now
this.getApplicationContext().startService(new Intent(this.getApplicationContext(), TimelineService.class));

Прежде чем мы закончим, давайте реализуем метод destroy для основного класса Activity, поскольку он касается объектов, которые мы здесь использовали:

01
02
03
04
05
06
07
08
09
10
11
12
13
@Override
public void onDestroy() {
    super.onDestroy();
    try {
            //stop the updater Service
        stopService(new Intent(this, TimelineService.class));
            //remove receiver register
        unregisterReceiver(niceStatusReceiver);
            //close the database
        timelineDB.close();
    }
    catch(Exception se) { Log.e(LOG_TAG, «unable to stop Service or receiver»);
}

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


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