Статьи

Использовать веб-сервис в Android с помощью intentService

В этом посте я объясню, как использовать Restful Webservice с помощью IntentService . Мы знаем, что можем использовать веб-сервис непосредственно в пользовательском интерфейсе, используя, например, Volley lib или простой Http-клиент , но в некоторых случаях мы предпочитаем использовать веб-сервис в службе Android.

Мы знаем, что у Сервиса Android «жизнь» дольше, чем у Android, и если мы хотим, чтобы задача была выполнена, даже если пользовательский интерфейс приложения больше не доступен, мы должны использовать Сервис . Мы можем рассмотреть, например, сценарий загрузки: предположим, мы хотим загрузить некоторую информацию с удаленного сервера; мы можем сделать это в пользовательском интерфейсе (то есть в Activity), но если пользователь, например, переместится в другое приложение, загрузка будет прервана. Если мы хотим быть уверены, что загрузка продолжается, даже если приложение больше не активно, мы можем использовать Сервис.

Описание IntentService

Когда мы хотим использовать веб-сервис, мы должны знать, что время, необходимое для обращения к серверу и получения ответа, может быть довольно долгим. Даже если мы выполняем эту логику как отдельную службу, сама служба запускается в основном потоке, так что вызывающая активность может запаздывать, ожидая ответа от службы, и ОС может уничтожить ее, показывая раздражающее сообщение ANR. Мы можем избежать этой проблемы, обрабатывая отдельный поток внутри службы, но Android SDK предоставляет специализированный класс, производный от класса Service, который называется IntentService, определяемый как (из документов API Android):

«IntentService — это базовый класс для Service которые обрабатывают асинхронные запросы (выраженные как Intent ) по требованию. Клиенты отправляют запросы через вызовы startService(Intent) ; служба запускается по мере необходимости, обрабатывает каждое намерение поочередно, используя рабочий поток, и останавливает себя, когда у него заканчивается работа. Этот шаблон «процессор рабочей очереди» обычно используется для выгрузки задач из основного потока приложения ».

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

Создание IntentService

В качестве примера мы будем использовать Yahoo! Финансовая служба, чтобы получить текущее значение Ask и Bid для Акции. В конце мы создадим приложение, подобное этому:

android_webservice_intentservice [4]

Первый шаг — создание сервиса:

1
2
3
4
5
6
7
public class QuoteService extends IntentService {
 
    @Override
    protected void onHandleIntent(Intent intent) {
         // Here we do the heavy work
    }
}

В строке 4 в onHandleIntent мы выполняем «тяжелую работу», поэтому мы вызываем удаленный Web-сервис и анализируем ответ. В этом случае мы можем просто использовать XmlPullParser для анализа данных:

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
// Here we retrieve the stock quote using Yahoo! Finance
Log.d("Srv", "Get stock");
String url = YAHOO_FINANCE_URL.replaceAll("#sym#", stock.getSymbol());
try {
    HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
    con.setRequestMethod("GET");
    con.connect();
    InputStream is = con.getInputStream();
 
    // Start parsing XML
    XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
    parser.setInput(is, null);
    int event = parser.getEventType();
    String tagName = null;
    String currentTag = null;
    Stock stockResult = new Stock();
 
    while (event != XmlPullParser.END_DOCUMENT) {
        tagName = parser.getName();
        Log.d("Srv", "Tag:" + tagName);
        if (event == XmlPullParser.START_TAG) {
            currentTag = tagName;
        }
        else if (event == XmlPullParser.TEXT) {
            if ("ASK".equalsIgnoreCase(currentTag))
                //
            else if    ("BID".equalsIgnoreCase(currentTag)) {                       
                //
            }
        }
 
        event = parser.next();
 
    }
 
} catch (Exception e) {
    e.printStackTrace();
}

Затем мы определяем наш сервис в Manifest.xml:

1
<service android:name=".QuoteService" />

Деятельность: клиент Webservice

Следующим шагом является создание клиентского действия, которое вызывает службу. Мы используем IntentService, затем вызывающая активность вызывает службу и затем забывает об этом. Активность довольно тривиальна, потому что, как только пользователь нажимает «Получить цитату!» кнопку мы вызываем сервис, используя:

1
startService(service_intent);

В любом случае, мы должны рассмотреть два аспекта:

  1. Как мы передаем данные в сервис
  2. Как мы получаем данные от сервиса и отображаем информацию в пользовательском интерфейсе

Чтобы передать данные в сервис, мы можем создать простой класс Java, который содержит символ акции и значения, которые мы хотим заполнить сервисом при получении ответа. Наш класс должен быть классом Parcelable :

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
public class Stock implements Parcelable {
 
    private String symbol;
    private double askValue;
    private double bidValue;
 
    public Stock() {}
 
    public Stock(String symbol, double askValue, double bidValue) {
        this.symbol = symbol;
        this.askValue = askValue;
        this.bidValue = bidValue;
    }
 
    // get and set method
 
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // We write the stock information in the parcel
        dest.writeString(symbol);
        dest.writeDouble(askValue);
        dest.writeDouble(bidValue);
    }
 
    public static final Creator<Stock> CREATOR = new Creator<Stock>() {
 
        @Override
        public Stock createFromParcel(Parcel source) {
            Stock stock = new Stock();
            stock.setSymbol(source.readString());
            stock.setAskValue(source.readDouble());
            stock.setBidValue(source.readDouble());
            return stock;
        }
 
        @Override
        public Stock[] newArray(int size) {
            return new Stock[0];
        }
    };
 
    @Override
    public int describeContents() {
        return 0;
    }
}

В строках 18 и 25 мы реализуем два метода, необходимые для маршалинга и демаршализации нашего класса.

Получить данные из сервиса с помощью ResultReceiver

Еще один интересный аспект — как мы можем получить значения обратно из IntentService. Поскольку мы уже знаем, что вызывающая деятельность не ждет результата, поэтому мы должны найти другой путь. Вообще говоря, есть разные способы решения этой проблемы, в этом посте я объясню, как использовать ResultReceiver . Этот метод довольно прост и элегантен, на мой взгляд.

Первым шагом является создание класса, расширяющего ResultReceiver, который мы называем QuoteServiceReceiver :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class QuoteServiceReceiver extends ResultReceiver {
 
    private Listener listener;
 
    public QuoteServiceReceiver(Handler handler) {
        super(handler);       
    }
 
    public void setListener(Listener listener) {
        this.listener = listener;
    }
 
    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (listener != null)
            listener.onReceiveResult(resultCode, resultData);
    }
 
    public static interface Listener {
        void onReceiveResult(int resultCode, Bundle resultData);
    }
 
}

Этот класс очень прост. В строке 19 мы определяем интерфейс, который должен реализовать вызывающий оператор, чтобы получать уведомления, когда данные доступны. В этом интерфейсе мы определяем Bundle, который содержит полученные данные. Затем в строке 14 мы переопределяем onReceiveResult и вызываем наш метод обратного вызова, определенный в предыдущем интерфейсе.

С клиентской точки зрения (то есть нашей Деятельности) нам просто нужно реализовать определенный интерфейс и передать QuoteServiceReceiver IntentService:

1
2
3
4
5
6
7
8
private Intent createCallingIntent(Stock stock) {
    Intent i = new Intent(this, QuoteService.class);
    QuoteServiceReceiver receiver = new QuoteServiceReceiver(new Handler());
    receiver.setListener(this);       
    i.putExtra("rec", receiver);
    i.putExtra("stock", stock);
    return i;
}

и когда мы вызываем службу:

1
startService(createCallingIntent(stock));

..и в методе обратного вызова:

1
2
3
4
5
6
7
8
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
    Stock stock = resultData.getParcelable("stock");
    Log.d("Srv", "Stock ["+stock+"]");
 
    askValue.setText("" + stock.getAskValue());
    bidValue.setText("" + stock.getBidValue());
}

… и здесь мы обновляем интерфейс с полученными данными.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Override
protected void onHandleIntent(Intent intent) {
 
    Stock stock = intent.getParcelableExtra("stock");
    final ResultReceiver rec = (ResultReceiver) intent.getParcelableExtra("rec");
    ....
    // When data is ready
   if    ("BID".equalsIgnoreCase(currentTag)) {                       
          stockResult.setBidValue(Double.parseDouble(parser.getText()));
          Bundle b = new Bundle();
          b.putParcelable("stock", stockResult);
          rec.send(0, b);
    }
    ....
}
  • Доступен исходный код @ github
Ссылка: Используйте Webservice в Android с помощью intentService от нашего партнера JCG Франческо Аццолы в блоге Surviving w / Android .