前言
在日常开发中除非我们特别为某个操作指定特定的线程,否则大部分在前台UI界面上的操作任务都执行在一个叫做UI Thread的特殊线程中。这可能存在某些隐患,因为部分在UI界面上的耗时操作可能会影响界面的响应性能。UI界面的性能问题会容易惹恼用户,甚至可能导致系统ANR错误。为了避免这样的问题,Android Framework提供了几个类,用来帮助你把那些耗时操作移动到后台线程中执行。那些类中最常用的就是IntentService.
IntentService的局限性:
不可以直接和UI做交互。为了把他执行的结果体现在UI上,需要把结果返回给Activity。
工作任务队列是顺序执行的,如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。
正在执行的任务无法打断。
1)创建IntentService
为你的app创建一个IntentService组件,需要自定义一个新的类,它继承自IntentService,并重写onHandleIntent()方法,如下所示:
public class RSSPullService extends IntentService {
@Override
protected void onHandleIntent(Intent workIntent) {
// Gets data from the incoming Intent
String dataString = workIntent.getDataString();
…
// Do work here, based on the contents of dataString
…
}
}
注意一个普通Service组件的其他回调,例如onStartCommand()会被IntentService自动调用。在IntentService中,要避免重写那些回调。
2)在Manifest文件中定义IntentService
IntentService需要在manifest文件添加相应的条目,将此条目作为元素的子元素下进行定义,如下所示:
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
...
<!--
Because android:exported is set to "false",
the service is only available to this app.
-->
<service
android:name=".RSSPullService"
android:exported="false"/>
...
<application/>
android:name属性指明了IntentService的名字。
注意标签并没有包含任何intent filter。因为发送任务给IntentService的Activity需要使用显式Intent,所以不需要filter。这也意味着只有在同一个app或者其他使用同一个UserID的组件才能够访问到这个Service。
至此,你已经有了一个基本的IntentService类,你可以通过构造Intent对象向它发送操作请求。构造这些对象以及发送它们到你的IntentService的方式
3)创建任务请求并发送到IntentService
为了创建一个任务请求并发送到IntentService。需要先创建一个显式Intent,并将请求数据添加到intent中,然后通过调用 startService() 方法把任务请求数据发送到IntentService。
下面的是代码示例:
创建一个新的显式Intent用来启动IntentService。
/*
* Creates a new Intent to start the RSSPullService
* IntentService. Passes a URI in the
* Intent’s “data” field.
*/
mServiceIntent = new Intent(getActivity(), RSSPullService.class);
mServiceIntent.setData(Uri.parse(dataUrl));
执行startService()
// Starts the IntentService
getActivity().startService(mServiceIntent);
注意可以在Activity或者Fragment的任何位置发送任务请求。例如,如果你先获取用户输入,您可以从响应按钮单击或类似手势的回调方法里面发送任务请求。
一旦执行了startService(),IntentService在自己本身的onHandleIntent()方法里面开始执行这个任务,任务结束之后,会自动停止这个Service。
下一步是如何把工作任务的执行结果返回给发送任务的Activity或者Fragment。
4)利用IntentService 发送任务状态
为了在IntentService中向其他组件发送任务状态,首先创建一个Intent并在data字段中包含需要传递的信息。作为一个可选项,还可以给这个Intent添加一个action与data URI。
下一步,通过执行LocalBroadcastManager.sendBroadcast() 来发送Intent。Intent被发送到任何有注册接受它的组件中。为了获取到LocalBroadcastManager的实例,可以执行getInstance()。代码示例如下:
public final class Constants {
…
// Defines a custom Intent action
public static final String BROADCAST_ACTION =
“com.example.android.threadsample.BROADCAST”;
…
// Defines the key for the status “extra” in an Intent
public static final String EXTENDED_DATA_STATUS =
“com.example.android.threadsample.STATUS”;
…
}
public class RSSPullService extends IntentService {
…
/*
* Creates a new Intent containing a Uri object
* BROADCAST_ACTION is a custom Intent action
*/
Intent localIntent =
new Intent(Constants.BROADCAST_ACTION)
// Puts the status into the Intent
.putExtra(Constants.EXTENDED_DATA_STATUS, status);
// Broadcasts the Intent to receivers in this app.
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
…
}
下一步是在发送任务的组件中接收发送出来的broadcast数据。
5)接收来自IntentService的状态广播
为了接受广播的数据对象,需要使用BroadcastReceiver的子类并实现BroadcastReceiver.onReceive() 的方法,这里可以接收LocalBroadcastManager发出的广播数据。
// Broadcast receiver for receiving status updates from the IntentService
private class ResponseReceiver extends BroadcastReceiver
{
// Prevents instantiation
private DownloadStateReceiver() {
}
// Called when the BroadcastReceiver gets an Intent it’s registered to receive
@
public void onReceive(Context context, Intent intent) {
…
/*
* Handle Intents here.
*/
…
}
}
一旦定义了BroadcastReceiver,也应该定义actions,categories与data用过滤广播。为了实现这些,需要使用IntentFilter。如下所示:
// Class that displays photos
public class DisplayActivity extends FragmentActivity {
…
public void onCreate(Bundle stateBundle) {
…
super.onCreate(stateBundle);
…
// The filter’s action is BROADCAST_ACTION
IntentFilter mStatusIntentFilter = new IntentFilter(
Constants.BROADCAST_ACTION);
// Adds a data filter for the HTTP scheme
mStatusIntentFilter.addDataScheme("http");
...
为了给系统注册这个BroadcastReceiver和IntentFilter,需要通过LocalBroadcastManager执行registerReceiver()的方法。如下所示:
// Instantiates a new DownloadStateReceiver
DownloadStateReceiver mDownloadStateReceiver =
new DownloadStateReceiver();
// Registers the DownloadStateReceiver and its intent filters
LocalBroadcastManager.getInstance(this).registerReceiver(
mDownloadStateReceiver,
mStatusIntentFilter);
…
一个BroadcastReceiver可以处理多种类型的广播数据。每个广播数据都有自己的ACTION。这个功能使得不用定义多个不同的BroadcastReceiver来分别处理不同的ACTION数据。为BroadcastReceiver定义另外一个IntentFilter,只需要创建一个新的IntentFilter并重复执行registerReceiver()即可。例如:
/*
* Instantiates a new action filter.
* No data filter is needed.
*/
statusIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);
…
// Registers the receiver with the new filter
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
mDownloadStateReceiver,
mIntentFilter);
发送一个广播Intent并不会启动或重启一个Activity。即使是你的app在后台运行,Activity的BroadcastReceiver也可以接收、处理Intent对象。但是这不会迫使你的app进入前台。当你的app不可见时,如果想通知用户一个发生在后台的事件,建议使用Notification。永远不要为了响应一个广播Intent而去启动Activity。