13.4. IntentService

优质
小牛编辑
125浏览
2023-12-01

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);

}

}

}

  1. 继承自IntentService,而非Service。
  2. 这里需要一个构造函数。在这里,可以给Service一个命名,以便于在观测工具(比如TraceView)中,辨认相应的线程。
  3. 这是关键的方法。其中的代码会在独立的工人线程中执行,因此不会影响到主UI线程的响应。
  4. 下面将所做的变化广播出去,具体可见第十一章中的"广播 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;

}

...

}

  1. 简单地发送一个Intent,启动UpdaterService。

这样选项菜单就多了一个Refresh按钮,可以启动一个Service让它在后台更新消息。测试的话,点击这个按钮就好。

实现同样的功能还有一个选择,那就是AsyncTask。在设计角度讲,使用AsyncTask甚至可能比IntentService更加合适——它使得这些功能在UI层面即可全部实现,这在第六章中的Android的线程机制已有提及。不过在这里,我们更希望了解IntentService的工作方式,通过演示不难看出,它的功能并不弱于普通的Service。

接下来需要做的,就是定时触发UpdaterService。因此引入AlarmManager。