如何运行后台Service?

Unless you specify otherwise, most of the operations you do in an app run in the foreground on a special thread called the UI thread. 除非特别指定,一般情况下所有在前台执行的操作都运行在名为UI的线程中。可能会存在某些隐患,因为部分在UI界面上的耗时操作可能会影响到界面的响应性能。UI界面的性能问题会容易惹恼用户,甚至可能导致系统的ANR。为了避免这样的问题,Android Framework提供了几个类,用来帮助你把那些耗时操作移动到后台线程中执行。

This annoys your users, and can even cause system errors. To avoid this, the Android framework offers several classes that help you off-load operations onto a separate thread running in the background. 经常使用的是IntentService类。

主题一:如何创建IntentService实例? --> 创建后台Service

The IntentService class provides a straightforward structure for running an operation on a single background thread. This allows it to handle long-running operations without affecting your user interface's responsiveness. 可以处理一个耗时的任务并确保不影响到UI的响应性能,IntentService的运行不受UI生命周期的影响。

IntentService存在的一些局限性:

It can't interact directly with your user interface. To put its results in the UI, you have to send them to an Activity.

IntentService不能直接和Activity交互,必须将运行结果反馈给Activity。

Work requests run sequentially. If an operation is running in an IntentService, and you send it another request, the request waits until the first operation is finished.

工作任务队列是顺序执行的;如果一个任务在IntentService中执行,此时再发送一个新的任务请求,这个任务会一直等待直到前一个任务执行完成为止。

An operation running on an IntentService can't be interrupted.

在IntentService中运行的任务不会被打断,除非进程被kill。

虽然有上述的几点局限,但IntentService仍然是执行简单后台任务的最佳选择。

如何创建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中应该避免覆写这些回调方法。

如何在AndroidManifest.xml文件中声明该Service?

注意:IntentService同样需要在AndroidManifest.xml文件中声明,类似于Service。

    <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/>

在<service>标签中并没有使用<intent-filter>,因此Activity需要使用显式Intent传递任务请求给IntentService。同时,也意味着只有在同一个app或者其他使用同一个UserID的组件才能够访问到这个Service。

主题二:向IntentService发送任务请求

如何发送一个Intent来触发IntentService执行后台任务?

通过Intent可以传递一些数据给后台任务,可以在Activity和Fragment的任何时间点发送这个Intent。

执行步骤如下:

步骤一:创建Intent对象

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

必须是显式的Intent,同时可以附带有相关的数据。

步骤二:启动该Intent,执行startService(Intent intent)

// Starts the IntentService
getActivity().startService(mServiceIntent);

注意可以在Activity或者Fragment的任何位置发送任务请求。例如,如果你先获取用户输入,您可以从响应按钮单击或类似手势的回调方法里面发送任务请求。一旦执行了startService(),IntentService在自己本身的onHandleIntent()方法里面开始执行这个任务,任务结束之后,会自动停止这个Service。

主题三:返回IntentService执行结果

我们需要获取在IntentService中执行的结果,而IntentService则需要反馈执行的结果和任务执行的状态。比如:根据后台任务执行的进度,让Activity更新UI。推荐的方法是使用LocalBroadcastManager,这个类可以限制Broadcast中的Intent只在本地App中使用。

如何反馈执行状态?

在IntentService中,创建Intent,并通过广播的方式发送出去,该Intent则附带有状态信息。

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

如何接受上述步骤发出的广播?

具体实践如下:

为了接受广播的数据对象,需要使用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。

posted @ 2017-02-12 21:24  jamesK4W  阅读(759)  评论(0编辑  收藏  举报