微信号:BetterAndroid

介绍:从Framework层原理到应用层开发的最佳实践,充分考虑碎片化阅读的特点,让您在等地铁的时间就能学会写出更优秀的app.

Android后台服务最佳实践

2016-06-05 00:32 absfree

【本篇文章的内容译自Android Developer,可以说是Android中使用后台服务(IntentService)的最佳实践。】

1.创建一个后台服务

IntentService类为"在单一后台线程中执行操作"这件事提供了一个直截了当的结构。它能够在不影响用户界面流畅度的情况下执行需要长时间运行的操作。同样,IntentService也不会被大多数用户界面的生命周期事件所影响,所以它能够在一些AsyncTask会被关闭的情形下保持运行状态。


但是IntentService存在着如下不足:

  • 它不能直接与你的用户界面交互。要把它的执行结果体现在UI上,你必须把这些结果发送个一个Activity。

  • IntentService会以串行方式执行你请求它执行的后台任务。若IntentService当前正在执行任务A,而此时你又请求它执行任务B,则IntentService在执行完任务A时,才会开始执行任务B。

  • IntentService在执行后台任务时不能被中断。


然而,在大多数情况下,IntentService还是执行简单后台任务的最佳选择。下面我们来详细介绍使用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自动调用,你应该避免重写这些方法。


(2)在Manifest文件中定义IntentService

你应该在你应用的AndroidManifest.xml文件中注册IntentService。如下所示:

    <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对应的类名。

注意,<service>元素不包含任何intent filter,因为发送“工作请求”(即请求IntentService执行一个后台任务)给IntentService的Activity必须使用显式意图(explicit Intent)。这也意味着只有相同app中的组件或是拥有相同user ID的其他app才能访问这个IntentService。

在上面我们已经定义好了一个IntentService类,那么现在我们就可以通过Intent对象发送“工作请求”给它了。


2. 向后台服务发送工作请求


(1)创建工作请求并发送给IntentService

要创建并发送给IntentService一个工作请求,只需创建一个Intent对象,并添加后台任务所需的数据,最后通过调用startService()把这个Intent对象发送给目标IntentService即可。具体过程如下:

  • 创建一个用于启动目标IntentService的Intent对象,并把执行后台任务所需的数据添加到其中:

  1. /*
     * 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():

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

注意,你可以从Activity或Fragment的任何地方向IntentService发送工作请求。比如,如果你需要获取用户输入的内容,你可以在一个响应用户点击的回调中发送工作请求。


一旦调用了startService(),目标IntentService会开始执行定义在它的onHandleIntent()方法中的工作,执行完毕后IntentService会自动停止。


下一步我们要做的是把后台任务的执行结果报告给请求执行后台任务的Activity或Fragment。


3. 报告工作结果


(1)从IntentService中报告结果

要发送后台任务的执行结果给其它组件,首先要创建一个Intent对象,然后把执行结果作为额外数据添加给它。而给这个Intent对象添加action或是data URI则并不是必须的。

创建好了一个Intent对象后,我们就可以通过LocalBroadcastManager.sendBroadcast()调用来发送这个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);
...
}

接下来我们要做的就是在发送工作请求的组件中着手处理收到的广播Intent对象了。


(2)接收来自IntentService的结果

要接收广播Intent对象,只需使用一个BroadcastReceiver的子类。在这个子类中,我们需要实现 BroadcastReceiver.onReceive()回调方法。当广播接收者收到Intent对象时,LocalBroadcastManager会调用它的onReceive()方法。也就是说,LocalBroadcastManager会把Intent对象传递到BroadcastReceiver.onReceive()方法中。示例代码如下:

// 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.
         */
...
   
}
}


一旦你已经定义了广播接收者,你可以为它定义一些过滤规则,使得它只接收特定的广播。要做到这一点,首先我们要创建一个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");
       
...


要向系统注册带有IntentFilter的广播接收者,我们只需得到一个LocalBroadcastManager实例并调用它的 registerReceiver()方法。示例代码如下:

        // Instantiates a new DownloadStateReceiver
       
DownloadStateReceiver mDownloadStateReceiver =
               
new DownloadStateReceiver();
       
// Registers the DownloadStateReceiver and its intent filters
       
LocalBroadcastManager.getInstance(this).registerReceiver(
                mDownloadStateReceiver
,
                mStatusIntentFilter
);
       
...


一个广播接收者可以处理不只一种广播Intent对象。这个特性允许你根据不同action的Intent执行不同的代码,而无需为每种action的Intent都定义一个广播接收者。要为一个相同的广播接收者定义另一个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不会开启(start)或是恢复(resume)一个Activity。甚至当你的app运行于后台时,Activity的广播接收者都会接收并处理Intent对象,而且无需系统将你的app强制运行于前台,广播接收者就能做到这一切。若在你的app不可见时,你想要通知用户后台发生了某个事件,你可以使用Notification。永远不要将”启动一个Activity“作为对广播Intent的响应方式。



 
Android开发探索 更多文章 阿里巴巴笔试题简析——Android中闪退的分析 [译]理解Android世界中的命名惯例 从源码角度看finish()方法的执行流程 十分钟理解Java中的弱引用 十分钟写一个Android轮播控件
猜您喜欢 推送通知栏 PendingIntent 参数解读 PHP100:8 个最佳 PHP 库 有多少人眼里只有胜负,却不懂让步? 我们身边的大数据 干货精选大集合!看看腾讯一年来都做了哪些H5广告?