13.4. IntentService
13.4.IntentService
我们已对系统服务的工作方式有所了解,接下来可以利用系统服务的有关特性,简化UpdaterService的实现。回忆下前面的内容:UpdaterService会一直运行,定期从服务端抓取Timeline的更新。由于Service默认与UI在同一个线程(或者说,都在UI线程中执行)中执行,为避免网络操作造成界面的响应不灵,我们需要新建一个Updater线程,并将UpdaterService放在里面独立执行。然后在Service的onCreate()与onStartCommand()中让线程开始,并一直执行下去,直到onDestroy()为止。另外,UpdaterService会在两次更新之间休眠一段时间。这是第八章 Service中的内容,但在这里,我们将介绍更简便的实现方法。
IntentService是Service的一个子类,也是通过startService()发出的intent激活。与一般Service的不同在于,它默认在一个独立的工人线程(Worker Thread)中执行,因此不会阻塞UI线程。另一点,它一旦执行完毕,生命周期也就结束了。不过只要接到startService()发出的Intent,它就会新建另一个生命周期,从而实现重新执行。在此,我们可以利用Alarm服务实现定期执行。
同一般的Service不同,我们不需要覆盖onCreate()、onStartCommand()、onDestroy()及onBind(),而是覆盖一个onHandleIntent()方法。网络操作的相关代码就放在这里。另外IntentService要求我们实现一个构造函数,这也是与一般Service不同的地方。
简言之,除了创建一个独立线程使用普通的Service之外,我们可以使用IntentService在它自己的工人线程中实现同样的功能,而且更简单。到现在剩下的只是定期唤醒它,这就引出了AlarmManager——另一个系统服务。
以上的原理就在于,IntentService的onHandleIntent()会在独立的线程中执行。
例 13.9. UpdaterService.java,基于IntentService
package com.marakana.yamba8;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class UpdaterService1 extends IntentService { //
private static final String TAG = "UpdaterService";
public static final String NEW_STATUS_INTENT = "com.marakana.yamba.NEW_STATUS";
public static final String NEW_STATUS_EXTRA_COUNT = "NEW_STATUS_EXTRA_COUNT";
public static final String RECEIVE_TIMELINE_NOTIFICATIONS = "com.marakana.yamba.RECEIVE_TIMELINE_NOTIFICATIONS";
public UpdaterService1() { //
super(TAG);
Log.d(TAG, "UpdaterService constructed");
}
@Override
protected void onHandleIntent(Intent inIntent) { //
Intent intent;
Log.d(TAG, "onHandleIntent'ing");
YambaApplication yamba = (YambaApplication) getApplication();
int newUpdates = yamba.fetchStatusUpdates();
if (newUpdates > 0) { //
Log.d(TAG, "We have a new status");
intent = new Intent(NEW_STATUS_INTENT);
intent.putExtra(NEW_STATUS_EXTRA_COUNT, newUpdates);
sendBroadcast(intent, RECEIVE_TIMELINE_NOTIFICATIONS);
}
}
}
- 继承自IntentService,而非Service。
- 这里需要一个构造函数。在这里,可以给Service一个命名,以便于在观测工具(比如TraceView)中,辨认相应的线程。
- 这是关键的方法。其中的代码会在独立的工人线程中执行,因此不会影响到主UI线程的响应。
- 下面将所做的变化广播出去,具体可见第十一章中的"广播 Intent"一节。
这样,我们重构了原先的Service。要测试它,不妨先把Start/Stop Service的按钮改为Refresh按钮。为此需要修改menu.xml添加这个新条目,然后在BaseActivity类中修改它的处理方法。
例 13.10. res/xml/menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
...
<item android:title="@string/titleRefresh" android:id="@+id/itemRefresh"
android:icon="@android:drawable/ic_menu_rotate"></item>
</menu>
把itemToggle改为itemRefresh,这样更合适一些。同时必须更新string.xml中对应的条目。
接下来修改BaseActivity.java文件,添加这个按钮的处理方法,也就是修改onOptionsItemSelected()中对应的case语句。同时onMenuOpened()已经没有用处了,删掉即可。
例 13.11. BaseActivity.java,添加Refresh按钮之后
public class BaseActivity extends Activity {
...
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
...
case R.id.itemRefresh:
startService(new Intent(this, UpdaterService.class)); //
break;
...
}
return true;
}
...
}
- 简单地发送一个Intent,启动UpdaterService。
这样选项菜单就多了一个Refresh按钮,可以启动一个Service让它在后台更新消息。测试的话,点击这个按钮就好。
实现同样的功能还有一个选择,那就是AsyncTask。在设计角度讲,使用AsyncTask甚至可能比IntentService更加合适——它使得这些功能在UI层面即可全部实现,这在第六章中的Android的线程机制已有提及。不过在这里,我们更希望了解IntentService的工作方式,通过演示不难看出,它的功能并不弱于普通的Service。
接下来需要做的,就是定时触发UpdaterService。因此引入AlarmManager。